ASP.NET Core 启动和配置

  • Program类,Main方法
  • Startup类

其中很核心的思想是(Dependency Injection,依赖注入);

还有IoC容器(Inversion of Control,控制反转);

  • 注册(services注册);
  • 请求实例;
  • 实例的生命周期;

其中,生命周期 ​ 1)Transient ​ 注册的服务在每次被请求的时候都会生成一个新的实例; ​ 2) Scoped ​ .netcore 中有web请求,一个Web请求产生一个实例; ​ 3) Singleton ​ 服务的实例一旦被创建,以后的服务都只用这一个实例(一直存在),直到应用程序结束实例才被销毁;

依赖注入的好处是,①解耦,没有强依赖;(利于单元测试)②不用去管生命周期(实例的生命周期是由IOC容器来控制的);③类型之间没有依赖;

Program 类:Web程序的入口

Program 类的本质就是一个控制台应用,可以说做是.netcore web程序的入口。

其中的 Main 方法是 Web 应用的入口方法。

我们可以在 Main方法中启动 Web 服务。

Program 类的源码

首先,Program 类中的 Main方法调用CreateWebHostBuilder(args).Build().Run() 方法。

该方法的最终目的和意义就是使用 CreateWebHostBuilder 方法返回的 IWebHostBuilder 接口类型对象来构建(Build)一个 WebServe 然后运行(Run)这个 WebServe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Program
{
  public static void Main(string[] args)
  {
    CreateWebHostBuilder(args).Build().Run();
  }
  public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>();
}

CreateWebHostBuilder 方法返回的对象是静态类WebHost 的静态方法 CreateDefaultBuilder 方法返回的。

CreateWebHostBuilder 方法中我们可以看到一个 lambda 表达式,他返回了 WebHost.CreateDefaultBuilder(args).UseStartup();

CreateDefaultBuilder方法的源码

反编译 WebHost.CreateDefaultBuilder 方法的源码,我们先来看一下完整的代码实现。

我们发现 CreateDefaultBuilder 方法首先 new 了一个 WebHostBuilder 类型的对象 var builder = new WebHostBuilder(); 。

WebHostBuilder 类就实现了我们所需要返回的的 IWebHostBuilder 接口类型。

 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
36
37
38
39
40
41
42
43
44
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = new WebHostBuilder();
    if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
    {
        builder.UseContentRoot(Directory.GetCurrentDirectory());
    }
    if (args != null)
    {
        builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
    }
    builder.ConfigureAppConfiguration((hostingContext, config) =>
    {
        var env = hostingContext.HostingEnvironment;
        config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
        if (env.IsDevelopment())
        {
            var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
            if (appAssembly != null)
            {
                config.AddUserSecrets(appAssembly, optional: true);
            }
        }
        config.AddEnvironmentVariables();
        if (args != null)
        {
            config.AddCommandLine(args);
        }
    })
    .ConfigureLogging((hostingContext, logging) =>
    {
        logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
        logging.AddConsole();
        logging.AddDebug();
        logging.AddEventSourceLogger();
    }).
    UseDefaultServiceProvider((context, options) =>
    {
        options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
    });
    ConfigureWebDefaults(builder);
    return builder;
}

ConfigureWebDefaults 方法的源码

在方法的末尾,调用了 ConfigureWebDefaults(builder); 方法。

该方法详细地配置了默认的 builder 的各种设置,我们也来看一下完整的源码实现。

 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
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
    builder.UseKestrel((builderContext, options) =>
    {
        options.Configure(builderContext.Configuration.GetSection("Kestrel"));
    })
    .ConfigureServices((hostingContext, services) =>
    {
        // Fallback
        services.PostConfigure<HostFilteringOptions>(options =>
        {
            if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
            {
                // "AllowedHosts": "localhost;127.0.0.1;[::1]"
                var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                // Fall back to "*" to disable.
                options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
            }
        });
        // Change notification
        services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
                    new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));

        services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
    })
    .UseIIS()
    .UseIISIntegration();
}

builder 的默认配置

1.使用 Kestrel Web Serve

如上代码所示,我们可以看到 builder 首先调用了 UseKestrel 方法,配置了 Kestrel 服务器。

2.IIS 集成

然后 builder 使用了 UseIIS().UseIISIntegration(); 这两个方法配置了 IIS 服务器。

UseIISIntegration 方法和前者的区别就是它允许 IIS 通过 Windows 的验证来到 Kestrel 服务器,这有利于构建内网的 Web 应用。

而在项目的 csproj 文件中我们可以看到声明了 InProcess

InProcess 模式下 IIS 会和 Web 程序运行在一个系统内。而与之相对的是 OutOfProcess 模型。

OutOfProcess 模式会把请求转发给 Kestrel 服务器。相比 OutOfProcess 这种分开的模式,InProcess 模式提高了性能。

3.Logger

回到 CreateDefaultBuilder 方法,这段代码配置了 ConfigureLogging,将 logger 输出到 ConsoleDebugEventSourceLogger。

4.IConfiguration 接口

我们可以通过实现 IConfiguration 接口的类型对象里获取一些需要的配置信息。

前面提到 CreateDefaultBuilder 方法首先 new 了一个 WebHostBuilder 类型的对象。 var builder = new WebHostBuilder();

WebHostBuilder 类型就持有一个 IConfiguration 接口类型的成员字段 _config

IConfiguration 接口类型也很简单,其中的 string this[string key]{get; set;} 就是以 key-value 的形式,通过string来获取配置信息,封装的其他方法就在此省略了。

配置信息首先从json文件中获取,我们的项目中存在着 appsettings.jsonappsettings.Development.json 两个默认的 json 文件。

打开 appsettings.json 文件,我们可以看到默认生成的配置如下所示。

1
2
3
4
5
6
7
8
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHost": "*"
}

获取配置信息的方法和顺序

回到 CreateDefaultBuilder 方法,我们可以找到这一句代码。

1
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

其中,config 是 builder.ConfigureAppConfiguration() 调用的 lambda 表达式参数 (hostingContext, config) =>{} ,其设置了配置文件的具体名称。

注意,config 调用 AddJsonFile 方法,调用了两次,第二个 json 文件是系统环境变量为名的json文件,默认是 Devolopment,也就是前面提到的 appsettings.Development.json

如果两个文件有相同的属性,后者就会覆盖掉前者的设置。但是 CreateDefaultBuilder 方法的代码中还有一句设置 config.AddEnvironmentVariables();

这一句代码是设置系统环境变量,如果有和以上配置文件相同的属性,也会覆盖的前者的设置。

在系统环境变量之后又有一句 config.AddCommandLine(args); ,命令行中如果有和以上配置文件相同的属性,也会覆盖的前者的设置。

所以最终的属性的覆盖顺序为 appsettings.json -> appsettings.{env.EnvironmentName}.json -> AddEnvironmentVariables -> AddCommandLine

UseStartup<> 方法

回到 Program 类的 CreateWebHostBuilder 方法,我们可以发现 WebHost.CreateDefaultBuilder(args).UseStartup(); 。

其调用了 UseStartup<> 方法。 UseStartup() 指定 StartupStartup 类,以该类来配置整个 Web 应用,如注册服务、使用中间件等等。

然后CreateWebHostBuilder 方法返回的 builder 回到 Main 方法,Build 后再调用 Run 方法,我们的 Web 应用就可以监听 Http/Https 的请求了。

Progarm 这个类主要是为了配置我们的 Web 应用的基础设施,如 Http 服务器以及如何集成到 IIS 等。