1. 程式人生 > 實用技巧 >Asp.net Core啟動流程講解(三)

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

分享一個公眾號,關注學習/分享的