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