CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
理解ASP.NET Core - Startup
0
.NetCore
小笨蛋
发布于:2022年08月29日
更新于:2022年08月29日
123
#custom-toc-container
### 准备工作:一份ASP.NET Core Web API应用程序 > 当我们来到一个陌生的环境,第一件事就是找到厕所在哪。 当我们接触一份新框架时,第一件事就是找到程序入口,即Main方法 ```csharp 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
(); }); } ``` 代码很简单,典型的建造者模式:通过IHostBuilder创建一个通用主机(Generic Host),然后启动它(至于什么是通用主机,咱们后续的文章会说到)。咱们不要一上来就去研究CreateDefaultBuilder、ConfigureWebHostDefaults这些方法的源代码,应该去寻找能看的见、摸得着的,很明显,只有Startup。 ### Startup类 Startup类承担应用的启动任务,所以按照约定,起名为Startup,不过你可以修改为任意类名(强烈建议类名为Startup)。 默认的Startup结构很简单,包含: - 构造函数 - Configuration属性 - ConfigureServices方法 - Configure方法 ```csharp public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. // 该方法由运行时调用,使用该方法向DI容器添加服务 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // 该方法由运行时调用,使用该方法配置HTTP请求管道 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { } } ``` #### Startup构造函数 当使用通用主机(Generic Host)时,Startup构造函数支持注入以下三种服务类型: - IConfiguration - IWebHostEnvironment - IHostEnvironment ```csharp public Startup( IConfiguration configuration, IHostEnvironment hostEnvironment, IWebHostEnvironment webHostEnvironment) { Configuration = configuration; HostEnvironment = hostEnvironment; WebHostEnvironment = webHostEnvironment; } public IConfiguration Configuration { get; } public IHostEnvironment HostEnvironment { get; set; } public IWebHostEnvironment WebHostEnvironment { get; set; } ``` 这里你会发现 HostEnvironment 和 WebHostEnvironment 的实例是同一个。别着急,后续文章我们聊到Host的时候,你就明白了。 #### ConfigureServices - 该方法是可选的 - 该方法用于添加服务到DI容器中 - 该方法在Configure方法之前被调用 - 该方法要么无参数,要么只能有一个参数且类型必须为IServiceCollection - 该方法内的代码大多是形如Add{Service}的扩展方法 常用的服务有(部分服务框架已默认注册): - AddControllers:注册Controller相关服务,内部调用了AddMvcCore、AddApiExplorer、AddAuthorization、AddCors、AddDataAnnotations、AddFormatterMappings等多个扩展方法 - AddOptions:注册Options相关服务,如IOptions<>、IOptionsSnapshot<>、IOptionsMonitor<>、IOptionsFactory<>、IOptionsMonitorCache<>等。很多服务都需要Options,所以很多服务注册的扩展方法会在内部调用AddOptions - AddRouting:注册路由相关服务,如IInlineConstraintResolver、LinkGenerator、`IConfigureOptions
`、RoutePatternTransformer等 - AddAddLogging:注册Logging相关服务,如ILoggerFactory、`ILogger<>`、`IConfigureOptions
>`等 - AddAuthentication:注册身份认证相关服务,以方便后续注册JwtBearer、Cookie等服务 - AddAuthorization:注册用户授权相关服务 - AddMvc:注册Mvc相关服务,比如Controllers、Views、RazorPages等 - AddHealthChecks:注册健康检查相关服务,如HealthCheckService、IHostedService等 #### Configure - 该方法是必须的 - 该方法用于配置HTTP请求管道,通过向管道添加中间件,应用不同的响应方式。 - 该方法在ConfigureServices方法之后被调用 - 该方法中的参数可以接受任何已注入到DI容器中的服务 - 该方法内的代码大多是形如Use{Middleware}的扩展方法 - 该方法内中间件的注册顺序与代码的书写顺序是一致的,先注册的先执行,后注册的后执行 常用的中间件有 UseDeveloperExceptionPage:当发生异常时,展示开发人员异常信息页。如图 ![图片alt](/uploads/images/20220829/214044-75743a92cb9d4dc5ba480835ecd56324.png '代码片段:Www.CodeSnippet.Cn') - UseRouting:路由中间件,根据Url中的路径导航到对应的Endpoint。必须与UseEndpoints搭配使用。 - UseEndpoints:执行路由所选择的Endpoint对应的委托。 - UseAuthentication:身份认证中间件,用于对请求用户的身份进行认证。比如,早晨上班打卡时,管理员认出你是公司员工,那么才允许你进入公司。 - UseAuthorization:用户授权中间件,用于对请求用户进行授权。比如,虽然你是公司员工,但是你是一名.NET开发工程师,那么你只允许坐在.NET开发工程师区域的工位上,而不能坐在老总的办公室里。 - UseMvc:Mvc中间件。 - UseHealthChecks:健康检查中间件。 - UseMiddleware:用来添加匿名中间件的,通过该方法,可以方便的添加自定义中间件。 ### 省略Startup类 另外,Startup类也可以省略,直接进行如下配置即可(虽然可以这样做,但是不推荐): ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // ConfigureServices 可以调用多次,最终会将结果聚合 webBuilder.ConfigureServices(services => { }) // Configure 如果调用多次,则只有最后一次生效 .Configure(app => { var env = app.ApplicationServices.GetRequiredService
(); }); }); ``` ### IStartupFilter ```csharp public interface IStartupFilter { Action
Configure(Action
next); } ``` 有时,我们想要将一系列相关中间件的注册封装到一起,那么我们只需要通过实现IStartupFilter,并在Startup.ConfigureServices中配置IStartupFilter的依赖注入即可。 - **在IStartupFilter中配置的中间件,总是比Startup类中Configure方法中的中间件先注册;对于多个IStartupFilter实现,执行顺序与服务注册时的顺序一致** 我们可以通过一个例子来验证一下中间件的注册顺序。 首先是三个IStartupFilter的实现类: ```csharp public class FirstStartupFilter : IStartupFilter { public Action
Configure(Action
next) => app => { app.Use((context, next) => { Console.WriteLine("First"); return next(); }); next(app); }; } public class SecondStartupFilter : IStartupFilter { public Action
Configure(Action
next) => app => { app.Use((context, next) => { Console.WriteLine("Second"); return next(); }); next(app); }; } public class ThirdStartupFilter : IStartupFilter { public Action
Configure(Action
next) => app => { app.Use((context, next) => { Console.WriteLine("Third"); return next(); }); next(app); }; } ``` 接下来进行注册: ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices(services => { // 第一个被注册 services.AddTransient
(); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup
(); }) .ConfigureServices(services => { // 第三个被注册 services.AddTransient
(); }); public class Startup { public void ConfigureServices(IServiceCollection services) { // 第二个被注册 services.AddTransient
(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 第四个被注册 app.Use((context, next) => { Console.WriteLine("Forth"); return next(); }); } } ``` 最后通过输出可以看到,执行顺序的确是这样子的。 ```csharp First Second Third Forth ``` ### IHostingStartup 与IStartupFilter不同的是,IHostingStartup可以在启动时通过**外部程序集**向应用增加更多功能。不过这要求必须调用ConfigureWebHost、ConfigureWebHostDefaults等类似用来配置Web主机的扩展方法 > 我们经常使用的Nuget包SkyApm.Agent.AspNetCore就使用了该特性。 下面我们就来看一下该如何使用它。 #### HostingStartup 程序集 要创建HostingStartup程序集,可以通过创建类库项目或无入口点的控制台应用来实现。 接下来咱们还是看一下上面提到过的SkyApm.Agent.AspNetCore: ```csharp using SkyApm.Agent.AspNetCore; [assembly: HostingStartup(typeof(SkyApmHostingStartup))] namespace SkyApm.Agent.AspNetCore { internal class SkyApmHostingStartup : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting())); } } } ``` 该HostingStartup类: - 实现了IHostingStartup接口 - Configure方法中使用IWebHostBuilder来添加增强功能 - 配置了HostingStartup特性 #### HostingStartup 特性 HostingStartup特性用于标识哪个类是HostingStartup类,HostingStartup类需要实现IHostingStartup接口。 当程序启动时,会自动扫描入口程序集和配置的待激活的的程序集列表(参见下方:激活HostingStarup程序集),来找到所有的HostingStartup特性,并通过反射的方式创建Startup并调用Configure方法。 以SkyApm.Agent.AspNetCore为例 ```csharp using SkyApm.Agent.AspNetCore; [assembly: HostingStartup(typeof(SkyApmHostingStartup))] namespace SkyApm.Agent.AspNetCore { internal class SkyApmHostingStartup : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting())); } } } ``` #### 激活HostingStarup程序集 要激活HostingStarup程序集,我们有两种配置方式: ##### 1.使用环境变量(推荐) 使用环境变量,无需侵入程序代码,所以我更推荐大家使用这种方式。 配置环境变量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES,多个程序集使用分号(;)进行分隔,用于添加要激活的程序集。变量WebHostDefaults.HostingStartupAssembliesKey就是指代这个环境变量的Key。 另外,还有一个环境变量,叫做ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES,多个程序集使用分号(;)进行分隔,用于排除要激活的程序集。变量WebHostDefaults.HostingStartupExcludeAssembliesKey就是指代这个环境变量的Key。 我们在 launchSettings.json 中添加两个程序集: ```csharp "environmentVariables": { "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore;HostingStartupLibrary" } ``` ##### 2.在程序中配置 ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseSetting( WebHostDefaults.HostingStartupAssembliesKey, "SkyAPM.Agent.AspNetCore;HostingStartupLibrary") .UseStartup
(); }); ``` 这样就配置完成了,很🐮🍺的一个功能点吧! **需要注意的是,无论使用哪种配置方式,当存在多个HostingStartup程序集时,将按配置这些程序集时的书写顺序执行 Configure方法。** ### 多环境配置 一款软件,一般要经过需求分析、设计编码,单元测试、集成测试以及系统测试等一系列测试流程,验收,最终上线。那么,就至少需要4套环境来保证系统运行: - Development:开发环境,用于开发人员在本地对应用进行调试运行 - Test:测试环境,用于测试人员对应用进行测试 - Staging:预发布环境,用于在正式上线之前,对应用进行集成、测试和预览,或用于验收 - Production:生产环境,应用的正式线上环境 #### 环境配置方式 通过环境变量ASPNETCORE_ENVIRONMENT指定运行环境 **注意:如果未指定环境,默认情况下,为 Production** 在项目的Properties文件夹里面,有一个“launchSettings.json”文件,该文件是用于配置VS中项目启动的。 接下来我们就在launchSettings.json中配置一下。 先解释一下该文件中出现的几个参数: - commandName:指定要启动的Web服务器,有三个可选值: - Project:启动 Kestrel - IISExpress:启动IIS Express - IIS:不启用任何Web服务器,使用IIS - dotnetRunMessages:bool字符串,指示当使用 dotnet run 命令时,终端能够及时响应并输出消息,具体参考[stackoverflow](https://stackoverflow.com/questions/65923063/purpose-of-dotnetrunmessages-in-launchsettings-json "stackoverflow")和[github issue](https://github.com/dotnet/sdk/issues/12227 "github issue") - launchBrowser:bool值,指示当程序启动后,是否打开浏览器 - launchUrl:默认启动路径 - applicationUrl:应用程序Url列表,多个URL之间使用分号(;)进行分隔。当launchBrowser为true时,将{applicationUrl}/{launchUrl}作为浏览器默认访问的Url - environmentVariables:环境变量集合,在该集合内配置环境变量 ```csharp { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { // 如果不指定profile,则默认选择第一个 // Development "ASP.NET.WebAPI": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "weatherforecast", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, // Test "ASP.NET.WebAPI.Test": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "weatherforecast", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Test" } }, // Staging "ASP.NET.WebAPI.Staging": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "weatherforecast", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Staging" } }, // Production "ASP.NET.WebAPI.Production": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "weatherforecast", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Production" } }, // 用于测试在未指定环境时,默认是否为Production "ASP.NET.WebAPI.Default": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "weatherforecast", "applicationUrl": "http://localhost:5000" } } } ``` 配置完成后,就可以在VS上方工具栏中的项目启动处选择启动项了 #### 基于环境的 Startup Startup类支持针对不同环境进行个性化配置,有三种方式: - 将IWebHostEnvironment注入 Startup 类 - Startup 方法约定 - Startup 类约定 ##### 1.将IWebHostEnvironment注入 Startup 类 通过将IWebHostEnvironment注入 Startup 类,然后在方法中使用条件判断书写不同环境下的代码。该方式适用于多环境下,代码差异较少的情况。 ```csharp public class Startup { public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { Configuration = configuration; WebHostEnvironment = webHostEnvironment; } public IConfiguration Configuration { get; } public IWebHostEnvironment WebHostEnvironment { get; } public void ConfigureServices(IServiceCollection services) { if (WebHostEnvironment.IsDevelopment()) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsTest()) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsStaging()) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsProduction()) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } } public void Configure(IApplicationBuilder app) { if (WebHostEnvironment.IsDevelopment()) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsTest()) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsStaging()) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } else if (WebHostEnvironment.IsProduction()) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } } } public static class AppHostEnvironmentEnvExtensions { public static bool IsTest(this IHostEnvironment hostEnvironment) { if (hostEnvironment == null) { throw new ArgumentNullException(nameof(hostEnvironment)); } return hostEnvironment.IsEnvironment(AppEnvironments.Test); } } public static class AppEnvironments { public static readonly string Test = nameof(Test); } ``` ##### 2.Startup 方法约定 上面的方式把不同环境的代码放在了同一个方法中,看起来比较混乱也不容易区分。因此我们希望ConfigureServices和Configure能够根据不同的环境进行代码拆分。 我们可以通过方法命名约定来解决,约定Configure{EnvironmentName}Services和Configure{EnvironmentName}Services来装载不同环境的代码。如果当前环境没有对应的方法,则使用原来的ConfigureServices和Configure方法。 我就只拿 Development 和 Production 举例了 ```csharp public class Startup { // 我这里注入 IWebHostEnvironment,仅仅是为了打印出来当前环境信息 public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { Configuration = configuration; WebHostEnvironment = webHostEnvironment; } public IConfiguration Configuration { get; } public IWebHostEnvironment WebHostEnvironment { get; } #region ConfigureServices private void StartupConfigureServices(IServiceCollection services) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } public void ConfigureDevelopmentServices(IServiceCollection services) { StartupConfigureServices(services); } public void ConfigureProductionServices(IServiceCollection services) { StartupConfigureServices(services); } public void ConfigureServices(IServiceCollection services) { StartupConfigureServices(services); } #endregion #region Configure private void StartupConfigure(IApplicationBuilder app) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } public void ConfigureDevelopment(IApplicationBuilder app) { StartupConfigure(app); } public void ConfigureProduction(IApplicationBuilder app) { StartupConfigure(app); } public void Configure(IApplicationBuilder app) { StartupConfigure(app); } #endregion } ``` ##### 3.Startup 类约定 该方式适用于多环境下,代码差异较大的情况。 程序启动时,会优先寻找当前环境命名符合Startup{EnvironmentName}的 Startup 类,如果找不到,则使用名称为Startup的类 首先,CreateHostBuilder方法需要做一处修改 ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { //webBuilder.UseStartup
(); webBuilder.UseStartup(typeof(Startup).GetTypeInfo().Assembly.FullName); }); ``` 接下来,就是为各个环境定义 Startup 类了(我就只拿 Development 和 Production 举例了) ```csharp public class StartupDevelopment { // 我这里注入 IWebHostEnvironment,仅仅是为了打印出来当前环境信息 public StartupDevelopment(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { Configuration = configuration; WebHostEnvironment = webHostEnvironment; } public IConfiguration Configuration { get; } public IWebHostEnvironment WebHostEnvironment { get; } public void ConfigureServices(IServiceCollection services) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } public void Configure(IApplicationBuilder app) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } } public class StartupProduction { public StartupProduction(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) { Configuration = configuration; WebHostEnvironment = webHostEnvironment; } public IConfiguration Configuration { get; } public IWebHostEnvironment WebHostEnvironment { get; } public void ConfigureServices(IServiceCollection services) { Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}"); } public void Configure(IApplicationBuilder app) { Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}"); } } ```
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024