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
分享一个公众号,关注学习/分享的