Asp.Net Core中间件学习

起因

从Asp.Net(Webform/Mvc/Api)到Asp.Net Core不同就是请求管道换成中间件,在Asp.Net WebForm到MVC请求管道(底层)变化不是很大.看Asp.Net管道可以参考这篇很早之前在博客园写的博客:重新认识Asp.Net管道模型

中间件是一种装配到应用管道以处理请求和响应的软件

作用:
  1. 选择是否将请求传递到管道中的下一个组件
  2. 可在管道中的下一个组件前后执行工作

官方文档中的关于中间件的执行图:
Asp.Net Core 中间件执行过程

第一个中间件

在Asp.Net Core Web程序中,在Startup类Configure方法增加以下代码:
app.Use(async (context, next) =>
{
    //context为HttpContext上下文包含HttpRequest(请求信息)和HttpResponse(响应信息)
    //context.Request为HttpRequest
    //context.Response为和HttpResponse
    await context.Response.WriteAsync("hello kestrel,hello asp.net core  \n");
});

在程序根目录执行命令:

dotnet run

默认配置的情况下直接访问: http://localhost:5000

访问我们编写的中间件,查看访问的内容

中间件执行顺序

编写3个中间件,并在控制台输出顺序.
app.Use(next =>
{
    Console.WriteLine("1");
    return async c =>
    {
        //_logger.LogInformation("===============/========");
        await c.Response.WriteAsync("kestrel exec middleware start 1 \n");
        await next.Invoke(c);
        await c.Response.WriteAsync("kestrel exec middleware end 1 \n");
    };
});

app.Use(next =>
{
    Console.WriteLine("2");
    return async c =>
    {
        //_logger.LogInformation("===============/========");
        await c.Response.WriteAsync("kestrel exec middleware start 2 \n");
        await next.Invoke(c);
        await c.Response.WriteAsync("kestrel exec middleware end 2 \n");
    };
});

app.Use(next =>
{
    Console.WriteLine("3");
    return async c =>
    {
        //_logger.LogInformation("===============/========");
        await c.Response.WriteAsync("kestrel exec middleware start 3 \n");
        await c.Response.WriteAsync("kestrel exec middleware end 3 \n");
    };
});

查看Kestrel启动后的打印顺序:

看Kestrel启动后的打印中间件的顺序

在浏览器中访问: http://localhost:5000

Asp.Net Core多个中间件执行的顺序

控制台的顺序和在浏览器的结果,是相反的.为什么是相反的? 带着好奇心我们去看看源码是什么样的.

github上ApplicationBuilder地址为:https://github.com/dotnet/aspnetcore/blob/v3.1.0/src/Http/Http/src/Builder/ApplicationBuilder.cs

/// <summary>
/// 存放HttpContext 委托集合
/// </summary>
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();


/// <summary>
/// Use函数只是把中间件(委托)放到集合
/// </summary>
/// <param name="middleware"></param>
/// <returns></returns>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}

public IApplicationBuilder New()
{
    return new ApplicationBuilder(this);
}

/// <summary>
/// Build函数才是执行中间件的
/// </summary>
/// <returns></returns>
public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
        // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
        var endpoint = context.GetEndpoint();
        var endpointRequestDelegate = endpoint?.RequestDelegate;
        if (endpointRequestDelegate != null)
        {
            var message =
                $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
                $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                $"routing.";
            throw new InvalidOperationException(message);
        }

        context.Response.StatusCode = StatusCodes.Status404NotFound;
        return Task.CompletedTask;
    };

    //_components调用Reverse 进行反转 所以在启动的时候先打印最后一个中间件
    //那为什么在请求的时候,中间件的执行又按照我们添加中间件的顺序呢?
    //是因为_components在循环结束,app指向的是第一个中间件
    //在第一个中间件中输出kestrel exec middleware start 1 调用 next(调用第二个) 形成链式调用
    //所以在结尾的中间件中不能进行next
    foreach (var component in _components.Reverse())
    {
        //原本是component(app)  后跟Invoke看起来更清晰
        app = component(app).Invoke();
    }

    return app;
}
秋风 2020-07-25