Asp.net Core启动流程讲解(三)

Startup.cs启动前后,做了什么?以及如何从Startup到Webapi/Mvc流程接管?

Startup

UseStartup配置了Startup初始化

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }

实际上Startup类是按照IStartup实现的非硬性约束的扩展

        public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
        {
//省略不重要的代码段

            return hostBuilder
                .ConfigureServices(services =>
                {
                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                    {
                        services.AddSingleton(typeof(IStartup), startupType);
                    }
                    else
                    {
                        services.AddSingleton(typeof(IStartup), sp =>
                        {
                            var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
                            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                        });
                    }
                });
        }

这里是不是豁然开朗?asp.net core其实内部依赖的是IStartup接口,至于Startup只是一个非IStartup硬性约束的实现

	public interface IStartup
	{
		IServiceProvider ConfigureServices(IServiceCollection services);

		void Configure(IApplicationBuilder app);
	}

Startup类依旧有一定既定约束

1、需要实现ConfigureServices方法,参数为1,且类型为 IServiceCollection,返回值可为void/IServiceProvider(asp.net core 3.0以上,返回值只能为void)
2、需要实现Configure,参数且为生命周期Singleton/Transient的Ioc容器内服务
3、在ConfigureServices方法内配置DI,Configure内启用 中间件
4、启动顺序由ConfigureServices->Configure

中间件

中间件由IApplicationBuilder扩展

常见的IApplicationBuilder.UseMvc就是中间件,其实就是基于Route的一套扩展,本质上webapi/mvc,都是Route上层的一套扩展组件,这块可以翻阅源码,不具体展开了

IApplicationBuilder.Use

下面一段演示示例

            app.Use(async (context, next) =>
            {
                Console.WriteLine("Use");
                await next.Invoke();
            });

            app.Use(async (context, next) =>
            {
                Console.WriteLine("Use1");
                await next.Invoke();
            });

            app.UseMvc();

先打印Use,然后Use1,最后完成执行。
使用Use方法运行一个委托,我们可以在Next调用之前和之后分别执行自定义的代码,从而可以方便的进行日志记录等工作。这段代码中,使用next.Invoke()方法调用下一个中间件,从而将中间件管道连贯起来;如果不调用next.Invoke()方法,则会造成管道短路。

IApplicationBuilder.Use是IApplicationBuilder Run/Map/MapWhe/Middleware的 核心 模块,基于IApplicationBuilder.Use做各种管道的扩展与实现。

IApplicationBuilder.Run

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });

            app.UseMvc();

很简单的示例,在默认api流程前,加了一段输出。段代码中,使用Run方法运行一个委托,这就是最简单的中间件,它拦截了所有请求,返回一段文本作为响应。Run委托终止了管道的运行,因此也叫作中断中间件。

IApplicationBuilder Map/MapWhen

Map创建基于路径匹配的分支、使用MapWhen创建基于条件的分支。
创建一段IApplicationBuilder.Map的示例

            app.Map("/api/test", (_map) =>
            {
                _map.Run(async (conetxt) =>
                {
                    await conetxt.Response.WriteAsync("test");
                });
            });

访问 /api/test 路由地址时,浏览器输出 test 的字符串。

再编写一段IApplicationBuilder.MapWhen 基于条件的分支示例

            app.Map("/api/test", (_map) =>
            {
                _map.MapWhen((context) =>
                {
                    return context.Request.Path == "/a";
                },(__map) => {
                    __map.Run(async (conetxt) =>
                    {
                        await conetxt.Response.WriteAsync("test a");
                    });
                });

                _map.Run(async (conetxt) =>
                {
                    await conetxt.Response.WriteAsync("test");
                });
            });

访问 /api/test 路由时,浏览器默认输出 test 字符串,当访问 /api/test/a 路由时,打印 test a 字符串。

Middleware

自定义一个Middleware

    public class ContentMiddleware
    {
        private RequestDelegate _nextDelegate;

        public ContentMiddleware(RequestDelegate nextDelegate)
        {
            _nextDelegate = nextDelegate;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext.Request.Path.ToString().ToLower() == "/middleware")
            {
                await httpContext.Response.WriteAsync(
                    "Handled by content middleware", Encoding.UTF8);
            }
            else
            {
                await _nextDelegate.Invoke(httpContext);
            }
        }
    }

访问路由 /middleware, 输出 "Handled by content middleware",反之则管道继续向下运行。

IMiddleware

UseMiddleware内部,判断类是否继承了IMiddleware,是则通过Ioc获取IMiddlewareFactory,通过IMiddlewareFactory 创建,然后在 IApplicationBuilder.Use 运行。
反之则生成表达式在 IApplicationBuilder.Use 运行。

        public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
        {
            if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
            {
                // IMiddleware doesn't support passing args directly since it's
                // activated from the container
                if (args.Length > 0)
                {
                    throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
                }

                return UseMiddlewareInterface(app, middleware);
            }

            var applicationServices = app.ApplicationServices;
            return app.Use(next =>
            {
                //省略代码段

                var factory = Compile<object>(methodInfo, parameters);

                return context =>
                {
                    var serviceProvider = context.RequestServices ?? applicationServices;
                    if (serviceProvider == null)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                    }

                    return factory(instance, context, serviceProvider);
                };
            });
        }

后记

深挖了一下中间件的相关细节,也查阅了很多作者的分享文章,参考 Asp.Net Core 3.0 源码,重学了一下中间件这块
如果对于内容有交流和学习的,可以加 .Net应用程序框架交流群,群号386092459

分享一个公众号,关注学习/分享的

posted @ 2020-08-03 14:46  沉迷代码的萌新  阅读(671)  评论(0编辑  收藏  举报