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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM