ASP.NET Core 3 系列 - 应用程序生命周期 (Application Lifetime)

要了解程式的运作原理,要先知道程式的进入点及生命周期。
过往 ASP.NET MVC 启动方式,是继承 HttpApplication 做为网站开始的进入点。
ASP.NET Core 改变了网站启动的方式,是用 Console Application 的方式,Host Kestrel,提供 HTTP 的服务。
本篇将介绍 ASP.NET Core 3 的程式生命周期 (Application Lifetime) 及补捉 Application 停启事件。

程式进入点

.NET Core 把 WebConsole 专案都变成一样的启动方式,预设从 Program.csMain() 做为程式进入点,再从程式进入点把 Kestrel 实例化。

透过 .NET Core CLI 建置的 Program.cs 内容大致如下:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

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

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

Main() 透过 CreateHostBuilder 方法宣告需要相依的相关服务,并设定 WebHost 启动后要执行 Startup 类别。

  • CreateHostBuilder
    透过此方法宣告相依的服务及组态设定等,其中包含 WebHost
    可以在 Host 产生之前设定一些前置准备动作,当 Host 建立完成时,就可以使用已准备好的物件等。
  • UseStartup
    设定 WebHostBuilder 产生的 WebHost 时,所要执行的类别。
  • Build
    当前置准备都设定完成后,就可以呼叫此方法实例化 Host,同时也会实例化 WebHost
  • Run
    启动 Host

.NET Core 3.0 官方建议的方式是透过 Generic Host 建立 Web Host。
但如果真的不想透过 Generic Host 建立 Web Host,可改成以下方式:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var hostBuilder = CreateHostBuilder(args);
            var host = hostBuilder.Build();
            host.Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Startup.cs

Host 建置时,WebHost 会呼叫 UseStartup 泛型类别的 ConfigureServices 方法。
Host 启动后,WebHost 会呼叫 UseStartup 泛型类别的 Configure 方法。

透过 .NET Core CLI 建置的 Startup.cs 内容大致如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyWebsite
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // 注册 Services ...
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }
    }
}
  • ConfigureServices
    ConfigureServices 是用来将服务注册到 DI 容器用的。这个方法可不实做,并不是必要的方法。
    (DI 可以参考这篇:ASP.NET Core 3 系列 - 依赖注入 (Dependency Injection))
  • Configure
    这是必要的方法,一定要时做。但 Configure 方法的参数并不固定,参数的实例都是从 WebHost 注入进来,可依需求增减需要的参数。
    • IApplicationBuilder 是最重要的参数也是必要的参数,Request 进出的 Pipeline 都是透过 ApplicationBuilder 来设定。
      (Pipeline 可以参考这篇:ASP.NET Core 3 系列 - Middleware)

对 WebHost 来说 Startup.cs 并不是必要存在的功能。
可以试著把 Startup.cs 中的两个方法,都改成在 WebHost Builder 设定,变成启动的前置准备。Program.cs 如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var hostBuilder = CreateHostBuilder(args);
            var host = hostBuilder.Build();
            host.Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices(services =>
                {
                    // Generic Host 注册 Services ...
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .ConfigureServices(services =>
                        {
                            // Web Host 注册 Services ...
                        })
                        .Configure(app =>
                        {
                            app.UseRouting();
                            app.UseEndpoints(endpoints =>
                            {
                                endpoints.MapGet("/",
                                    async context => {
                                        await context.Response.WriteAsync("Hello World!");
                                    });
                            });
                        });
                });
    }
}

ConfigureServicesConfigure 都改到 WebHost Builder 注册,网站的执行结果会是一样的。
若是透过 Generic Host 建立 Web Host,也可以在 HostBuilderConfigureServices 注册 Services。

Application Lifetime

除了程式进入点外,WebHost 的停起也是网站事件很重要一环,ASP.NET Core 不像 ASP.NET MVC 用继承的方式补捉启动及停止事件。
而是透过 IHostApplicationLifetime 来补捉 WebHost 的停启事件。

IHostApplicationLifetime 有三个注册监听事件及终止网站事件可以触发。如下:

public interface IHostApplicationLifetime
{
    CancellationToken ApplicationStarted { get; }
    CancellationToken ApplicationStopping { get; }
    CancellationToken ApplicationStopped { get; }
    void StopApplication();
}
  • ApplicationStarted
    当 WebHost 启动完成后,会执行的启动完成事件
  • ApplicationStopping
    当 WebHost 触发停止时,会执行的准备停止事件
  • ApplicationStopped
    当 WebHost 停止事件完成时,会执行的停止完成事件
  • StopApplication
    可以透过此方法主动触发终止网站

范例程式

透过 Console 输出执行的过程,Program.cs 范例如下:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Output("[Program] Start");

            Output("[Program] Create HostBuilder");
            var hostBuilder = CreateHostBuilder(args);

            Output("[Program] Build Host");
            var host = hostBuilder.Build();

            Output("[Program] Run Host");
            host.Run();

            Output("[Program] End");
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices(services => {
                    Output("[Program] hostBuilder.ConfigureServices - Called");
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .ConfigureServices(services => {
                            Output("[Program] webBuilder.ConfigureServices - Called");
                        })
                        .Configure(app => {
                            Output("[Program] webBuilder.Configure - Called");
                        })
                        .UseStartup<Startup>();
                });

        public static void Output(string message)
        {
            Console.WriteLine($"[{DateTime.Now:yyyy/MM/dd HH:mm:ss}] {message}");
        }
    }
}

Startup.cs

using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MyWebsite
{
    public class Startup
    {
        public Startup()
        {
            Program.Output("[Startup] Constructor - Called");
        }

        public void ConfigureServices(IServiceCollection services)
        {
            Program.Output("[Startup] ConfigureServices - Called");
        }

        public void Configure(IApplicationBuilder app, IHostApplicationLifetime appLifetime)
        {
            appLifetime.ApplicationStarted.Register(() =>
            {
                Program.Output("[Startup] ApplicationLifetime - Started");
            });

            appLifetime.ApplicationStopping.Register(() =>
            {
                Program.Output("[Startup] ApplicationLifetime - Stopping");
            });

            appLifetime.ApplicationStopped.Register(() =>
            {
                Thread.Sleep(5 * 1000);
                Program.Output("[Startup] ApplicationLifetime - Stopped");
            });

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); });
            });

            // For trigger stop WebHost
            var thread = new Thread(() =>
            {
                Thread.Sleep(5 * 1000);
                Program.Output("[Startup] Trigger stop WebHost");
                appLifetime.StopApplication();
            });
            thread.Start();

            Program.Output("[Startup] Configure - Called");
        }
    }
}

执行结果

[2019/10/24 01:24:59] [Program] Start
[2019/10/24 01:24:59] [Program] Create HostBuilder
[2019/10/24 01:24:59] [Program] Build Host
[2019/10/24 01:24:59] [Program] hostBuilder.ConfigureServices - Called
[2019/10/24 01:24:59] [Program] webBuilder.ConfigureServices - Called
[2019/10/24 01:24:59] [Startup] Constructor - Called
[2019/10/24 01:24:59] [Startup] ConfigureServices - Called
[2019/10/24 01:25:00] [Program] Run Host
[2019/10/24 01:25:00] [Startup] Configure - Called
[2019/10/24 01:25:00] [Startup] ApplicationLifetime - Started
[2019/10/24 01:25:05] [Startup] Trigger stop WebHost
[2019/10/24 01:25:05] [Startup] ApplicationLifetime - Stopping
[2019/10/24 01:25:10] [Startup] ApplicationLifetime - Stopped
[2019/10/24 01:25:10] [Program] End

输出内容少了 [Program] webBuilder.Configure - Called,因为 Configure 只能有一个,后注册的 Configure 会把之前注册的盖掉。

执行流程如下:
在这里插入图片描述

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值