zoukankan      html  css  js  c++  java
  • ASP.NET Core 中文文档 第三章 原理(2)中间件

    原文:Middleware
    作者:Steve SmithRick Anderson
    翻译:刘怡(AlexLEWIS)
    校对:许登洋(Seay)

    章节:

    查看或下载样例代码

    什么是中间件

    中间件是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求交给下一个组件、并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个 HTTP 请求。

    请求委托通过使用 IApplicationBuilder 类型的 RunMap 以及 Use 扩展方法来配置,并在 Startup 类中传给 Configure 方法 。每个单独的请求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可重用的类被称作 中间件 或 中间件组件。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。

    Migrating HTTP Modules to Middleware 解释了请求管道在 ASP.NET Core 和之前版本之间的区别,并提供了更多中间件样例。

    用 IApplicationBuilder 创建中间件管道

    ASP.NET 请求管道由一系列的请求委托所构成,它们一个接着一个被调用,如图所示(该执行线程按黑色箭头的顺序执行):
    request-delegate-pipeline

    每个委托在下一个委托之前和之后都有机会执行操作。任何委托都能选择停止传递到下一个委托,转而自己处理该请求。这被叫做请求管道的短路,而且是一种有意义的设计,因为它可以避免不必要的工作。比方说,一个授权(authorization)中间件只有在通过身份验证之后才调用下一个委托,否则它就会被短路并返回 “Not Authorized” 的响应。异常处理委托需要在管道的早期被调用,这样它们就能够捕捉到发生在管道内更深层次出现的异常了。

    你可以看一下 Visual Studio 2015 附带的默认 Web 站点模板关于请求管道设置的例子。Configure 方法增加了下列这些中间件组件:

    1. 错误处理(同时针对于开发环境和非开发环境)
    2. 静态文件服务器
    3. 身份验证
    4. MVC
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();//手工高亮
            app.UseDatabaseErrorPage();//手工高亮
            app.UseBrowserLink();//手工高亮
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");//手工高亮
        }
    
        app.UseStaticFiles();//手工高亮
    
        app.UseIdentity();//手工高亮
    
        // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
    
        app.UseMvc(routes =>//手工高亮
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    

    上面的代码中(在非开发环境时),UseExceptionHandler 是第一个被加入到管道中的中间件,因此将会捕获之后调用中出现的任何异常。

    静态文件模块 不提供授权检查,由它提供的任何文件,包括那些位于wwwroot 下的文件都是公开的可被访问的。如果你想基于授权来提供这些文件:

    1. 将它们存放在 wwwroot 外面以及任何静态文件中间件都可访问得到的目录。
    2. 利用控制器操作来判断授权是否允许,如果允许则通过返回 FileResult 来提供它们。

    被静态文件模块处理的请求会在管道中被短路(参见 Working with Static Files)。如果该请求不是由静态文件模块处理,那么它就会被传给 Identity 模块 执行身份验证。如果未通过身份验证,则管道将被短路。如果请求的身份验证没有失败,则管道的最后一站是 MVC 框架。

    注意
    你添加中间件组件的顺序通常会影响到它们处理请求的顺序,然后在响应时则以相反的顺序返回。这对应用程序安全、性能和功能很关键。在上面的代码中,静态文件中间件 在管道的早期被调用,这样就能处理并及时短路管道,以避免请求走到不必要的组件中。身份验证中间件被添加在任何需要身份认证的处理请求的前面。异常处理必须被注册在其它中间件之前以便捕获其它组件的异常。

    最简单的 ASP.NET 应用程序是使用单个请求委托来处理所有请求。事实上在这种情况下并不存在所谓的“管道”,调用单个匿名函数以相应每个 HTTP 请求。

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });
    

    第一个 App.Run 委托中断了管道。在下面的例子中,只有第一个委托(“Hello, World!”)会被运行。

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");//手工高亮
        });
    
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World, Again!");
        });
    

    将多个请求委托彼此链接在一起;next 参数表示管道内下一个委托。通过  调用 next 参数,你可以中断(短路)管道。你通常可以在执行下一个委托之前和之后执行一些操作,如下例所示:

    public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
    {
        loggerfactory.AddConsole(minLevel: LogLevel.Information);
        var logger = loggerfactory.CreateLogger(_environment);
        app.Use(async (context, next) =>//手工高亮
        {
            logger.LogInformation("Handling request.");
            await next.Invoke();//手工高亮
            logger.LogInformation("Finished handling request.");
        });
    
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from " + _environment);//手工高亮
        });
    }
    

    警告
    应当避免在修改了 HttpResponse 之后还调用管道内下一个会修改响应的组件,从而导致它被送到客户端处。

    提示
    当应用程序运行的环境设置为 LogInline 时,这个 ConfigureLogInline 方法就会被调动。要了解更多请访问环境 Working with Multiple Environments 一章。本文剩下的篇幅将使用变化的 Configure[Environment] 来展示不同的选项。 Visual Studio 中运行示例代码的最简单办法是使用 web 命令,该命令由 project.json 文件所配置。也可参考 Application Startup 。

    在上例中,调用 await next.Invoke() 将会调用下一个委托await context.Response.WriteAsync("Hello from " + _environment);。客户端将收到预期的响应(“Hello from LogInline”),同时服务端这边的控制台将先后输出如下信息:
    console-loginline

    Run,Map 与 Use

    你可以使用 RunMap 和 Use 配置 HTTP 管道。Run 方法将会短路管道(因为它不会调用 next 请求委托)。因此,Run 应该只能在你的管道尾部被调用。Run 是一种惯例,有些中间件组件可能会暴露他们自己的 Run[Middleware] 方法,而这些方法只能在管道末尾处运行。下面这两个中间件等价的,其中有用到 Use 的版本没有使用 next 参数:

    public void ConfigureEnvironmentOne(IApplicationBuilder app)
    {
        app.Run(async context =>//手工高亮
        {
            await context.Response.WriteAsync("Hello from " + _environment);
        });
    }
    
    public void ConfigureEnvironmentTwo(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>//手工高亮
        {
            await context.Response.WriteAsync("Hello from " + _environment);
        });
    }
    

    注意
    IApplicationBuilder 接口向外暴露了一个 Use 方法,因此从技术上来说它们并不完全是 扩展 方法。

    我们已经看了几个关于如何通过 Use 构建请求管道的例子,同时约定了 Map*扩展被用于分支管道。当前的实现已支持基于请求路径或使用谓词来进入分支。Map 扩展方法用于匹配基于请求路径的请求委托。Map 只接受路径,并配置单独的中间件管道的功能。在下例中,任何基于路径 /maptest 的请求都会被管道中所配置的 HandleMapTest 方法所处理。

    private static void HandleMapTest(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test Successful");
        });
    }
    
    public void ConfigureMapping(IApplicationBuilder app)
    {
        app.Map("/maptest", HandleMapTest);//手工高亮
    
    }
    

    注意
    当使用了 Map,每个请求所匹配的路径段将从 HttpRequest.Path 中移除,并附加到 HttpRequest.PathBase 中。

    除基于路径的映射外,MapWhen 方法还支持基于谓词的中间件分支,允许以非常灵活的方式构建单独的管道。任何 Func<HttpContext, bool> 类型的谓语都被用于将请求映射到新的管到分支。在下例中使用了一个简单的谓词来检测查询字符串变量 branch 是否存在:

    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Branch used.");//手工高亮
        });
    }
    
    public void ConfigureMapWhen(IApplicationBuilder app)
    {
        app.MapWhen(context => {//手工高亮
            return context.Request.Query.ContainsKey("branch");//手工高亮
        }, HandleBranch);//手工高亮
    
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from " + _environment);
        });
    }
    

    使用了上述设置后,任何包含请求字符 branch 的请求将使用定义于HandleBranch 方法内的管道(其响应就将是“Branch used.”)。其他请求(即没有为 branch 定义查询字符串值)将被第 17 行所定义的委托处理。

    你也可以嵌套映射:

    app.Map("/level1", level1App => {
        level1App.Map("/level2a", level2AApp => {
            // "/level1/level2a"
            //...
        });
        level1App.Map("/level2b", level2BApp => {
            // "/level1/level2b"
            //...
        });
    });
    

    内置中间件

    ASP.NET 带来了下列中间件组件:

    中间件 描述
    身份验证(Authentication) 提供身份验证支持。
    跨域资源共享(CORS) 配置跨域资源共享。CORS 全称为 Cross-Origin Resource Sharing。
    路由(Routing) 定义和约定请求路由。
    会话(Session) 提供对管理用户会话(session)的支持。
    静态文件 提供对静态文件服务于目录浏览的支持。

    编写中间件

    CodeLabs 中间件教程 提供了一个清晰介绍用于编写中间件。

    对于更复杂的请求处理功能,ASP.NET 团队推荐在他们自己的类中实现中间件,并暴露 IApplicationBuilder 扩展方法,这样就能通过 Configure 方法来被调用。之前演示的简易日志中间件就能被转换为一个中间件类(middleware class):只要在其构造函数中获得下一个 RequestDelegate 并提供一个 Invoke方法,如下所示:

    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    
    namespace MiddlewareSample
    {
        public class RequestLoggerMiddleware
        {
            private readonly RequestDelegate _next;
            private readonly ILogger _logger;
    
            public RequestLoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)//手工高亮
            {
                _next = next;
                _logger = loggerFactory.CreateLogger<RequestLoggerMiddleware>();
            }
    
            public async Task Invoke(HttpContext context)//手工高亮
            {
                _logger.LogInformation("Handling request: " + context.Request.Path);
                await _next.Invoke(context);
                _logger.LogInformation("Finished handling request.");
            }
        }
    }
    

    中间件遵循 显式依赖原则 并在其构造函数中暴露所有依赖项。中间件能够利用到 UseMiddleware 扩展方法的优势,直接通过它们的构造函数注入服务,就像下面的例子所示。依赖注入服务是自动完成填充的,扩展所用到的 params 参数数组被用于非注入参数。

    public static class RequestLoggerExtensions
    {
        public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestLoggerMiddleware>();//手工高亮
        }
    }
    

    通过使用扩展方法和相关中间件类,Configure 方法变得非常简洁和高可读性。

    public void ConfigureLogMiddleware(IApplicationBuilder app,
        ILoggerFactory loggerfactory)
    {
        loggerfactory.AddConsole(minLevel: LogLevel.Information);
    
        app.UseRequestLogger();//手工高亮
    
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from " + _environment);
        });
    }
    

    尽管 RequestLoggerMiddleware 在其构造函数中需要 ILoggerFactory 参数,但无论是 Startup 类还是 UseRequestLogger 扩展方法都不需要显式依赖之。相反,它将自动地通过内置的 UseMiddleware<T> 来执行依赖注入以提供之。

    测试中间件(通过给 LogMiddleware 设置 Hosting:Environment 环境变量)会输出下图的结果(当时用了 WebListener 时):
    console-logmiddleware

    注意
    UseStaticFiles 扩展方法(该方法会创建 StaticFileMiddleware)同样也使用了 UseMiddleware<T>。所以除了 StaticFileOptions 参数被传入之外,构造函数的其他参数都由 UseMiddleware<T> 和依赖注入所提供。

    扩展资源

    返回目录

  • 相关阅读:
    在C#代码中应用Log4Net(二)典型的使用方式
    在C#代码中应用Log4Net(一)简单使用Log4Net
    Windows Azure Active Directory (2) Windows Azure AD基础
    Windows Azure Virtual Network (6) 设置Azure Virtual Machine固定公网IP (Virtual IP Address, VIP) (1)
    Windows Azure Active Directory (1) 前言
    Azure China (6) SAP 应用在华登陆 Windows Azure 公有云
    Microsoft Azure News(3) Azure新的基本实例上线 (Basic Virtual Machine)
    Microsoft Azure News(2) 在Microsoft Azure上运行SAP应用程序
    Microsoft Azure News(1) 新的数据中心Japan East, Japan West and Brazil South
    Windows Azure HandBook (2) Azure China提供的服务
  • 原文地址:https://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-3_2-middleware.html
Copyright © 2011-2022 走看看