翻譯 - ASP.NET Core 基本知識 - Web 主機 (Web Host)

sims發表於2021-01-25

翻譯自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-5.0

ASP.NET Core 應用程式配置和啟動一個 Host。Host 負責應用程式的啟動和生命週期的管理。至少的,Host 配置一個伺服器和一個請求處理管道。Host 也會設定日誌,依賴注入和配置。

這篇文章覆蓋了 Web Host,任然是隻是向後相容可用。 Generic Host 推薦用於所有型別的應用程式。

配置一個 Host

使用 IWebHostBuilder 的例項建立一個 Host。一般會在應用程式的入口點 Main 方法中建立。

在工程模板中,Main 方法位於 Program.cs 中。典型的應用程式呼叫 CreateDefaultBuilder 啟動配置一個 Host:

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

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

程式碼中呼叫 CreateDefaultBuilder 的是在一個叫做 CreateWebHostBuilder 的方法中,這個方法從 Main 中分離出來,在 Main 方法中在 builder 物件上呼叫 Run 方法。這中分離是有必須的,如果你使用了 Entity Framework Core tools。這個工具期望找到一個 CreateWebHostBuilder 方法,以便能不必執行應用程式就能夠在設計的時候配置 Host。一種途徑是實現介面 IDesignTimeDbContextFactory。更多資訊,檢視 Design-time DbContext Creation

CreateDefaultBuilder 執行了以下任務:

CreateDefaultBuilder 定義的配置可以被覆蓋和使用 ConfigureAppConfigurationConfigureLogging 和其它方法及 IWebHostBuilder 的擴充套件方法擴充套件。下面是幾個例子:

  • ConfigureAppConfiguration 用來為應用程式指定額外的 IConfiguration。下面的 ConfigureAppConfiguration 呼叫新增了一個代理去包含在 appsettings.xml 檔案中的應用程式的配置。ConfigureAppConfiguration 可能會呼叫多次。注意這個配置並不應用到主機(例如,伺服器 URLs 或者 環境)。檢視 Host configuration values 部分。
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true);
        })
        ...
  • 下面的 ConfigureLogging 呼叫新增一個代理去配置最小的logging level (SetMinimumLevel) 為 LogLevel.Warning。這個設定覆蓋了appsettings.Development.json (LogLevel.Debug) 和 appsettings.Production.json (LogLevel.Error) 裡面通過 CreateDefaultBuilder 配置的設定。ConfigureLogging 可能會呼叫多次。
    WebHost.CreateDefaultBuilder(args)
        .ConfigureLogging(logging => 
        {
            logging.SetMinimumLevel(LogLevel.Warning);
        })
        ...
  • 下面的 ConfigureKestrel 的呼叫覆蓋了 Kestrel 通過 CreateDefaultBuilder 配置的預設的 Limits.MaxRequestBodySize 30,000,000 位元組:
    WebHost.CreateDefaultBuilder(args)
        .ConfigureKestrel((context, options) =>
        {
            options.Limits.MaxRequestBodySize = 20000000;
        });

    content root 決定了 Host 在哪裡搜尋內容檔案,例如 MVC 檢視檔案。當應用程式從工程根目錄啟動的時候,工程根目錄被用作內容根目錄。這在 Visual Studio 和 dotnet new templates 預設使用。

更多關於應用程式配置的資訊,檢視 Configuration in ASP.NET Core

注意

作為一種使用靜態 CreateDefaultBuilder 方法的途徑,從 WebHostBuilder 建立一個 Host 在 ASP.NET Core 2.x 中是受支援的。

當設定一個主機的時候,可以提供 Configure 和 ConfigureServices 這兩個方法。如果一個 Startup 方法被指定了,它必須定義 Configure 方法。更多資訊,檢視 App startup in ASP.NET Core 。多次呼叫 ConfigureServices 將會附加到另外一個上面。在 WebHostBuilder 上多次呼叫 Configure 或者 UseStartup 將會替換之前的設定。

Host configuration values

WebHostBuilder 依賴下面的方法設定主機配置值:

  • Host builder configuration,包含格式為 ASPNETCORE_{configurationKey} 的環境變數。例如,ASPNETCORE_ENVIRONMENT。
  • 擴充套件,例如 UseContentRoot 和 UseConfiguration (檢視 Override configuration 部分)
  • UseSetting 和相關的鍵。當使用 UseSetting 設定值得時候,值被設定為字串而忽略它的型別。

主機使用最後設定值得選項。更多資訊檢視,Override configuration

Application Key (Name)

當 UseStartup 或者 Configure 在主機構造方法中呼叫的時候,IWebHostEnvironment.ApplicationName 屬性會自動設定。值被設定為包含應用程式入口點的程式集的名稱。顯式的設定,可以使用 WebHostDefaults.ApplicationKey

Key: applicationName

Type: string

Default: 包含應用程式入口點程式集的名稱

Set using: UseSetting

Environment variable: ASPNETCORE_APPLICATIONNAME

WebHost.CreateDefaultBuilder(args)
    .UseSetting(WebHostDefaults.ApplicationKey, "CustomApplicationName")

Capture Startup Errors

設定捕獲啟動錯誤的控制

Key: captureStartupErrors

Type: bool (true or 1)

Default: 預設為 false,除非應用程式使用 Kestrel 執行在 IIS 之後,這時預設是 true

Set using: CaptureStartupErrors

Environment variable: ASPNETCORE_CAPTURESTARTUPERRORS

當設定為 false 時,啟動過程中的錯誤會導致主機退出。當設定為 true 時,主機會捕獲啟動過程中的異常,並且試圖啟動伺服器。

WebHost.CreateDefaultBuilder(args)
    .CaptureStartupErrors(true)

Content root

這個設定決定了 ASP.NET Core 開始搜尋內容檔案的位置。

Key: contentRoot

Type: string

Default: 預設是應用程式程式集所在的目錄

Set using: UseContentRoot

Environment variable: ASPNETCORE_CONTENTROOT

content root 也被用作 web root 的基本路徑。如果 content root 路徑不存在,主機就會啟動失敗。

WebHost.CreateDefaultBuilder(args)
    .UseContentRoot("c:\\<content-root>")

更多資訊,請檢視:

Detailed Errors

決定是否應該詳細錯誤資訊

Key: detailedErrors

Type: bool (treu 或者 1)

Default: false

Set using: UseSetting

Environment variable: ASPNETCORE_DETAILEDERRORS

當使能的時候(或者 Environment 被設定為 Development 的時候),應用程式會捕獲異常詳細資訊。

WebHost.CreateDefaultBuilder(args)
    .UseSetting(WebHostDefaults.DetailedErrorsKey, "true")

Environment

 設定應用程式環境

Key: environment

Type: string

Default: Production

Set using: UseEnvironment

Environment variable: ASPNETCORE_ENVIRONMENT

environmenmt 可以被設定為任意的值。框架定義的值包括 Development,Staging 和 Production。值不區分大小寫。預設的,Environment 從 ASPNETCORE_ENVIRONMENT  環境變數中讀取。當使用 Visual Studio 時,環境變數可能在 lauchSetting.json 檔案中設定。更過資訊,請檢視: Use multiple environments in ASP.NET Core

WebHost.CreateDefaultBuilder(args)
    .UseEnvironment(EnvironmentName.Development)

Hosting Startup Assemblies

設定應用程式託管啟動程式集

Key: hostingStartupAssemblies

Type: string

Default: Empty string

Set using: UseSetting

Environment variable: ASPNETCORE_HOSTINGSTARTUPASSEMBLIES

以逗號分隔的字串,啟動時載入的託管的啟動程式集

儘管配置值被設定為空字串,託管程式集總是包含應用程式程式集。當提供了託管啟動程式集,它們在應用程式啟動時建立公共服務時被新增到應用程式程式集。

WebHost.CreateDefaultBuilder(args)
    .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2")

HTTPs Port

設定 HTTPS 重定向埠。使用 enforcing HTTPS

Key: https_port

Type: string

Defalut: 預設無設定

Set using: UseSetting

Environment variable: ASPNETCORE_HTTPS_PORT

WebHost.CreateDefaultBuilder(args)
    .UseSetting("https_port", "8080")

Hosting Startup Exclude Assemblies

冒號分隔的字串,啟動時排除託管啟動程式集

Key: hostingStartupExcludeAssemblies

Type: string

Default: Empty string

Set using: UseSetting

Environment variable: ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES

WebHost.CreateDefaultBuilder(args)
    .UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "assembly1;assembly2")

Prefer Hosting URLs

表明主機是否應該在使用 WebHostBuilder 配置的 URLs 上監聽,而不是 IServer 實現配置的 URLs

Key: preferHostingUrls

Type: bool (true 或者 1)

Default: true

Set using: PreferHostingUrls

Environment variable: ASPNETCORE_PREFERHOSTINGURLS

WebHost.CreateDefaultBuilder(args)
    .PreferHostingUrls(false)

Prevent Hosting Startup

阻止自動載入託管啟動程式集,包括應用程式程式集配置的託管啟動程式集。更多資訊檢視,Use hosting startup assemblies in ASP.NET Core

Key: preventHostingStartup

Type: bool (true 或者 1)

Default: false

Set using: UseSetting

Environment variable: ASPNETCORE_PREVENTHOSTINGSTARTUP

WebHost.CreateDefaultBuilder(args)
    .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")

Server URLs

表明帶有埠和協議的 IP 地址或者主機地址是否應該被伺服器監聽請求

Key: urls

Type: string

Default: http://localhost:5000

Set using: UseUrls

Environment variable: ASPNETCORE_URLS

設定一組伺服器應該響應的冒號(;)分隔的 URL 字首。例如,http://localhost:123。使用 "*" 表明伺服器是否應該監聽任意使用特定埠和協議(例如,http://*:5000)的 IP 地址或者主機地址。協議 (http:// 或者 https://) 必須包含在每一個 URL 中。支援的格式因伺服器不同而不同。

WebHost.CreateDefaultBuilder(args)
    .UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")

Kestrel 有它自己的 endpoint 配置 API。更多資訊檢視,Configure endpoints for the ASP.NET Core Kestrel web server

Shutdown Timeout

指定等待 Web Host 關閉的超時時間

Key: shutdownTimeoutSeconds

Type: int

Default: 5

Set using: UseShutdownTimeout

Environment variable: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS

儘管使用 UseSetting 可以接受鍵為 int 的值(例如,.UseSetting(WebHostDefaults.ShutdownTimeoutKey,"10")),UseShutdownTimeout 帶有 TimeSpan 引數。

在超時時間內,主機會:

如果在所有服務停止之前超時了,任何活動的服務在應用程式關閉時都會停止。即使服務沒有完成處理也會被停止。如果服務需要更多的時間去停止,增加超時時間。

WebHost.CreateDefaultBuilder(args)
    .UseShutdownTimeout(TimeSpan.FromSeconds(10))

Startup Assembly

決定搜尋 Startup 類的程式集

Key: startupAssembly

Type: string

Default: 應用程式程式集

Set using: UseStartup

Environment variable: ASPNETCORE_STARTUPASSEMBLY

可以指定程式的名稱(string)或者型別 (TStartup)。如果多個 UseStartup 方法被呼叫,則最後一個優先順序最高:

WebHost.CreateDefaultBuilder(args)
    .UseStartup("StartupAssemblyName")
WebHost.CreateDefaultBuilder(args)
    .UseStartup<TStartup>()

Web root

設定應用程式靜態資源的相對路徑

Key: webroot

Type: string

Default: 預設是 wwwroot。路基 {contentroot}/wwwroot 必須存在。如果路徑不存在,一個 no-op 檔案提供器將被使用。

WebHost.CreateDefaultBuilder(args)
    .UseWebRoot("public")

更多資訊檢視:

覆蓋配置

使用 Configuration 配置 Web Host。在下面的例子中,host 配置在 hostsetting.json 檔案中是可選指定的。任何從 hostsetting.json 檔案中載入的配置可能會被命令列引數覆蓋。編譯的配置(in config)使用 UseConfiguration 來配置主機。IWebHostBuilder 配置被新增到應用程式配置中,但是相反的就不是,ConfigureAppConfigureation 不會影響 IWebHostBuilder 配置。

覆蓋 UseUrls 提供的配置優先使用 hostsettings.json 配置,其次是命令列引數配置:

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

    public static IWebHostBuilder CreateWebHostBuilder(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("hostsettings.json", optional: true)
            .AddCommandLine(args)
            .Build();

        return WebHost.CreateDefaultBuilder(args)
            .UseUrls("http://*:5000")
            .UseConfiguration(config)
            .Configure(app =>
            {
                app.Run(context => 
                    context.Response.WriteAsync("Hello, World!"));
            });
    }
}

hostsettings.json:

{
    urls: "http://*:5005"
}

注意

UseConfiguration 只會複製 IConfiguration 提供的鍵值到 host builder 配置。因此,設定 reloadOnChange: true 為 JSON,INI,和 XML 設定檔案沒有影響。

指定主機在一個特定的 URL 上執行,期望的值可以在執行 dotnet run 時命令列提示中傳入。命令列引數覆蓋了來自 hostsettings.json 中的 urls 值,伺服器在 8080 埠監聽:

dotnet run --urls "http://*:8080"

管理 Host

Run

Run 方法啟動 web 應用程式並阻塞呼叫執行緒直到 Host 關閉:

host.Run();

Start

通過呼叫它的 Start 方法以 non-blocking 方式執行 Host:

using (host)
{
    host.Start();
    Console.ReadLine();
}

如果一組 URLs 傳遞給 Start 方法,它就會監聽這組指定的 URLs:

var urls = new List<string>()
{
    "http://*:5000",
    "http://localhost:5001"
};

var host = new WebHostBuilder()
    .UseKestrel()
    .UseStartup<Startup>()
    .Start(urls.ToArray());

using (host)
{
    Console.ReadLine();
}

應用程式可以使用預設值的預設 CreateDefaultBuilder 使用一個靜態約定的方法初始化和啟動一個新的 Host。這些方法啟動伺服器時沒有控制檯輸出,使用 WaitForShutdown 等待終止(Ctrl-C/SIGINT  或者 SIGTERM):

Start(RequestDelegate app)

使用 RequestDelegate 啟動:

using (var host = WebHost.Start(app => app.Response.WriteAsync("Hello, World!")))
{
    Console.WriteLine("Use Ctrl-C to shutdown the host...");
    host.WaitForShutdown();
}

在瀏覽器中傳送一個請求 http://localhost:5000 接收到 "Hello World!" 響應,WaitForShutdown 阻塞了直到一個結束訊號 (Ctrl-C/SIGINT  或者  SIGTERM) 出現。應用程式顯示了 Console.WriteLine 資訊,等待按鍵退出。

Start(string url, RequestDelegate app)

使用一個 URL 和 RequestDelegate 啟動應用程式:

using (var host = WebHost.Start("http://localhost:8080", app => app.Response.WriteAsync("Hello, World!")))
{
    Console.WriteLine("Use Ctrl-C to shutdown the host...");
    host.WaitForShutdown();
}

和 Start(RequestDelegate app) 生成同樣的結果,期望應用程式在 http://localhost:8080 上響應。

Start(Action <IRouteBuilder> routerBuilder)

使用 IRouteBuilder (Microsoft.AspNetCore.Routing) 的例項使用 routing 中介軟體:

using (var host = WebHost.Start(router => router
    .MapGet("hello/{name}", (req, res, data) => 
        res.WriteAsync($"Hello, {data.Values["name"]}!"))
    .MapGet("buenosdias/{name}", (req, res, data) => 
        res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
    .MapGet("throw/{message?}", (req, res, data) => 
        throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
    .MapGet("{greeting}/{name}", (req, res, data) => 
        res.WriteAsync($"{data.Values["greeting"]}, {data.Values["name"]}!"))
    .MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
    Console.WriteLine("Use Ctrl-C to shutdown the host...");
    host.WaitForShutdown();
}

對上面的示例使用下面的瀏覽器請求:

Request Response
http://localhost:5000/hello/Martin Hello,Martin!
http://localhost:5000/buenosdias/Catrina Buenos dias,Catrina!
http://localhost:5000/throw/ooops! Throw an exception with string "ooops!"
http://localhost:5000/throw  Throw an exception with string "Uh oh!"
http://localhost:5000/Sante/Kevin Sante,Kevin! 
 http://localhost:5000 Hello World! 

 WaitForShutdown 阻塞直到結束訊號(Ctrl-C/SIGINT 或者 SIGTERM)出現。應用程式顯示 Console.WriteLine 資訊,等待按鍵按下退出。

Start(string url, Action<IRouteBuilder> routeBuilder)

使用 URL 和 IRouterBuilder 例項:

using (var host = WebHost.Start("http://localhost:8080", router => router
    .MapGet("hello/{name}", (req, res, data) => 
        res.WriteAsync($"Hello, {data.Values["name"]}!"))
    .MapGet("buenosdias/{name}", (req, res, data) => 
        res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
    .MapGet("throw/{message?}", (req, res, data) => 
        throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
    .MapGet("{greeting}/{name}", (req, res, data) => 
        res.WriteAsync($"{data.Values["greeting"]}, {data.Values["name"]}!"))
    .MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
    Console.WriteLine("Use Ctrl-C to shut down the host...");
    host.WaitForShutdown();
}

生成和 Start(Action<IRouteBuilder> routeBuilder) 相同的結果,期望應用程式在 http://localhost:8080 上響應。

StartWith(Action<IApplicationBuilder> app)

提供一個代理配置一個 IApplicationBuilder:

using (var host = WebHost.StartWith(app => 
    app.Use(next => 
    {
        return async context => 
        {
            await context.Response.WriteAsync("Hello World!");
        };
    })))
{
    Console.WriteLine("Use Ctrl-C to shut down the host...");
    host.WaitForShutdown();
}

在瀏覽器中請求 http://localhost:5000 接收到 "Hello World!" 響應,WaitForShutdown 阻塞直到一個結束訊號(Ctrl-C/SIGINT 或者 SIGTERM)發出。應用程式顯示了 Console.WriteLine 資訊,等待按鍵按下退出。

StartWith(string url, Action<IApplicationBuilder> app)

提供一個 url 和一個代理配置 IApplicationBuilder:

using (var host = WebHost.StartWith("http://localhost:8080", app => 
    app.Use(next => 
    {
        return async context => 
        {
            await context.Response.WriteAsync("Hello World!");
        };
    })))
{
    Console.WriteLine("Use Ctrl-C to shut down the host...");
    host.WaitForShutdown();
}

結果和 StartWith(Action<IApplicatonBuilder> app) 相同的結果,期望在應用程式在 http://localhost:8080 上響應。

IWebHostEnvironment interface

IWebHostEnvironment 介面提供了關於應用程式 web 託管環境的資訊。使用 constructor injection 訪問 IWebHostEnvironment 保證使用它的屬性和擴充套件方法:

public class CustomFileReader
{
    private readonly IWebHostEnvironment _env;

    public CustomFileReader(IWebHostEnvironment env)
    {
        _env = env;
    }

    public string ReadFile(string filePath)
    {
        var fileProvider = _env.WebRootFileProvider;
        // Process the file here
    }
}

一個 convention-based approach 可以基於環境在啟動時用來配置應用程式。或者,在 ConfigureServices 中將 IWebHostEnvironment 注入到 Startup 構造方法使用:

public class Startup
{
    public Startup(IWebHostEnvironment env)
    {
        HostingEnvironment = env;
    }

    public IWebHostEnvironment HostingEnvironment { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        if (HostingEnvironment.IsDevelopment())
        {
            // Development configuration
        }
        else
        {
            // Staging/Production configuration
        }

        var contentRootPath = HostingEnvironment.ContentRootPath;
    }
}

注意

除了 IsDevelopment 擴充套件方法外,IWebHostEnvironment 提供了 IsStaging,IsProduction 和 IsEnvironment(string environment) 方法。更多資訊檢視,Use multiple environments in ASP.NET Core

 IWebHostEnvironment 服務也可以直接注入到 Configure 方法用來設定處理管道:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // In Development, use the Developer Exception Page
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // In Staging/Production, route exceptions to /error
        app.UseExceptionHandler("/error");
    }

    var contentRootPath = env.ContentRootPath;
}

當建立自定義中介軟體(middleware)的時候,IWebHostEnvironment 可以被注入到 Invoke 方法:

IHostApplicationLifetime interface

IHostApplicationLifetime 允許 post-startup 和 關閉。介面的三個屬性是取消令牌,用來註冊定義啟動和關閉時間的Action 方法。 

Cancellation Token Triggered when...
ApplicationStarted 主機已經完全啟動
ApplicationStopped 主機已經完全正常關閉。所有的請求應該已經處理完畢。關閉阻塞直到這個事件完成。
ApplicationStopping 主機正在正常關閉。請求可能仍然正在處理。關閉阻塞直到這個事件完成。
public class Startup
{
    public void Configure(IApplicationBuilder app, IHostApplicationLifetime appLifetime)
    {
        appLifetime.ApplicationStarted.Register(OnStarted);
        appLifetime.ApplicationStopping.Register(OnStopping);
        appLifetime.ApplicationStopped.Register(OnStopped);

        Console.CancelKeyPress += (sender, eventArgs) =>
        {
            appLifetime.StopApplication();
            // Don't terminate the process immediately, wait for the Main thread to exit gracefully.
            eventArgs.Cancel = true;
        };
    }

    private void OnStarted()
    {
        // Perform post-startup activities here
    }

    private void OnStopping()
    {
        // Perform on-stopping activities here
    }

    private void OnStopped()
    {
        // Perform post-stopped activities here
    }
}

StopApplication 請求結束應用程式。下面的類使用 StopApplication 正常關閉應用程式當類的 Shutdown 方法被呼叫的時候:

public class MyClass
{
    private readonly IHostApplicationLifetime _appLifetime;

    public MyClass(IHostApplicationLifetime appLifetime)
    {
        _appLifetime = appLifetime;
    }

    public void Shutdown()
    {
        _appLifetime.StopApplication();
    }
}

 

Scope validation

當應用程式的環境是 Development 的時候,CreateDefaultBuilder 設定 ServiceProviderOptions.ValidateScopes 為 true。

當 ValidateScopes 被設定為 true 時,預設的服務提供器執行檢查驗證:

  • Scoped 服務不能直接或者間接的從根服務提供器中解析出來
  • Scoped 服務不能直接或者間接的注入到單例中

根服務提供器在 BuildServiceProvider 被呼叫時建立。根服務提供器的生命週期和應用程式/伺服器的生命週期一致,當提供器和應用程式一起啟動,在應用程式關閉時釋放。

Scoped 服務由建立它的容器釋放。如果一個 scoped 服務在根容器中建立,服務的生命週期會有效的提升為單例,因為它只有在應用程/伺服器關閉的時候會釋放掉。在呼叫 BuildServiceProvider 的時候,驗證服務會捕捉這些情況。

為了在 Production 環境中總是包含 validate scopes,在 Host builer 上使用 UseDefaultServiceProvider 配置 ServiceProviderOptions

WebHost.CreateDefaultBuilder(args)
    .UseDefaultServiceProvider((context, options) => {
        options.ValidateScopes = true;
    })

相關文章