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
分享一個公眾號,關注學習/分享的