Skip to content

.NET Core 实战 [No.326~329] Web 主机配置

🏷️ 《.NET Core 实战》

默认配置

ASP.NET Core Web 应用程序 项目创建时,已经提供了一些默认配置。

随着版本的升级,创建 WebHost 的方式也一直在更新。 .NET Core 1.1 时默认的写法如下:

csharp
public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

    host.Run();
}

.NET Core 2.1 中则改成了使用 WebHost 静态类封装了创建 WebHostBuilder 的过程。

csharp
public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build().Run();
}

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

WebHost.CreateDefaultBuilder 方法使用如下的默认配置来创建 Web 主机(摘自其方法的备注):

The following defaults are applied to the returned Microsoft.AspNetCore.Hosting.WebHostBuilder:

  • use Kestrel as the web server and configure it using the application's configuration providers,
    使用内置的 Kestrel 服务器组件
  • set the Microsoft.AspNetCore.Hosting.IHostingEnvironment.ContentRootPath to the result of System.IO.Directory.GetCurrentDirectory,
    将应用程序的当前目录作为 Web 内容的根目录
  • load Microsoft.Extensions.Configuration.IConfiguration from 'appsettings.json' and 'appsettings.[Microsoft.AspNetCore.Hosting.IHostingEnvironment.EnvironmentName].json',
    appsettings.jsonappsettings.{启动环境}.json 文件加载配置
  • load Microsoft.Extensions.Configuration.IConfiguration from User Secrets when Microsoft.AspNetCore.Hosting.IHostingEnvironment.EnvironmentName is 'Development' using the entry assembly,
    当启动环境为 Development 时从 User Secrets用户机密)加载配置
  • load Microsoft.Extensions.Configuration.IConfiguration from environment variables,
    从环境变量加载配置
  • load Microsoft.Extensions.Configuration.IConfiguration from supplied command line args,
    从命令行参数加载配置
  • configures the Microsoft.Extensions.Logging.ILoggerFactory to log to the console and debug output,
    配置日志输出到命令行窗口和调试窗口
  • enables IIS integration,
    启用 IIS 交互(IIS 将作为反向代理端,将 HTTP 请求转发到 Web 应用程序)
  • and enables the ability for frameworks to bind their options to their default configuration sections.

.NET Core 3.1 中则改成使用 Host 静态类来实现,其默认代码如下:

csharp
public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Host.CreateDefaultBuilder 方法使用如下的默认配置来创建 Web 主机(摘自其方法备注):

The following defaults are applied to the returned Microsoft.Extensions.Hosting.HostBuilder:

  • set the Microsoft.Extensions.Hosting.IHostEnvironment.ContentRootPath to the result of System.IO.Directory.GetCurrentDirectory
  • load host Microsoft.Extensions.Configuration.IConfiguration from "DOTNET_" prefixed environment variables
    DOTNET_ 前缀的环境变量加载主机配置
  • load host Microsoft.Extensions.Configuration.IConfiguration from supplied command line args
  • load app Microsoft.Extensions.Configuration.IConfiguration from 'appsettings.json' and 'appsettings.[Microsoft.Extensions.Hosting.IHostEnvironment.EnvironmentName].json'
  • load app Microsoft.Extensions.Configuration.IConfiguration from User Secrets when Microsoft.Extensions.Hosting.IHostEnvironment.EnvironmentName is 'Development' using the entry assembly
  • load app Microsoft.Extensions.Configuration.IConfiguration from environment variables
  • load app Microsoft.Extensions.Configuration.IConfiguration from supplied command line args
  • configure the Microsoft.Extensions.Logging.ILoggerFactory to log to the console, debug, and event source output
  • enables scope validation on the dependency injection container when Microsoft.Extensions.Hosting.IHostEnvironment.EnvironmentName is 'Development'
    当启动环境为 Development 时在依赖注入容器上启用 作用域验证

相比 .NET Core 2.1 多了两项:

  • DOTNET_ 前缀的环境变量加载配置

    ASP.NET Core Web 应用程序默认的环境变量是 ASPNETCORE_ 前缀的,这两个前缀倒是不知道有什么区别。

  • 当启动环境为 Development 时在依赖注入容器上启用 作用域验证

    若在程序的启动过程中(如 StartUp.Configure)解析 Scoped 生命周期的服务,运行时就会报 InvalidOperationException 异常。

    System.InvalidOperationException:“Cannot resolve scoped service 'WebApplication1.ConcreteA' from root provider.”

    之所以会引发上面的异常,原因在于使用 Scoped 生命周期时,正常的作用域是一次 HTTP 请求,请求结束时会释放掉实例。而在 启动过程 的 root provider 中解析服务时,其生命周期一般相当于整个 Application 的生命周期,这会造成资源的浪费,特别是当使用数据库链接等资源的时候。

    一种解决方法是在启动时禁用 作用域验证

    csharp
    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseDefaultServiceProvider(options => options.ValidateScopes = false)
            .UseStartup<Startup>();

    上面的方法并不好,只是不做验证,并没有解决资源占用问题。最好是手动指定一个 Scope ,在指定的 Scope 结束时释放资源(摘自 StackOverflow)。

    csharp
    // Create a new IServiceScope that can be used to resolve scoped services.
    using (var scope = app.ApplicationServices.CreateScope())
    {
        // resolve the services within this scope
        ConcreteA A = scope.ServiceProvider.GetRequiredService<ConcreteA>();
    
        //ConcreteA instance and injected ConcreteB are used in the same scope
    
        //do something
        A.Run();
    }
    
    //both will be properly disposed of here when they both got out of scope.

配置 URL

指定 URL 的方法有很多种,比较常用的有以下三种:

  1. 调用 UseUrls 方法

    UseUrls 方法支持多个参数。

    csharp
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseUrls("http://localhost:6500", "http://localhost:7000", "http://*:7500")
        .Build()
        .Run();

    APS.NET Core 3.1 中配置方法如下:

    csharp
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseDefaultServiceProvider(options => options.ValidateScopes = false)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                webBuilder.UseUrls("http://localhost:6500", "http://localhost:7000", "http://*:7500");
            });
  2. 调用 UseSetting 方法

    监听多个端口时,使用半角分号 ; 分隔。

    csharp
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseSetting(WebHostDefaults.ServerUrlsKey, "http://localhost:8990;http://localhost:46133")
        .Build()
        .Run();

    APS.NET Core 3.1 中配置方法如下:

    csharp
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseDefaultServiceProvider(options => options.ValidateScopes = false)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                webBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, "http://localhost:8990;http://localhost:46133");
            });
  3. 通过配置文件

    ASP.NET Core 2.1 中首先要添加 json 文件,假设名为 host.json

    json
    {
        "urls": "http://localhost:3600;http://*:8080"
    }

    然后修改 Main 方法:

    csharp
    var builder = WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();
    var config = new ConfigurationBuilder().SetBasePath(builder.GetSetting(WebHostDefaults.ContentRootKey))
        .AddJsonFile("host.json");
    builder.UseConfiguration(config.Build());
    builder.Build().Run();

    这里我有个感觉比较疑惑的地方,上面的 urls 如果放在 appsettings.json 文件中并没有默认就被加载。按照上面一节中对 WebHost.CreateDefaultBuilde 方法的描述,appsettings.json 应该是会被默认加载的才对。 StackOverflow 上也有一个类似的问题,可惜回答中只有一个通过配置 Kestrel 来设置 URL 的方式,而且貌似只支持设置一个端口。

    json
    "Kestrel": {
        "EndPoints": {
            "Http": {
                "Url": "http://localhost:5000"
            }
        }
    }

    对应的通过代码设置 Kestrel 的方式如下(摘自 StackOverflow):

    csharp
    WebHost.CreateDefaultBuilder(args)
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Loopback, 5000);  // http:localhost:5000
            options.Listen(IPAddress.Any, 80);         // http:*:80
            options.Listen(IPAddress.Loopback, 443, listenOptions =>
            {
                listenOptions.UseHttps("certificate.pfx", "password");
            });
        })
        .UseStartup<Startup>()
        .Build();

    APS.NET Core 3.1 中则不需要新增 json 文件,也不需要修改 main 方法,直接在 appsettings.json 文件中添加一个 Urls 就行了。

    json
    {
        "Urls": "http://localhost:3600;http://*:8099",
        "Logging": {
            "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
            }
        },
        "AllowedHosts": "*"
    }

再补充一个书中没有提到的方法:通过命令行参数。

bash
dotnet run --urls "http://*:8090;http://localhost:9090"

另外还可以通过环境变量(ASPNETCORE_URLS)配置 URL。

Kestrel

KestrelASP.NET Core 框架的默认 Web 服务器组件。WebHost.CreateDefaultBuilder 方法的默认配置中已经默认使用了 Kestrel,但若是自己编写代码来创建 Web 服务器,则可以通过 UseKestrel 扩展方法启用 Kestrel 。另外可以通过 UseIISIntegration 方法启用 IIS 集成功能,只有在使用 IIS 作为反向代理时才有效。

csharp
new WebHostBuilder()
    .UseStartup<Startup>()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseKestrel()
    .UseIISIntegration()
    .Build()
    .Run();

配置调试方案

ASP.NET Core Web 项目自带两个调试方案:

  • 以 IIS Express 作为反向代理运行应用程序
  • 用项目名称命名,独立运行项目(通过 dotnet 命令执行)

可以在 项目属性 → 调试 页面新增/修改/删除调试方案。配置完成后可以在 Properties\launchSettings.json 文件看到保存的配置内容。大致结构如下:

json
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:65132",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApplication2": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000"
    }
  }
}

commandName 一共有 4 个可选值

  • IIS Express:以 IIS Express 作为反向代理进程
  • IIS:以 IIS 服务(完整版 IIS)作为反向代理进程
  • Project:使用 dotnet 命令直接运行应用程序
  • Executable:自定义一个可执行文件,这种调试方案一般不常用

environmentVariables 是环境变量的键值对,名称以 ASPNETCORE_ 为前缀,后跟环境变量名。
Environment 环境变量有 3 个预定义的值:

  • Development:开发环境
  • Staging:预览环境
  • Production:生产环境

参考:《.NET Core 实战:手把手教你掌握 380 个精彩案例》 -- 周家安 著