中间件(Middleware)

本篇概述

  本篇介绍如何使用中间件(Middleware)。

1 初步演练

  先写几个中间件

public class DemoAMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public DemoAMiddleware(RequestDelegate next, ILogger < DemoAMiddleware > logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task Invoke(HttpContext context)
    {
        _logger.LogInformation("(1) DemoAMiddleware.Invoke front");
        await _next(context);
        _logger.LogInformation("[1] DemoAMiddleware:Invoke back");
    }
}
public class DemoBMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public DemoBMiddleware(RequestDelegate next, ILogger < DemoBMiddleware > logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task Invoke(HttpContext context)
    {
        _logger.LogInformation("(2) DemoBMiddleware.Invoke part1");
        await _next(context);
        _logger.LogInformation("[2] DemoBMiddleware:Invoke part2");
    }
}
public class RequestRecordMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public RequestRecordMiddleware(RequestDelegate next, ILogger < RequestRecordMiddleware > logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task Invoke(HttpContext context)
    {
        _logger.LogInformation("(3) RequestRecordMiddleware.Invoke");
        String URL = context.Request.Path.ToString();
        _logger.LogInformation($ "URL : {URL}");
        await _next(context);
        _logger.LogInformation("[3] RequestRecordMiddleware:Invoke next");
        _logger.LogInformation($ "StatusCode : {context.Response.StatusCode}");
    }
}

  以上中间件前两个没有做什么正经工作,就打印一些日志信息,第三个干了一点工作,它打印了用户输入的url,同时打印了返回给客户端的状态码。

  要使中间件工作,需要启用中间件。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration
    {
        get;
    }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseUnifyException();
        app.UseMiddleware < DemoAMiddleware > ();
        app.UseMiddleware < DemoBMiddleware > ();
        app.UseMiddleware < RequestRecordMiddleware > ();
        app.UseStaticFiles();
        app.UseMvcWithDefaultRoute();
    }

  通过扩展方法,我们对中间件的启用代码进行改造:

public static class RequestRecordMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestRecord(this IApplicationBuilder builder)
    {
        if(builder == null)
        {
            throw new ArgumentNullException("builder is null");
        }
        return builder.UseMiddleware < RequestRecordMiddleware > ();
    }
}

  此时启用代码由:app.UseMiddleware();

  可以修改为: app.UseRequestRecord();

  实现效果没有变化。可见下面代码都是中间件的使用。

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();

  我的理解,中间件类似车间流水线上的工人,操作的零件就是HttpContext,每个人负责两道工序,我把它们定义为“前道工序”和“后道工序”,通过代码 _next(context);

  把两道工序隔离开,处理的顺序需要特别注意,按照中间件注册的顺序依次处理“前道工序”,处理完成后,再按相反的顺序处理“后道工序”,如果某个中间件没有调用_next(context),那么就不会调用后续的中间件,所以中间件启用的顺序是需要特别考虑的。

  以上代码中三个中间件输出到控制台的信息顺序如下:

(1) (2) (3) 【3】 【2】 【1】

  个人认为,“前道工序”应重点处理Request,“后道工序”应重点处理Response。

2 做一个类似MVC的中间件

  我们做一个中间件,让其返回一些内容给客户端:

public class MyMvcMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public MyMvcMiddleware(RequestDelegate next, ILogger < DemoAMiddleware > logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task Invoke(HttpContext context)
    {
        var str = "hello,world!";
        await context.Response.WriteAsync(str);
    }
}

  这个中间件只是返回固定的字符串,我们还可以调用某个Controller的提供的方法。

public class MyMvcMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public MyMvcMiddleware(RequestDelegate next, ILogger < DemoAMiddleware > logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task Invoke(HttpContext context)
    {
        var obj = context.RequestServices.GetRequiredService < ArticleController > ().GetArticleList();
        var str = JsonConvert.SerializeObject(obj);
        await context.Response.WriteAsync(str);
    }
}

  ArticleController的定义如下:

public class ArticleController: Controller
{
    private readonly SalesContext _context;
    private readonly ILogger _logger;
    private readonly IMemoryCache _cache;
    public ArticleController(SalesContext context, ILogger < ArticleController > logger, IMemoryCache memoryCache)
        {
            _context = context;
            _logger = logger;
            _cache = memoryCache;
        }
        [HttpGet]
    public ResultObject GetArticleList()
    {
        _logger.LogInformation("==GetArticleList==");
        List < Article > articles = _context.Articles.AsNoTracking().ToList();
        return new ResultObject
        {
            result = articles
        };
    }
}

  要在中间件中通过依赖使用该Controller,需要将其注册到DI容器:

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddScoped < ArticleController > ();
 }

  以上中间件实现了一个文章信息查询的功能,如果在此中间件内先判断路径,再根据不同的路径调用不同的Contorller提供的服务,就可以形成一个简单的MVC中间件了。

3 中间件的用途

  中间件的使用体现了AOP(面向切片)的编程思想,在不修改现有代码的情况下,通过增加一些中间件实现一些特定逻辑,可以做的事情很多,比如:

URL重写

缓存处理

异常处理

用户认证

4 中间件的注册顺序

  前文提到中间件的注册顺序是比较重要的,建议的顺序如下:

  1. 异常/错误处理
  2. 静态文件服务器
  3. 身份验证
  4. MVC
版权声明: 本文为智客工坊「seabluescn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

results matching ""

    No results matching ""