这批.Net程序员水平不行啊!居然ASP.NET Core Middleware都不会用

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这批.Net程序员水平不行啊!居然ASP.NET Core Middleware都不会用相关的知识,希望对你有一定的参考价值。

最近问了几个面试同一个问题:如果有多个自定义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的Assembly
        var 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

以上是关于这批.Net程序员水平不行啊!居然ASP.NET Core Middleware都不会用的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC4入门到精通系列目录汇总(转)

能说一下ADO.NET 和.NET,还有asp.NET的区别吗?

在 asp.net Web 应用程序的 Gridview 中使用 shift + 滚动进行水平滚动?

asp.net 如何 刷新并关闭父页面?

asp.net页面上点击button后怎么使页面不刷新

用ASP.NET做网站时,为啥设置了showMessageBox属性为True却不会弹出窗口?