ASP.NET Core 中间件
ASP.NET Core 中间件(有时称为中间件,有时称为中间软件或模块),这也是我们系列的第一篇文章。我选择这个主题的原因是为了解释新技术提供的便利性和可能性,并更深入地探讨这个主题。
我想指出的是,在面试中,很多人并没有完全理解这个主题。不幸的是,这会导致架构错误,特别是在与 SRP(单一职责原则)相关的表示层。
ASP.NET Core 在构建时采用了模块化结构设计。继续阅读,您将了解模块化部分。ASP.NET Core 使用管道架构风格(传入的请求通过特定的中间件,每个中间件执行其任务。它不是设计模式或架构模式——它使用架构风格,我将在另一篇文章中讨论)。借助管道结构,我们可以创建中间件并将其添加到管道中,从而构成应用程序的主干。
因此,我们可以应用AOP(面向方面??编程)特性并添加小的模块,这有助于我们避免代码重复,并提供灵活、可扩展的框架结构。
当然,我知道我总结得非常简短,但目的是解释中间件结构。在另一个系列中,我将讨论与中间件相关的主题,例如自托管、OWIN 和内置功能。
让我们解释一下
在深入了解细节之前,中间件有两个主要功能。
- 它可以检查是否调用管道中的下一个中间件。
- 它可以在下一个中间件(责任链——COR)之前或之后执行操作。
ASP.NET Core 是一个抽象的 Web 框架。它提供了一个基于 OWIN 或应用程序主干,允许我们通过将应用程序作为中间件附加来创建管道。
当添加多个中间件时,管道中的每个中间件都像链一样连接在一起,它们可以相互调用。从结构上讲,它使用责任链模式,通常缩写为 COR 或链(我有时会使用链这个词)。
ASP.NET Core 对传入的请求创建响应并将其发送回用户(客户端)。中间件负责处理这些传入的请求和响应作为其任务的一部分。
框架的设计是抽象的。如果我们不将 MVC(Web 应用程序)组件添加到管道,应用程序将不会产生任何结果,因为管道中没有中间件来处理它。
要使用 MVC,我们需要将 MVC 中间件集成到 ASP.NET Core 管道中。这样我们就可以从 Controller/Action 请求中获取结果(执行 Controller,返回特定的 ActionResult,然后执行此 ActionResult,将结果写入 HttpResponse。然后用户会看到它)。或者,我们可以添加自定义中间件来处理请求或响应并获取结果。
实际上,这里我们没有 MVC 中间件。我们有一个 Route 中间件,它通过将 URI 段映射到 RouteData 来处理传入的请求。这稍后允许使用反射执行我们的 Action。
我们可以用中间件做什么?
在应用程序中,通常会使用某些模块,例如,
- 日志记录
- 身份验证/授权
- 路由
- 静态文件处理
- 响应缓存
- URL 重写
这些是中间件结构的示例。
在转到示例之前,我想指出我们使用责任链(COR)模式来创建中间件!
由于中间件以链的形式相互调用(我将使用“链”这个词,以便让每个人都更容易理解),因此它们采用 RequestDelegate 类型的对象。此对象是链中下一个或上一个中间件的实例(由于链结构),您可以控制是否调用下一个中间件。在本文的后续部分中,我将更详细地解释中间件的类型以及如何在链中使用它们。
有两种方法可以在我们的应用程序中创建自定义中间件。
使用 IMiddleware 接口(强类型中间件)。
使用基于约定的方法。
必须始终使用 InvokeAsync 方法,并且作为输入参数,它必须采用 HttpContext 类型的对象。此方法的返回类型应为 Task。
为了使中间件以责任链 (COR) 样式工作,需要创建下一个中间件对象。这是可选的 - 如果您不想调用下一个中间件,则不必定义它。这表明中间件架构非常灵活。
现在,我们将以基于约定的中间件的例子来继续我们的文章。
中间件有哪些类型?
总体来说,中间件有四种类型。这些类型只是抽象定义,也就是说,在创建中间件时,会形成某些功能抽象类型。这些类型的使用方式完全基于责任链 (COR) 模式(此处演示了链式结构)。
ASP.NET Core 应用程序中使用的功能中间件类型包括:
- 响应编辑中间件(编辑响应)
- 请求编辑中间件(编辑请求)
- 短路中间件(停止链条并且不将请求传递给下一个中间件)
- 内容生成中间件(生成内容或输出)
我们不会遵循上面列出的顺序,但请注意,因为根据功能类型,顺序很重要!
- 内容生成中间件:这是最重要的中间件类型之一。MVC 建立在此类别之上。其目的是处理传入的请求并为最终用户生成结果。
- 短路中间件:作为短路中间件的示例,我们再次使用 MVC。如果您的控制器返回的视图已被缓存,则无需继续通过中间件管道一直到 MVC 中间件。为了优化和正确构建工作,我们首先转到短路中间件,在那里我们检查数据是否已在缓存中。如果是,我们将获取缓存的数据并将其作为响应发送。但如果数据不在缓存中,我们将继续通过中间件管道并运行将生成结果的中间件。
- 请求编辑中间件: 旨在在传入请求到达链中的下一个组件(中间件)之前更改其结构。当平台的工作原理发生变化或帮助链中的下一个中间件更轻松地处理请求时,此功能非常有用。这就像将请求转换为不同的模式。
- 响应编辑中间件:这种中间件会影响或操纵处理请求和生成响应的链中其他中间件产生的结果。例如,您可以使用这种中间件来处理 HTTP 上的错误。如果在生成的响应中遇到 404 错误,则可以编写这种功能性中间件来处理该错误,例如添加自定义 404 页面或执行类似任务。
这里,链中的其他中间件首先被调用,因为被调用的中间件会生成响应。之后,我们通过添加必要的代码来干预响应,这使我们能够操纵输出。这向我们展示了链结构的灵活性。
在管道中创建并注册中间件
我们在 Startup 类中的 Configure 方法中定义中间件(有几种方法可以做到这一点,但我将展示经典和常见的方法)。这就是我们的中间件的运行方式。
事实上,在 Configure 方法中,我们通过注册中间件来创建管道,为此,我们使用 IApplicationBuilder 接口。
Configure 方法中的注册顺序很重要。添加的每个中间件都会自动连接到链中的下一个中间件(在后台使用构建器和包装器模式)。根据我们之前描述的中间件类型,它们需要以特定的方式排序。让我们首先看看如何注册它们,然后我将在本文后面回到这个主题。
有三种方式来注册中间件。
- 使用 Use() 方法
- 使用 Run() 方法
- 使用 UseMiddleware() 方法
- Use() 方法:此方法的目的是快速在管道中注册委托,我们所有的抽象函数类型都可以使用该方法。它为“HttpContext”和“RequestDelegate”提供方法签名,这意味着它接受一个请求容器并传递链中的下一个中间件(使用 Func 委托)。
- Run() 方法:该方法的作用是快速在管道中注册一个委托,但在逻辑上,它只能在上述抽象函数类型中充当内容生成中间件的角色。它为“HttpContext”提供了方法签名,例如,它传递一个请求容器对象(链委托为 RequestDelegate)。
- Map() 方法:此方法的目的不是像 Use 或 Run 那样在管道中注册中间件。相反,它添加了一个新管道,使我们能够创建内部或嵌套管道(它允许在管道内创建管道)。
它允许我们在分支内创建分支或子分支。但是,有一个重要的区别:当我们注册时,它需要一个 URI 段。这意味着内部管道只有在传入请求与此段匹配时才会工作,因此它在段条件下运行。当应用程序由多个模块组成或在特定情况下时,我们会使用此方法。例如,它为 IApplicationBuilder 对象(使用 Action 委托)提供方法签名。
最佳实践
我们使用 UseMiddleware<>() 和扩展方法以可重用、灵活且可配置的方式注册中间件。此方法允许我们利用其通用结构轻松注册中间件,使我们能够将其注册为类。
让我们使用上面的一个示例来创建一个示例。
管道内 - COR 和订购
我们将使用我们构建的示例中间件创建一个示例管道。
正如我所提到的,链中抽象函数类型的顺序非常重要。为什么?因为抽象函数所做的工作会影响链结构。链中的任何错误顺序都可能对安全性、性能和正常运行至关重要(如果链结构不正确,则可能导致应用程序无法正常工作,甚至导致错误)。让我们逐一解释一下顺序:
- 响应编辑中间件位于最前面。这是因为它涉及已处理请求的输出(响应),而不是传入的请求。例如,让我们使用我们自己的示例“HttpErrorMiddleware”。当我们收到请求时,此中间件将首先运行。它将调用链中的下一个中间件而不先处理它。在生成输出(响应)之后,当它返回时(在管道中向上),中间件将操纵最后生成的输出(响应)以最终处理它。在链运行到 MVC 之后,如果 MVC 返回 HTTP 错误,则输出将通过链发送回。我们的“HttpErrorMiddleware”最后运行以提供用户友好的输出(它操纵输出)。您会注意到异常处理中间件总是首先定义的。这是因为如果您先调用方法堆栈,则可以最后处理它,这显示了 COR 的灵活性。
- 请求编辑中间件排在第二位,因为可能需要将传入的请求转换为更灵活的格式,以便下一个中间件处理。例如,假设我们有一个大型购物网站,以前使用 PHP,现在已过渡到 ASP.NET Core。某些 URL 必须更改。为了防止链接断开和失去老用户,我们使用了 URL 重写。例如,当用户使用 URL www.shop.com/Home?/PAGE=1 时,我们在 MVC 中将其转换为 www.shop.com/Home/Index/1。这是如何工作的?传入请求 www.shop.com/Home?/PAGE=1 将进入,我们的第二个中间件将分析此 URL 并将其转换为 MVC 可以理解的格式。
- 短路中间件排在第三位,因为它可以快速生成结果,在生成输出的中间件之前停止链。为了进行优化,它将结果(响应)发送回去,而不转到下一个中??间件。缓存就是一个例子;如果之前的结果被缓存,则无需再次执行该操作。结果生成并发送回去,而无需到达内容生成中间件。它还可用于防止机器人攻击。
- 内容生成中间件是最后一种功能类型的中间件。它的工作很简单:根据传入的请求生成内容、输出或结果。最后,这个中间件产生必要的输出。例如,MVC 就属于这一类。简要说明一下 MVC 的工作原理,UseMvc() 中间件是建立在我们的路由中间件之上的。根据传入请求的路由路径对其进行分析和解析,从而创建 RouteData。然后,执行指定的 Controller 和 Action,当该过程完成时,生成的输出将添加到 HttpContext 中的 HttpResponse 类并发送回用户。
让我们通过使用“配置”方法注册我们创建的所有中间件来结束本文。所有添加的中间件都是基于约定的。