最近问了几个面试同一个问题:如果有多个自定义Middleware,如何控制它们的执行顺序(比如先判断用户合法再写访问日志)。居然大部分人答不上来?!
对此,你有什么看法?
ASP.NET Core Middleware的调用顺序
这里我直接贴上官方文档[1]中的图片:
ASP.NET Core 请求管道包含一系列请求委托,依次调用。而调用顺序实际上就是我们在Startup.cs中注册(使用UseMiddlewareExtensions.UseMiddleware方法
)它们的顺序。
示例代码如下:
public void ConfigureServices(IServiceCollection services)
{//注册生命周期services.AddTransient<TransientMiddleware>();services.AddScoped<ScopedMiddleware>();
}public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{//注册调用顺序app.UseMiddleware<TransientMiddleware>();app.UseMiddleware<ScopedMiddleware>();
}
但这种方式,对于调整自定义Middleware的需求,需要经常修改Startup.cs,而且会使代码比较凌乱,可读性较差。
可以使用下面的方式,简化注册代码:
public void ConfigureServices(IServiceCollection services)
{services.AddMiddlewares();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{app.UseMiddlewares();
}
实现方式
1.定义Attribute
MiddlewareRegisterAttribute
将放在每个Middleware实现类上,表明它是需要被注册的Middleware。
[AttributeUsage(AttributeTargets.Class)]
public class MiddlewareRegisterAttribute : Attribute
{//注册顺序public int Sort { get; set; } = int.MaxValue;//生命周期public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped;
}
2.定义注册信息类
MiddlewareRegisterInfo
用于存放Middleware的注册信息,供注册方法调用。
public class MiddlewareRegisterInfo
{public MiddlewareRegisterInfo(Type type,MiddlewareRegisterAttribute attribute){Type = type;Sort = attribute.Sort;Lifetime = attribute.Lifetime;}public Type Type { get; private set; }public int Sort { get; private set; }public ServiceLifetime Lifetime { get; private set; }
}
2.实现注册扩展方法
读取Assembly中的Type, 如果存在MiddlewareRegisterAttribute
就把它放入List<MiddlewareRegisterInfo>
列表中,最后根据Sort
属性顺序依次注册,代码如下:
public static class MiddlewareRegisterExtensions
{private static readonly IEnumerable<MiddlewareRegisterInfo> _middlewareRegisterInfos = GetMiddlewareRegisterInfos();public static IServiceCollection AddMiddlewares(this IServiceCollection services){foreach (var middlewareRegisterInfo in _middlewareRegisterInfos){switch (middlewareRegisterInfo.Lifetime){case ServiceLifetime.Singleton:services.AddSingleton(middlewareRegisterInfo.Type);break;case ServiceLifetime.Transient:services.AddTransient(middlewareRegisterInfo.Type);break;default:services.AddScoped(middlewareRegisterInfo.Type);break;}}return services;}public static IApplicationBuilder UseMiddlewares(this IApplicationBuilder applicationBuilder){foreach (var middlewareRegisterInfo in _middlewareRegisterInfos){applicationBuilder.UseMiddleware(middlewareRegisterInfo.Type);}return applicationBuilder;}private static List<MiddlewareRegisterInfo> GetMiddlewareRegisterInfos(){var middlewareRegisterInfos = new List<MiddlewareRegisterInfo>();//所有包含Middleware的Assemblyvar assemblies = new Assembly[] { typeof(Startup).Assembly };foreach (var assembly in assemblies){foreach (var type in assembly.GetTypes().Where(x => !x.IsAbstract)){var attribute = type.GetCustomAttribute<MiddlewareRegisterAttribute>();if (attribute != null){middlewareRegisterInfos.Add(new MiddlewareRegisterInfo(type, attribute));}}}return middlewareRegisterInfos.OrderBy(p=>p.Sort).ToList();}
}
测试一下
创建3个Middleware,功能仅仅是输出日志:
[MiddlewareRegister(Sort = 100)]
public class OneMiddleware : IMiddleware
{private readonly ILogger<OneMiddleware> logger;public OneMiddleware(ILogger<OneMiddleware> logger){this.logger = logger;}public async Task InvokeAsync(HttpContext context, RequestDelegate next){logger.LogInformation("One");await next(context);}
}
Sort
用100、200、300,方便后面修改排序。
运行效果如下图:
现在,将ThreeMiddleware的Sort
改为150,调整注册顺序,再次运行,效果如下图:
结论
当然,这个解决方案也存在一些缺点,比如修改排序的位置移到每个Middleware,比较分散。但整体来说,代码更易读!
如果你有其他实现方式,欢迎到公众号后台留言指教。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“,记住我!
参考资料
[1]
官方文档: https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware