ASP.NETCore学习记录(二) —— ASP.NET Core 中间件


 ASP.NET Core 中间件


什么是中间件 ?

  • IApplicationBuilder
  • 使用 IApplicationBuilder 创建中间件
  • Run、Map 与 Use 方法
  • 实战中间件
  • 参考原文
  •  


    我们知道在 ASP.NET 中,有一个面向切面的请求管道,由22个主要的事件构成,能够让我们在往预定的执行顺序里面添加自己的处理逻辑。一般采取两种方式:一种是直接在 Global.asax 中对应的方法中直接添加代码。一种是是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监听,并通过 HttpHandler 进入到我们的应用程序中。而在 ASP.NET Core 中,对请求管道进行了重新设计,通过使用一种称为中间件的方式来进行管道的注册,同时也变得更加简洁和强大。关于 HTTP请求流程和管道模型可以看这篇博客 


    返回顶部


    ASP.NET Core 中定义了一个 RequestDelegate 委托,来表示请求管道中的一个步骤,而对请求管道的注册是通过 Func 类型的委托(也就是中间件)来实现的。

    返回顶部

    ASP.NET Core 2.1具有新功能)。

    然后就是静态文件中间件 UseStaticFiles,静态文件中间件不提供授权检查,由它提供的任何文件,包括那些位于wwwroot下的文件都是公开可被访问的。UseCookiePolicy 是使用ASP.NET Core中的Cookie策略(详解Microsoft.AspNetCore.CookiePolicy),管道的最后执行的也就是MVC框架。

    顺序
    Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此排序对于安全性、性能和功能至关重要。请求在每一步都可能被短路,所以我们要以正确的顺序添加中间件,如异常处理,我们需要添加在最开始,这样我们就能第一时间捕获异常,以及后续中间可能发生的异常,然后最终做处理返回。

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

    public void Configure(IApplicationBuilder app)
    {
        // 使用单个请求委托来处理所有请求
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
    

    上方代码中 app.Run() 会中断管道,调用单个匿名函数来处理HTTP请求。在下面的例子中,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("Welcome!");
        });
    }
    

    通过运行项目,发现确实只有在第一个委托执行了,app.Run 终止了管道。

     


     

     

    你可以将多个请求委托 app.Use 连接在一起,next参数表示管道内的下一个请求委托。在管道中,可以通过不调用next参数来终止或短路管道。通常可以在执行下一个委托之前和之后执行一些操作,如下例所示:

    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Handing Request.\r\n");
            
            // 调用管道中的下一个委托
            await next.Invoke();
            await context.Response.WriteAsync("Finish Handing Request.\r\n");
        });
    
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello from Middleware\r\n");
        });
    }
    

    运行项目,在浏览器中访问出现如下结果:

     


     

     

    返回顶部


    http://localhost:52831 的请求和响应。

    请求响应
    http://localhost:52831 Hello from non-Map delegate.
    http://localhost:52831/map1 Map Test 1
    http://localhost:52831/map2 Map Test 2
    http://localhost:52831/map3 Hello from non-Map delegate.

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

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
            app.Run(async context =>
            {
                await context.Response.WriteAsync("

    Hello from non-Map delegate.

    "
    ); }); } private static void HandleBranch(IApplicationBuilder app) { app.Run(async context => { var branchVer = context.Request.Query["branch"]; await context.Response.WriteAsync($"Branch used = {branchVer}"); }); } }

    使用了上面的设置后,任何包含请求字符 branch 的请求将使用定义于 HandleBranch 方法内的管道,如果没有包含查询字符串 branch 的请求,将被 app.Run 所定义的委托处理。

     


     

     

    请求响应
    http://localhost:52831 Hello from non-Map delegate.
    http://localhost:52831/?branch=1 Branch used = 1
    http://localhost:52831/?branch=2 Branch used = 2
    http://localhost:52831/?branch Branch used =

    另外还可以嵌套 Maps:

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

    Map 也可以一次匹配多个片段,例如:

    app.Map("/level1/level2", HandleMultiSeg); // "/level1/level2"
    

    以下 Startup.Configure 方法将为常见应用方案添加中间件组件:

    中间件描述
    身份验证(Authentication) 提供身份验证支持
    跨域资源共享(CORS) 配置跨域资源共享
    响应支持(Response Caching) 提供缓存响应支持
    响应压缩(Response Compression) 提供响应压缩支持
    路由(Routing) 定义和约束请求路由
    会话(Session) 提供对管理用户会话(session)的支持
    静态文件(Static Files) 为静态文件和目录浏览提供服务提供支持
    URL Rewriting Middleware 用于重写 Url,并将请求重定向的支持

    更多中间件组件可以到 Microsoft 文档 上查看。

    返回顶部


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

    下面来实现一个记录IP的中间件。

    ① 新建一个 ASP.NET Core WebApplication 项目,选择空的模板。

     


     

     

    然后为项目添加—个 Microsoft.Extensions.Logging.Console。
    NuGet命令行执行(请使用官方源):

    Install-Package Microsoft.Extensions.Logging.Console
    

     


     

     

    ② 新建一个类 RequestIPMiddleware.es,将中间件委托移动到类:

    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    namespace middlewareDemo
    {
        public class RequestIPMiddleware
        {
            private readonly RequestDelegate _next;
    
            private readonly ILogger _logger;
    
            public RequestIPMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
            {
                _next = next;
                _logger = loggerFactory.CreateLogger();
            }
    
            public async Task Invoke(HttpContext context)
            {
                _logger.LogInformation("User IP:" + context.Connection.RemoteIpAddress.ToString());
                await _next.Invoke(context);
            }
        }
    }
    

    ③ 再新建—个 RequestIPExtensions.cs,以下扩展方法通过 IApplicationBuilder 公开中间件:

    using Microsoft.AspNetCore.Builder;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    namespace middlewareDemo
    {
        public static class RequestIPExtensions
        {
            public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
            {
                return builder.UseMiddleware();
            }
        }
    }
    

    这样就编写好了一个中间件。
    ④ 使用中间件。在 Startup.cs 中添加 app.UseRequestIP() 来使用中间件:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
        app.UseRequestIP();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
    

    然后运行程序,这里选择使用 Kestrel
    访问:http://localhost:5000/

     


     

     

    成功运行,到这里我们还可以对这个中间件进行进一步改进,增加更多的功能,如限制访问等。


    Microsoft 文档 ASP.NET Core 中间件

    《ASP.NET Core 跨平台开发从入门到实战》

    返回顶部