您的位置:

深入理解HttpPipeline

HttpPipeline是ASP.NET Core中重要的一个概念,这个概念在开发中经常会用到。它通过定义一系列的middleware来组织请求的处理流程,可以方便地对请求进行处理。本文将从不同的方面,探讨HttpPipeline如何工作。

一、Pipeline的组成

HTTP请求在ASP.NET Core中经过一个由若干个中间件组成的管道,这个管道被称之为Pipeline。ASP.NET Core应用程序的Startup类就是用来定义和组织Pipeline的。Startup类中有两个方法:ConfigureServices和Configure方法。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 配置依赖注入
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 配置HTTP请求管道
    }
}

其中ConfigureServices方法是用来配置依赖注入,Configure方法是用来配置HTTP请求管道。在Configure方法中,可以向管道中添加中间件,Middleware的顺序将决定它们在管道中的执行顺序。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller}/{action}/{id?}");
    });
}

上述代码中,我们向管道中添加了以下五个Middleware:

  • UseHttpsRedirection : HTTP请求重定向到HTTPS
  • UseStaticFiles : 处理静态文件请求
  • UseRouting : 处理请求路径
  • UseAuthorization : 处理授权
  • UseEndpoints : 处理路由匹配

基本的请求处理流程如下图所示:

二、Middleware的工作原理

Middleware是处理请求的核心部件,也是Pipeline的基本单位。Middleware负责处理请求并调用管道中下一个Middleware。下一个Middleware可以通过HttpContext.RequestDelegate指定,比如:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        // 处理请求前的逻辑
        await _next(context);
        // 处理请求后的逻辑
    }
}

上述代码中,MyMiddleware是一个简单的Middleware,它在处理请求前和请求后都会有相应的逻辑。在MyMiddleware的构造函数中,我们注入了HttpContext.RequestDelegate类型的参数_next,它表示了Pipeline中下一个Middleware。在MyMiddleware的InvokeAsync方法中,我们首先执行自己的逻辑,然后调用_next委托,以便让执行流程继续下去。

如果你需要在管道中使用自己的Middleware,首先需要定义这个Middleware,在Startup类的Configure方法中引入自己定义的Middleware:

public void Configure(IApplicationBuilder app)
{
    app.UseMiddleware<MyMiddleware>();
    // 省略其他Middleware的配置
}

这样,自己定义的Middleware就被引入到了Pipeline中。

三、Middleware的生命周期

ASP.NET Core中定义了三种Middleware的生命周期,分别是Transistent、Scoped、Singleton。

  • Transient: 瞬时,实例每次请求都会创建
  • Scoped: 域,每次请求都只会创建一个实例
  • Singleton: 单例,实例在应用程序生命周期内只会被创建一次

为了了解生命周期的区别,我们可以通过一个简单的示例来说明:

public interface IMyService
{
    void DoSomething();
}
public class MyService : IMyService
{
    public MyService()
    {
        Console.WriteLine("MyService实例被创建");
    }

    public void DoSomething()
    {
        Console.WriteLine("MyService正在处理事务");
    }
}
public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMyService _service;

    public MyMiddleware(RequestDelegate next, IMyService service)
    {
        _next = next;
        _service = service;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _service.DoSomething();
        await _next(context);
    }
}

上述代码中,我们定义了一个IMyService接口和MyService实现类,还有一个MyMiddleware,它需要注入IMyService类型的service。在MyMiddleware的InvokeAsync方法中,我们首先调用service.DoSomething()方法来对IMyService实例进行操作。

在Startup类的ConfigureServices方法中,我们将IMyService注册到依赖注入容器中,使用Transient生命周期:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();
}

这意味着每次请求都会创建一个新的IMyService实例。当我们使用这个Pipeline时,会看到多次创建MyService的实例:

MyService实例被创建
MyService实例被创建

然后我们将IMyService的生命周期从Transient改成Scoped:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyService, MyService>();
}

这意味着,每次请求将使用同一个IMyService实例。输出结果为:

MyService实例被创建

最后我们将生命周期改成Singleton:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMyService, MyService>();
}

这将保证整个应用程序生命周期内只会创建一个IMyService实例,输出结果为:

MyService实例被创建

四、Middleware执行顺序

在管道中,Middleware的顺序对于请求的处理流程至关重要。每个Middleware只负责它自己的业务逻辑,所以每个Middleware是互相独立的。每个Middleware的顺序会对整个Pipeline的处理造成影响。

Middleware的顺序可以通过在Configure方法中的调用顺序决定:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 20210517 加入自定义的 Middleware
    app.UseMiddleware<RequestResponseLoggingMiddleware>();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseStaticFiles();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

除此之外,Middleware还可以通过条件控制顺序。例如:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        if (context.Request.Path.StartsWithSegments("/admin"))
        {
            await context.Response.WriteAsync("你需要管理员权限才能访问管理页面");
        }
        else
        {
            await next();
        }
    });

    // 省略其他Middleware的配置
}

在上述代码中,我们使用了一个匿名Middleware,它检查请求路径是否以"/admin"开头。如果请求路径以"/admin"开头,将返回一条错误信息。这个Middleware是在管道的开始处位置,这意味着即使后面的Middleware能够处理任何类型的请求,如果请求路径以"/admin"开头,这个Middleware也会优先执行。

五、自定义Middleware

在使用ASP.NET Core时,我们可能需要自己编写一些Middleware来处理请求。编写Middleware非常简单,只需要实现Invoke方法即可:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        // 处理请求前的逻辑
        await _next(context);
        // 处理请求后的逻辑
    }
}

在ASP.NET Core中,Middleware通常是由一个Invoke方法和一个构造函数组成的。Invoke方法是用来处理请求的,构造函数是用来接收参数的。我们可以在Invoke方法中执行任何逻辑。例如,我们可以修改请求或者响应的内容,或者执行任何附加逻辑(如日志记录)。

在编写Middleware时,我们需要记住以下几点:

  • 每个Middleware必须使用下一个Middleware,否则整个Pipeline的处理过程将被打断。
  • 每个Middleware应该尽可能做到高内聚、低耦合。
  • Middleware的顺序对请求的处理流程至关重要,必须慎重选择。

六、结语

ASP.NET Core中的HttpPipeline有着重要的作用,通过定义一系列的middleware来组织请求的处理流程,方便地对请求进行处理。在实际开发中,我们会经常使用到Middleware,因此理解Pipeline的工作原理是非常重要的。希望本文能够对你有所帮助。