.NET Web 宿主(Web Host)【下】
此为系列文章,对MSDN ASP.NET Core 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解。
重载配置
使用Configuration来配置一个Web 宿主。在下面的示例中,宿主配置以可选的形式在hostsettings.json 文件中指定。从hostsettings.json文件中加载的任何配置都可能被命令行参数重写。内置的配置(config 文件)被UseConfiguration 用来配置宿主。IWebHostBuilder
配置被添加到app的配置中,然而反过来却是不正确的。ConfigureAppConfiguration
不会影响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
中拷贝键值到宿主构造器配置中。因此,为JSON, INI, and XML设置文件设置reloadOnChange: true
是没有任何效果的。
为了指定宿主运行在一个特定的URL上,当执行dotnet run 命令时,所需要的值可以从命令提示符中传递进来。命令行参数重载了从hostsettings.json文件获取到的urls
值,使得服务会监听8080端口:
dotnet run --urls "http://*:8080"
管理宿主
Run
Run方法启动了web app,并且阻塞了调用线程直到宿主关闭。
host.Run();
Start
通过调用Start
方法来使宿主以非阻塞的方式运行。
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(); }
使用一个静态的便捷方法,app可以初始化并且开启一个宿主,其使用了CreateDefaultBuilder
的预配置的默认值。这些方法启动了服务并且没有控制台输出,并使用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)发出。app显示了一个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) 产生一样的结果,唯一不同之处在于app响应这个请求:http://localhost:8080
。
Start(Action<IRouteBuilder> routeBuilder)
使用一个IRouteBuilder(Microsoft.AspNetCore.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! |
Throws an exception with string "ooops!" |
http://localhost:5000/throw |
Throws an exception with string "Uh oh!" |
http://localhost:5000/Sante/Kevin |
Sante, Kevin! |
http://localhost:5000 |
Hello World! |
WaitForShutdown
方法会被阻塞,直到一个中断(Ctrl-C/SIGINT 或SIGTERM)发出。app显示了一个Console.WriteLine
方法并等待任何一个按键来退出。
Start(string url, Action<IRouteBuilder> routeBuilder)
使用一个URL和一个IRouteBuilder
实例:
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) 产生一样的结果,唯一不同之处在于app响应来自 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)发出。app显示了一个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<IApplicationBuilder> app)产生一样的结果,唯一不同之处在于app响应来自http://localhost:8080
的请求。
接口IWebHostEnvironment
接口IWebHostEnvironment
提供了关于app寄宿环境的信息。我们可以使用构造函数注入来获取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 可以用来在启动时候基于环境配置app。或者,将IWebHostEnvironment
注入到Startup构造函数中,之后便可以在ConfigureServices
中进行使用。
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 environmentName)
方法。更多信息,请参考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; }
当创建自定义中间件时,IWebHostEnvironment
也可以被注入到Invoke
方法:
public async Task Invoke(HttpContext context, IWebHostEnvironment env) { if (env.IsDevelopment()) { // Configure middleware for Development } else { // Configure middleware for Staging/Production } var contentRootPath = env.ContentRootPath; }
接口IHostApplicationLifetime
接口IHostApplicationLifetime
运行启动后和关闭活动。接口上的三个属性是取消令牌,它们用来注册定义了开始和结束事件的Action
方法。
Cancellation Token | Triggered when… |
---|---|
ApplicationStarted |
The host has fully started. |
ApplicationStopped |
The host is completing a graceful shutdown. All requests should be processed. Shutdown blocks until this event completes. |
ApplicationStopping |
The host is performing a graceful shutdown. Requests may still be processing. Shutdown blocks until this event completes. |
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
请求了app的终结。以下的类使用了StopApplication,当类的
来优雅的关闭一个app:Shutdown
方法被调用时,
public class MyClass { private readonly IHostApplicationLifetime _appLifetime; public MyClass(IHostApplicationLifetime appLifetime) { _appLifetime = appLifetime; } public void Shutdown() { _appLifetime.StopApplication(); } }
域验证
如果app处于开发环境,那么CreateDefaultBuilder 会将ServiceProviderOptions.ValidateScopes 设置为true。
当ValidateScopes
设置为true时,默认的服务提供器会执行检查以验证:
- scoped 服务不会直接或者间接从根服务提供器来解析。
- scoped服务不会直接或者间接注入到单例服务中。
当调用BuildServiceProvider 时会创建根服务提供器。根服务提供器的生命周期与app的生命周期保持一致。随着app的开始而启动,随着app的关闭而销毁。
scoped 服务被创建它们的容器所销毁。如果一个scoped服务在根容器中创建,那么这个服务的生命周期便会被提升为单例,因为它仅会在app关闭时被根容器所销毁。服务域验证会在BuildServiceProvider
被调用时捕获这些情形并执行检查。
为了总是执行域验证,包括在生产环境中,在宿主 构造器上用UseDefaultServiceProvider 来配置 ServiceProviderOptions:
WebHost.CreateDefaultBuilder(args) .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = true; })
额外资源
- Host ASP.NET Core on Windows with IIS
- Host ASP.NET Core on Linux with Nginx
- Host ASP.NET Core on Linux with Apache
- Host ASP.NET Core in a Windows Service