钱行慕

导航

.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();
}

        以示例代码来使用如下的浏览器请求:

                                

RequestResponse
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 TokenTriggered 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,当类的Shutdown方法被调用时,来优雅的关闭一个app:

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;
    })

额外资源    

 

posted on 2020-03-10 12:41  钱行慕  阅读(502)  评论(0编辑  收藏  举报