要了解程式的运作原理,要先知道程式的进入点及生命周期。
过往 ASP.NET MVC 启动方式,是继承 HttpApplication
做为网站开始的进入点。
ASP.NET Core 改变了网站启动的方式,是用 Console Application
的方式,Host Kestrel
,提供 HTTP 的服务。
本篇将介绍 ASP.NET Core 3 的程式生命周期 (Application Lifetime
) 及补捉 Application
停启事件。
程式进入点
.NET Core 把 Web
及 Console
专案都变成一样的启动方式,预设从 Program.cs 的 Main()
做为程式进入点,再从程式进入点把 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)
- IApplicationBuilder 是最重要的参数也是必要的参数,Request 进出的 Pipeline 都是透过 ApplicationBuilder 来设定。
对 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!");
});
});
});
});
}
}
把 ConfigureServices
及 Configure
都改到 WebHost Builder
注册,网站的执行结果会是一样的。
若是透过 Generic Host
建立 Web Host
,也可以在 HostBuilder
用 ConfigureServices
注册 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
会把之前注册的盖掉。
执行流程如下: