ASP.NET Core中的Startup
此为系列文章,对MSDN ASP.NET Core 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解
Startup类配置了ASP.NET Core所需要的服务以及app的请求管道。
Startup类
ASP.NET Core使用了一个Startup类,按照惯例,其被命名为Startup。Startup类主要包含:
- 可选的包含了一个ConfigureServices类用以配置应用程序的服务。服务是给app提供功能的可复用组件。服务在ConfigureServices中被注册并且通过DI或者 ApplicationServices在整个app中被消费。
- 包含一个Configure用来创建app的请求处理管道。
当app启动时,ConfigureServices
和Configure会被ASP.NET Core运行时所调用:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } }
以上的示例代码来源于Razor页面程序,MVC版本的Startup是类似的。
当app建立宿主时候,便会指定Startup方法。通常我们是在宿主建造器中通过调用WebHostBuilderExtensions.UseStartup<TStartup> 来指定一个Startup的。如下所示:
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
宿主提供器服务对于Startup的构造函数来说是可用的。而app通过ConfigureServices
来添加额外的服务。然后,宿主提供器服务以及额外添加的服务在Configure
中以及整个app中都是可用的。
当使用Generic Host (IHostBuilder)时候,只有如下的服务类型可以被注入到Startup
构造函数中:
public class Startup { private readonly IWebHostEnvironment _env; public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; _env = env; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { if (_env.IsDevelopment()) { } else { } } }
大部分服务直到调用Configure
方法之后才可用使用。
多个Startup
当app为了不同的环境定义了不同的Startup时(例如StartupDevelopment)
,在运行时ASP.NET Core会选择合适的Startup类。名称后缀匹配当前环境的类会被优先选择。如果app运行在Development环境并且都包含了一个Startup类和一个StartupDevelopment类,那么StartupDevelopment
类会被使用。更多信息,请参考Use multiple environments。
更多关于宿主的信息,请参考The host。关于在startup中处理错误的信息,请参考Startup exception handling。
ConfigureServices方法
- 可选的
- 在
Configure
方法对app的服务进行配置之前被宿主调用 - 按照惯例,在这儿配置选项被设置
在Startup方法被调用之前,宿主可能会配置一些服务,更多信息,请参考The host。
对于那些需要实质性安装的特性来说,我们可以使用IServiceCollection的Add{Service}
扩展方法。比如AddDbContext,AddDefaultIdentity,AddEntityFrameworkStores 以及 AddRazorPages:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddDefaultIdentity<IdentityUser>( options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); services.AddRazorPages(); }
将服务添加进服务容器使得它们在整个app以及Configure
方法里都是可用的。那些服务是通过dependency injection 或从ApplicationServices里进行解析。
Configure方法
Configure方法用来指定app如何响应一个HTTP 请求 。通过给一个IApplicationBuilder实例添加中间价组件的方式可用对一个请求管道进行配置。IApplicationBuilder
对于Configure方法来说是可用的,但其并不在服务容器中进行注册。宿主创建了一个IApplicationBuilder
并把它直接传递给Configure方法。
ASP.NET Core 模板 配置了请求响应管道以支持:
- Developer Exception Page
- Exception handler
- HTTP Strict Transport Security (HSTS)
- HTTPS redirection
- Static files
- ASP.NET Core MVC and Razor Pages
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } }
前面的示例是用于Razor Pages,MVC版本类似。
每一个Use扩展方法都会给请求处理管道添加一个或者多个中间件组件。比如UseStaticFiles配置了中间件用来服务static files。
请求管道中的每一个中间件组件都负责激活下一个组件或者将链条终结,如果正是这样希望的话。
其他服务,比如IWebHostEnvironment
,ILoggerFactory,或者
ConfigureServices中定义的任何东西,都可以在
Configure
方法签名中进行指定。如果这些服务是可用的,那么它们便会被注入到Configure方法。
更多关于如何使用IApplicationBuilder以及中间件处理顺序的信息,请参考ASP.NET Core Middleware。
不使用Startup来配置服务
为了不使用Startup来配置服务以及请求处理管道,我们可以在宿主构造器中调用ConfigureServices
和Configure
的方便方法。对ConfigureServices
方法的多次调用可用追加在另一个之后。如果存在多个
,那么最后Configure方法调用会被使用。Configure
方法调用
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureServices(services => { services.AddControllersWithViews(); }) .Configure(app => { var loggerFactory = app.ApplicationServices .GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger<Program>(); var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>(); var config = app.ApplicationServices.GetRequiredService<IConfiguration>(); logger.LogInformation("Logged in Configure"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } var configValue = config["MyConfigKey"]; }); }); }); }
使用Startup过滤器扩展Startup
- 可以在app配置中间件管道的开始或者结束配置中间件,而不用显示的调用
Use{Middleware}
方法。ASP.NET Core使用IStartupFilter来给管道的开始添加默认值,而不用使app的作者显式的注册默认中间件。
IStartupFilter
允许另一种不同的组件调用,其有益于app的作者。 - 创建了
Configure
方法的管道。IStartupFilter.Configure 可以设置中间件在 类库添加的中间件之前或者之后运行。
IStartupFilter
实现了Configure,其接收并返回一个Action<IApplicationBuilder>。IApplicationBuilder定义了一个类来配置一个app的请求处理管道。更多信息,请参考Create a middleware pipeline with IApplicationBuilder。
每一个IStartupFilter
都会在请求管道中添加一个或者多个中间件。过滤器以它们添加到服务容器中的顺序被激活。过滤器会在其将控制器传递给下一个过滤器之前或者之后添加中间件,因而它们会被追加到管道的开始或者末尾 。
以下示例演示了如何使用一个IStartupFilter
来注册一个中间件。此中间件RequestSetOptionsMiddleware
从查询字符串中设置了一个选项值。
public class RequestSetOptionsMiddleware { private readonly RequestDelegate _next; public RequestSetOptionsMiddleware( RequestDelegate next ) { _next = next; } // Test with https://localhost:5001/Privacy/?option=Hello public async Task Invoke(HttpContext httpContext) { var option = httpContext.Request.Query["option"]; if (!string.IsNullOrWhiteSpace(option)) { httpContext.Items["option"] = WebUtility.HtmlEncode(option); } await _next(httpContext); } }
中间件RequestSetOptionsMiddleware
在RequestSetOptionsStartupFilter
类中进行配置:
public class RequestSetOptionsStartupFilter : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return builder => { builder.UseMiddleware<RequestSetOptionsMiddleware>(); next(builder); }; } }
IStartupFilter
在ConfigureServices中的服务容器中进行注册:
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .ConfigureServices(services => { services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>(); }); }
当一个option查询字符串被提供时,在ASP.NET Core中间件呈现响应之前,中间件会对这个值进行处理。
IStartupFilter
的注册顺序决定了中间件的执行顺序:
- 多个
IStartupFilter
的实现会与一个相同的对象交互,如果顺序是重要的,我们必须对IStartupFilter
的注册进行排序,以使得这些中间件可以按本应的顺序来执行。 - 库可能会使用一个或者多个在其他 由
IStartupFilter
注册的app中间件的开始或者结束运行的IStartupFilter
实现来添加中间件。为了在一个由库的IStartupFilter
添加的中间件之前激活一个IStartupFilter
中间件,我们必须:
- 将服务注册的位置放在库被添加到服务容器之前
- 为了之后激活,将服务注册的位置放在库被添加之后
在 Startup中从外部程序集加载配置
一个IHostingStartup实现允许我们在startup中从一个外部程序集中对app进行增强。更多信息,请参考Use hosting startup assemblies in ASP.NET Core。
额外资源
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
2019-02-27 【译】索引进阶(五):带有包含列的索引