Skip to main content

中间件- Middleware

分类:  Asp.net Core入门 标签:  #Asp.Net core基础 #基础 #Web 发布于: 2023-06-04 20:18:05

什么是中间件(Middleware)? 中间件是一种装配到应用管道里处理请求和回应的软件,每个中间件组件都需要:

  • 选择是否在管道里传递请求到下一个中间件组件中。
  • 可以在调用下一个中间件组件之前或者是之后运行相应的代码,完成相应的任务.

ASP.net Core框架中,请求委托(Request delegate)用来设计应用的管道(App pipeline), 任何一个HTTP请求都从应用管道处理过。ASP.net Core使用三个扩展方法来配置请求委托:

  • use扩展方法
  • Run扩展方法
  • Map扩展方法

任何一个请求委托可以指定未一个匿名的方法,或者是一个可重用的类,这个匿名方法或者可重用的类,在这里我们就称为- 中间件,或者中间件组件。中间件负责在应用管道中决定是否将请求传递给下一个组件,或者直接在本组件里短路(也就是不调用下一个组件), 如果请求在中间件里不再调用下一个中间件组件,短路,我们称为中间件终结,因为它阻止了请求被传到下一个组件中。

使用IApplicationBuilder创建中间件管道

ASP.net Core的请求管道中,包含一系列的请求委托,按照顺序一个接一个的调用,下图可以说明这个设计,注意执行线程按照箭头的顺序执行:


每个中间件都要考虑是否将请求传递给下一个组件,都要可以在下一个组件调用之前或者之后进行一些处理。

最简单的中间件

我们这里使用Use扩展方法展示一个最简单的中间件:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

运行这个应用,就可以很容易的看到结果。

扩展方法RunUseMapMapWhenUseWhenUseMiddlerware<T>

在整个应用管道里,可以通过如上的方法来调用或者定义一个中间件组件,但是这个几个方法是有些区别的:

  • Run扩展方法只有一个HpptContext的参数,而没有下一个调用组件的参数RequestDelegate, 因此run一定是短路中间组件,因此一般最好是放在最后。
  • Use扩展可以把所有的中间件连接起来,一般使用这个。
  • MapMapWhenUseWhen:
    • 想想一下应用管道就像一条管子,但是MapMapWhenUseWhen就像一个管道从某一个点分支执行了。
    • Map只要符合路径匹配,就回执行这个分支。
    • MapWhen, 根据给定条件判断是否要执行某个分支, 不像Map要根据给定路径匹配。
    • UseWhen, 根据指定的条件判断是否要执行某个分支,但是执行完分支后,还会回到主线。前面两个可不会。
  • UseMiddleware<T>主要给用户自定义按照约定编写的中间类的,这个我们后面再来学习。

我们看一些例子:

Use 和 Run:

注意这里的run就直接短路了,但是Use把前后两个组件都串联起来了。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

Map

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

MapWhen:

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

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

UseWhen:

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

关于UseMiddlerware<T>后面自定义中间件组件再来学习。

ASP.net Core自带的中间件

我们前面学习了中间件是在Startup类中通过IApplicationBuilder的几个扩展方法来添加到应用,组件应用管道的。同时我们也注意到中间件是在应用启动的时候通过方法Configure(IWebHostingBuilder)来启用的。同时注意的是请求委托是一个接一个的执行,因此每个中间件的执行顺序就非常重要了。我们看一下ASP.net Core自带的中间件执行顺序:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

同时以图片做更进一步的说明:


可以看到最后一个中间件是Endpoint, 然后再看一下MVC或者Razor Page里的endpoint的流程:


再来看一个例子:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

编写自定义的中间件

目前支持两种中间件的方式,一种是按照约定的编写中间的方式,另外一种是利用已经实现注册在ASP.net Core容器中的IMiddleWareFactory结合对接口IMiddleware的实现来创建中间件,关于这两种方式的最大区别是:按照约定编写中间件的形式是中间件在应用启动的时候就已经初始化了,它不是根据用户的请求来的,所以如果要想注册依赖服务,必须从它的构造函数注入,另外如果想注册基于scoped的服务,那么就必须要在Invoke方法中注入。而使用IMiddleWareFactoryIMiddleware来创建的是基于Scoped request来自动创建和回收的。下面这个例子一起演示了如何通过这两种方式来创建自己的中间件。

一般情况有这样几个步骤:

  • 实现一个类,按照约定或者是实现接口IMidleWare
  • 定义一个IApplicationBuilder的扩展方法,扩展方法中使用UseMiddleware调用中间件。
  • [可选], 如果是基于接口IMiddleware定义,需要在ConfigureServicer中添加中间件服务。
  • 在Configure中调用该中间件。

定义约定式中间件:

public class ConventionalMiddleware
{
    private readonly RequestDelegate _next;

    public ConventionalMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, AppDbContext db)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "ConventionalMiddleware", 
                    Value = keyValue
                });

            await db.SaveChangesAsync();
        }

        await _next(context);
    }
}

定义实现IMiddleware的中间件:

public class FactoryActivatedMiddleware : IMiddleware
{
    private readonly AppDbContext _db;

    public FactoryActivatedMiddleware(AppDbContext db)
    {
        _db = db;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            _db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "FactoryActivatedMiddleware", 
                    Value = keyValue
                });

            await _db.SaveChangesAsync();
        }

        await next(context);
    }
}

**定义IApplicationBuilder的扩展方法:

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseConventionalMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ConventionalMiddleware>();
    }

    public static IApplicationBuilder UseFactoryActivatedMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<FactoryActivatedMiddleware>();
    }
}

实现IMiddleWare接口的中间件需要在ConfigureService中添加服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options =>
        options.UseInMemoryDatabase("InMemoryDb"));

    services.AddTransient<FactoryActivatedMiddleware>();

    services.AddRazorPages();
}

Configure中使用中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }

    app.UseConventionalMiddleware();
    app.UseFactoryActivatedMiddleware();

    app.UseStaticFiles();
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

中间件就介绍到这里了。