理解ASP.NET Core 中间件(Middleware) |
中间件先借用微软官方文档的一张图:
可以看到,中间件实际上是一种配置在HTTP请求管道中,用来处理请求和响应的组件 。它可以:
此外,中间件的注册是有顺序的,书写代码时一定要注意! 中间件管道Run该方法为HTTP请求管道添加一个中间件,并标识该中间件为管道终点,称为终端中间件 。也就是说,该中间件就是管道的末尾,在该中间件之后注册的中间件将永远都不会被执行 。所以,该方法一般只会书写在
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}
Use通过该方法快捷的注册一个匿名的中间件
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// 下一个中间件处理之前的操作
Console.WriteLine("Use Begin");
await next();
// 下一个中间件处理完成后的操作
Console.WriteLine("Use End");
});
}
}
注意:
以下都是错误的代码写法 错误1:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Use");
await next();
});
app.Run(context =>
{
// 由于上方的中间件已经开始 Response,此处更改 Response Header 会抛出异常
context.Response.Headers.Add("test", "test");
return Task.CompletedTask;
});
}
}
错误2:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Use");
// 即使没有调用 next.Invoke / next(),也不能在 Response 开始后对 Response 进行更改
context.Response.Headers.Add("test", "test");
});
}
}
UseWhen通过该方法针对不同的逻辑条件创建管道分支 。需要注意的是: 进入了管道分支后,如果管道分支不存在管道短路或终端中间件,则会再次返回到主管道 。 当使用 支持嵌套,即UseWhen中嵌套UseWhen等 支持同时匹配多个段,如 /get/user
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// /get 或 /get/xxx 都会进入该管道分支
app.UseWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
{
app.Use(async (context, next) =>
{
Console.WriteLine("UseWhen:Use");
await next();
});
});
app.Use(async (context, next) =>
{
Console.WriteLine("Use");
await next();
});
app.Run(async context =>
{
Console.WriteLine("Run");
await context.Response.WriteAsync("Hello World!");
});
}
}
当访问 /get 时,输出如下:
Map
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// 访问 /get 时会进入该管道分支
// 访问 /get/xxx 时会进入该管道分支
app.Map("/get", app =>
{
app.Use(async (context, next) =>
{
Console.WriteLine("Map get: Use");
Console.WriteLine($"Request Path: {context.Request.Path}");
Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
await next();
});
app.Run(async context =>
{
Console.WriteLine("Map get: Run");
await context.Response.WriteAsync("Hello World!");
});
});
// 访问 /post/user 时会进入该管道分支
// 访问 /post/user/xxx 时会进入该管道分支
app.Map("/post/user", app =>
{
// 访问 /post/user/student 时会进入该管道分支
// 访问 /post/user/student/1 时会进入该管道分支
app.Map("/student", app =>
{
app.Run(async context =>
{
Console.WriteLine("Map /post/user/student: Run");
Console.WriteLine($"Request Path: {context.Request.Path}");
Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
await context.Response.WriteAsync("Hello World!");
});
});
app.Use(async (context, next) =>
{
Console.WriteLine("Map post/user: Use");
Console.WriteLine($"Request Path: {context.Request.Path}");
Console.WriteLine($"Request PathBase: {context.Request.PathBase}");
await next();
});
app.Run(async context =>
{
Console.WriteLine("Map post/user: Run");
await context.Response.WriteAsync("Hello World!");
});
});
}
}
当你访问 /get/user 时,输出如下:
当你访问 /post/user/student/1 时,输出如下:
其他情况交给你自己去尝试啦! MapWhen与
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// /get 或 /get/xxx 都会进入该管道分支
app.MapWhen(context => context.Request.Path.StartsWithSegments("/get"), app =>
{
app.MapWhen(context => context.Request.Path.ToString().Contains("user"), app =>
{
app.Use(async (context, next) =>
{
Console.WriteLine("MapWhen get user: Use");
await next();
});
});
app.Use(async (context, next) =>
{
Console.WriteLine("MapWhen get: Use");
await next();
});
app.Run(async context =>
{
Console.WriteLine("MapWhen get: Run");
await context.Response.WriteAsync("Hello World!");
});
});
}
}
当你访问 /get/user 时,输出如下:
可以看到,即使该管道分支没有终端中间件,也不会回到主管道 。 Run & Use & UseWhen & Map & Map一下子接触了4个命名相似的、与中间件管道有关的API,不知道你有没有晕倒,没关系,我来帮大家总结一下:
编写中间件并激活上面已经提到过的 基于约定的中间件“约定大于配置”,先来个约法三章:
通过DI填充时,只能接收 Transient 和 Singleton 的DI参数 。这是由于中间件是在应用启动时构造的(而不是按请求构造),所以当出现 Scoped 参数时,构造函数内的DI参数生命周期与其他不共享,如果想要共享,则必须将Scoped DI参数添加到 通过
一个简单的中间件如下:
public class MyMiddleware
{
// 用于调用管道中的下一个中间件
private readonly RequestDelegate _next;
public MyMiddleware(
RequestDelegate next,
ITransientService transientService,
ISingletonService singletonService)
{
_next = next;
}
public async Task InvokeAsync(
HttpContext context,
ITransientService transientService,
IScopedService scopedService,
ISingletonService singletonService)
{
// 下一个中间件处理之前的操作
Console.WriteLine("MyMiddleware Begin");
await _next(context);
// 下一个中间件处理完成后的操作
Console.WriteLine("MyMiddleware End");
}
}
然后,你可以通过
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<MyMiddleware>();
}
}
不过,一般不推荐直接使用
public static class AppMiddlewareApplicationBuilderExtensions
{
public static IApplicationBuilder UseMy(this IApplicationBuilder app) => app.UseMiddleware<MyMiddleware>();
}
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMy();
}
}
基于工厂的中间件优势:
该方式的实现基于
public interface IMiddlewareFactory
{
IMiddleware? Create(Type middlewareType);
void Release(IMiddleware middleware);
}
public interface IMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
你有没有想过当我们调用 注意,基于工厂的中间件,在应用的服务容器中一般注册为 Scoped 或 Transient 服务 。 这样的话,咱们就可以放心的将 Scoped 服务注入到中间件的构造函数中了 。 接下来,咱们就来实现一个基于工厂的中间件:
public class YourMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// 下一个中间件处理之前的操作
Console.WriteLine("YourMiddleware Begin");
await next(context);
// 下一个中间件处理完成后的操作
Console.WriteLine("YourMiddleware End");
}
}
public static class AppMiddlewareApplicationBuilderExtensions
{
public static IApplicationBuilder UseYour(this IApplicationBuilder app) => app.UseMiddleware<YourMiddleware>();
}
然后,在
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<YourMiddleware>();
}
}
最后,在
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseYour();
}
}
微软提供了
public class MiddlewareFactory : IMiddlewareFactory
{
// The default middleware factory is just an IServiceProvider proxy.
// This should be registered as a scoped service so that the middleware instances
// don't end up being singletons.
// 默认的中间件工厂仅仅是一个 IServiceProvider 的代理
// 该工厂应该注册为 Scoped 服务,这样中间件实例就不会成为单例
private readonly IServiceProvider _serviceProvider;
public MiddlewareFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IMiddleware? Create(Type middlewareType)
{
return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;
}
public void Release(IMiddleware middleware)
{
// The container owns the lifetime of the service
// DI容器来管理服务的生命周期
}
}
可以看到,该工厂使用过DI容器来解析出服务实例的 。因此,当使用基于工厂的中间件时,是无法通过 基于约定的中间件 VS 基于工厂的中间件
到此这篇关于理解ASP.NET Core 中间件(Middleware)的文章就介绍到这了,更多相关ASP.NET Core Middleware内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! |