zoukankan      html  css  js  c++  java
  • ASP.NET 5基础之中间件

    来源https://docs.asp.net/en/latest/fundamentals/middleware.html

    一些可以整合进http请求管道的小的应用组件称做中间件。ASP.NET 5集成支持中间件,可以通过应用的Configure方法来配置中间件。

    什么是中间件###

    中间件是一些可以装配在应用请求管道中的一些组件,可以用来处理请求和响应。每个中间件可以觉得是否将请求转给管道中的下一个组件,也可以在下一个组件前或后执行一些操作。Request delegates用来构建请求管道,然后处理每一个到应用的请求。

    Request delegates是通过IApplicationBuilder类型的Run,Map,Use扩展方法来配置,IApplicationBuilder是在Startup类中传给Configure方法的。request delegate可以是内嵌的匿名方法,也可以是定义在一个可服用的类中。这写可服用的类就是中间件,管道中的每个中间件都负责invoke管道中的下一个中间件,或者在适当的情况下之间短路管道中的request delegate链(直接返回)。

    通过IApplicationBuilder来创建中间件管道##

    ASP.NET请求管道由一系列的请求代理(request delegate)组成,一个接一个的被调用。如下图所示,黑色箭头标识执行线程
    pipeline

    每个代理都在可以在下一个代理之前和之后执行一些操作。任何一个代理可以选择是将请求转给下一个代理还是自己进行处理。直接处理就是短路执行管道,这样可以在某些情况下避免执行无谓的操作。比如授权组件,可以在授权通过的情况下将请求转个下一个组件,或者在未授权的情况下直接返回未授权。还有异常处理组件,要在管道的较前方来调用,这样才能捕获后面组件链上抛出的异常。
    在默认的web模板中,你可以看到用请求代理来配置管道的例子,如下Configure方法,首先wire up 错误页面(in development)或者网站的生产环境的error handler,然后构建了支持static files, ASP.NET Identity authentication, and finally, ASP.NET MVC的管道。

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
    
        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
    
        app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
    
        app.UseStaticFiles();
    
        app.UseIdentity();
    
        // To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    

    因为管道是按顺序构建的,在生产环境下UseExceptionHandler配置在了管道的较前方,所以能够捕获管道上稍后被调用的组件抛出的异常。而且在这里,UseStaticFiles是在UseIdentity之前配置的,这样静态文件就不需要授权认证,这样可以提高性能(ASP.NET 5中静态文件都在wwwroot,默认是都可以访问的,是不需要执行授权组件的 )。对与非静态文件的请求则可以流转到管道中的下一个组件。 Learn more about Working with Static Files.

    Note: 在Configure方法中配置的中间件Use[Middleware],顺序很重要。

    最简单的ASP.NET应用就是设置一个请求代理来处理所有请求。在这种情况下没有“真正的”请求管道,因为只有一个匿名函数来处理所有请求。

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

    这里很重要的一点是,如上所写的请求代理会终结管道,不管你是否有别的App.Run调用。在下面的例子中,只有第一个代理会被调用。

    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调用来将多个request代理链接起来,这里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);
        });
    }
    

    警告: Be wary of modifying HttpResponse after invoking next, since one of the components further down the pipeline may have written to the response, causing it to be sent to the client.(在invoke next之后尽量不要修改HttpResponse,因为管道后面的组件可能已经写入了response)
    Note: 环境设置为LogInline,ConfigureLogInline方法会在应用运行时调用。更多见Working with Multiple Environments.在剩下的文章中,我们能够使用Configure[Environment]来显示不同的选项。最简单的运行样例的方式是使用web命令,它是在project.json中配置的。

    在上面的例子中,await next.Invoke() 会使程序进入到第14行,客户端收到预期的响应“Hello from LogInline”,然后服务器控制台输出组件前和组件后的消息,如下所示。
    console

    Run, Map, and Use###

    可以通过扩展方法Run, Map, and Use来配置管道。按照约定,Run是一种简便的添加中间件到管道的方法,并且不会调用其他剩余中间件。所以Run应该在管道的最后被调用。Run是一种约定,一些中间件暴露出它的Run[Middleware]方法,这些方法应该只在管道的末端调用。如下例子(one using Run and the other 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(next => async context =>
        {
            await context.Response.WriteAsync("Hello from " + _environment);
        });
    }
    

    Note: IApplicationBuilder接口本身暴露出了Use方法,所以技术上他们不全是扩展方法。
    关于Use的例子,我们已经看了好几个。Map*扩展方法是一种branching the pipeline的约定。当前的实现支持基于请求路径的分支和使用断言的(predicate)。Map扩展方法可以用来基于请求路径来匹配请求代理。Map 仅仅接受路径和配置单独中间件的管道的函数。如下例所示,根路径为/base path的请求,都会由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);
    
    }
    

    Note: 当使用Map时,对每个请求,匹配的path segment会从HttpResponse.Path中移除,并添加到HttpRequest.PathBase.

    除了基于路径的匹配,MapWhen支持基于断言的管道分支,运行灵活的构建一个seperate管道。任何满足Func<HttpContext, bool>的断言,都可以使用,将请求map到一个新的管道。在下例中,一个简单的断言来检查请求参数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);
        });
    }
    

    在此例中任何满足断言的请求都会由定义在HandleBranch中的管道来处理。其他的请求在会有17行的定义的代理来处理。

    Built-in middleware(内建中间件)##

    ASP.NET 内有如下自带中间件

    Middleware Description
    Authentication Provides authentication support.
    CORS Configures Cross-Origin Resource Sharing.
    Diagnostics Includes support for error pages and runtime information.
    Routing Define and constrain request routes.
    Session Provides support for managing user sessions.
    Static Files Provides support for serving static files, and directory browsing.
    ##Writing middleware(编写中间件)## 对于较附加的请求处理函数,建议使用单独的类来构建中间件,通过暴露出可以在Configure中调用的IApplicationBuilder的扩展方法。把之前简单的logging 中间件转换到单独的一个类中,并把RequestDelegate 作为构造器参数,支持Invoke方法,如下。 ``` using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.Framework.Logging; using System.Threading.Tasks;

    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.");
        }
    }
    

    }

    中间件遵循 [Explicit Dependencies Principle](http://deviq.com/explicit-dependencies-principle/)。并通过构造器暴露出所有依赖。中间件利用 UseMiddleware<T>扩展来直接注入所依赖服务,如下。依赖注入的服务是自动填充的,the extension takes a params array of arguments to be used for non-injected parameters。
    

    public static class RequestLoggerExtensions
    {
    public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
    {
    return builder.UseMiddleware();
    }
    }

    使用扩展方法和相关连的中间件类,使得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>内的依赖注入操作自动提供的。
    Testing the middleware (by setting the ASPNET_ENV environment variable to LogMiddleware) should result in output like the following (when using WebListener):
    ![logging](https://docs.asp.net/en/latest/_images/console-logmiddleware.png)
    
    **Note:**另一个是用UseMiddleware<T>的例子是 UseStaticFiles扩展方法,它使用必须的构造器参数来创建StaticFileMiddleware 。在这个例子中,StaticFileOptions参数是传进来的,别的构造器参数则是UseMiddleware<T>依赖注入提供的。
    
    ##Summary##
    Middleware provide simple components for adding features to individual web requests. Applications configure their request pipelines in accordance with the features they need to support, and thus have fine-grained control over the functionality each request uses. Developers can easily create their own middleware to provide additional functionality to ASP.NET applications.
  • 相关阅读:
    【Java基础】浅谈常见设计模式
    面试中的排序算法总结
    Spring Boot中静态资源(JS, 图片)等应该放在什么位置
    分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)
    分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)
    分布式缓存技术redis学习系列(二)——详细讲解redis数据结构(内存模型)以及常用命令
    Springmvc参数绑定
    SpringMVC框架的图片上传
    全文检索技术ElasticSearch
    MQ(Message Queue)消息队列
  • 原文地址:https://www.cnblogs.com/yanbinliu/p/5148855.html
Copyright © 2011-2022 走看看