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的工作原理是非常重要的。希望本文能够对你有所帮助。