.NET WebAPI 启动顺序

TL;DR

本文为面试遇到启动顺序问题,先上结论,感兴趣可以往下看(会简单聊下源码,对比下 .NET Core 各个版本的区别)

  1. ConfigureWebHostDefaults
  2. ConfigureHostConfiguration
  3. ConfigureAppConfiguration
    • 以下根据语句的先后顺序执行
      • Startup.ConfigureServices
      • ConfigureLogging
      • ConfigureServices`
  4. Startup.Configure

结果是很容易得出,dotnet new webapi, 然后对应位置console下就好了,但是为什么如此呢?
如果根据结果去推原因,并不是一个好的方法,你会用各种不知对错的想法去对应结果(不只是代码,人生也是如果 就如各种畅销书/垃圾水文,根据人的成功,去证明人的各种小习惯的重要性)。所以本文试图通过两个方式探究下原因。

Program, Startup 变化

.NET Core 1.0

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();

host.Run();
}

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseMvc();
}

.NET Core 1.1

Program.cs

1.0 一致

Startup.cs

1.0 一致

.NET Core 2.0

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}

嗯,2.0的时候已经清爽了不少,把那些默认的中间件的使用都放在了 CreateDefaultBuilder 中,我们来看下这个方法做了哪些事情(代码在下边),可以看到UseKestrel,UseIIS都是在这个方法中实现的。并且把 .NET Core 1 中Startup做的config部分工作放在了这里,还有log部分,也是在此实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
return new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).ConfigureAppConfiguration(delegate (WebHostBuilderContext hostingContext, IConfigurationBuilder config)
{
IHostingEnvironment hostingEnvironment = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).AddJsonFile($"appsettings.{hostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (hostingEnvironment.IsDevelopment())
{
Assembly assembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName));
if (assembly != null)
{
config.AddUserSecrets(assembly, optional: true);
}
}

config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging(delegate (WebHostBuilderContext hostingContext, ILoggingBuilder logging)
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider(delegate (WebHostBuilderContext context, ServiceProviderOptions options)
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
}

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseMvc();
}
}

因为在main函数中就把config配置好了,所以Startup的ctor参数也从 IHostingEnvironment -> IConfiguration, 从这之后基本就没有太大的变动了

BTW 常看报文的肯定经常看到这个 Server: Kestrel , 有心的人估计也查过这个,知道这个是与 Nginx,IIS,Apache一样的,用来负载你的web程序,但是有一大部分人都说自己在程序中并没用用过这个,部署的时候也一直是 nignx,IIS, 实际上是微软已经默认使用了。

.NET Core 2.1

Program.cs

1
2
3
4
5
6
7
8
9
10
11
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}

换了下函数的返回值 IWebHost -> IWebHostBuilder

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseMvc();
}
}

SetCompatibilityVersion 看名字也知道是为了兼容性,不用管
多了一个HSTS 的中间件(为了强制使用HTTPS),估计是 RFC 有啥新出条例,所以项目默认支持
UseHttpsRedirection 一样是为了安全

这一版基本没什么变化

.NET Core 2.2

Program.cs

无变化

Startup.cs

基本无变化

.NET Core 3.1

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>();
});
}

IWebHostBuilder -> IHostBuilder 这样可以更好的将 ASP.NET Core 应用与非 Web 特定的其他服务器方案集成 ,通过 Hosting Exception, create default build, config web host default 启用 API 。

这一部分更多是,微软对于 .NET Core 代码类库的变化,整体的封装都做了很大的调整(v2 的时候出了Microsoft.AspNetCore.All,基本引入的所有的必需包,当然会导致用不到的包也引入了,项目加载必然会慢一点,V3的时候做了调整 )

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

这次是将 AddMvc -> AddControllers, 即默认的注入变为Controll,不再包括视图(view,page),也是因为这个变化,可以看到下边多了一些中间件的注入(route,authorization,endpoint)。

为什么这个时候多出了这些呢? 如果看下之前代码,会发现的UseMvc()中route,authorization,endpoint这些东西实际上在MVC中都有实现。

可以说这次的更新,微软更多的是把默认配置变得更轻巧,灵活一点。(当然之前的useMvc也是可以正常用的,注意 微软只是改的默认写法,而不是规定你必须这样做

.NET Core 5

Program.cs

无变化

Startup.cs

无变化

PS. 从这一代,webapi项目默认集成了OpenAPI(Swagger)

.NET Core 6

Program.cs

无变化

Startup.cs

无变化

瞧瞧源码

先把相关的方法都加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Program
{
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
Console.WriteLine("ConfigureAppConfiguration");
})
.ConfigureWebHostDefaults(webBuilder =>
{
Console.WriteLine(" ConfigureWebHostDefaults");
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(log =>
{
Console.WriteLine("ConfigureLogging");
})
.ConfigureServices(service =>
{
Console.WriteLine("ConfigureServices");
})
.ConfigureHostConfiguration(config =>
{
Console.WriteLine("ConfigureHostConfiguration");
});
}
ConfigureWebHostDefaults
ConfigureHostConfiguration
ConfigureAppConfiguration
Startup
Startup.ConfigureServices
ConfigureLogging
ConfigureServices
service:3226198
service2:29035785
Startup.Configure