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

本文介绍 ASP.NET Core 的启动机制,包括程式进入点、生命周期管理及如何捕获应用程式启动与停止事件。通过 IHostApplicationLifetime 接口实现对应用程式的生命周期控制。

要了解程式的运作原理,要先知道程式的进入点及生命周期。
过往 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 会把之前注册的盖掉。

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

<think>我们讨论的是ASP.NET Core Web API项目的典型结构以及一些关键部分的语法。由于项目结构是标准化的,我将按照常见的项目组织方式逐一解释,并给出关键代码示例。 典型的ASP.NET Core Web API项目结构如下: 1. **Program.cs**:应用程序的入口点,负责配置主机和启动。 2. **Startup.cs**(在较新版本中,启动配置已合并到Program.cs,但为兼容性,我们也会提到): 配置服务和HTTP请求管道。 3. **appsettings.json**:配置文件,用于存储应用设置(如数据库连接字符串)。 4. **Controllers**:包含处理HTTP请求的控制器类。 5. **Models**:包含数据模型(实体)和DTO(数据传输对象)。 6. **Services**(可选):包含业务逻辑服务类。 7. **Data**(可选):包含数据库上下文和数据访问层(如使用Entity Framework Core)。 8. **Middleware**(可选):自定义中间件。 9. **Properties/launchSettings.json**:启动配置文件(如IIS设置、环境变量等)。 ### 详细说明及代码示例 #### 1. Program.cs 在.NET 6及更高版本中,使用顶级语句,因此Program.cs非常简洁。它创建了一个WebApplicationBuilder,配置服务,然后构建应用并配置中间件。 ```csharp var builder = WebApplication.CreateBuilder(args); // 添加服务到容器 builder.Services.AddControllers(); // 添加控制器服务 // 添加Swagger生成器(可选) builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // 配置HTTP请求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); // 映射控制器路由 app.Run(); ``` #### 2. Startup.cs(旧版模式,在.NET 5及以前) 在旧版模板中,Startup.cs有两个方法:`ConfigureServices`和`Configure`。在.NET 6+中,这些配置可以放在Program.cs中,但为了兼容性,我们也可以使用Startup类。 ```csharp public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddControllers(); // 其他服务注册... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } ``` #### 3. appsettings.json 配置文件示例: ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;" } } ``` #### 4. Controllers 控制器类负责处理HTTP请求。每个公共方法通常是一个动作(Action),可通过路由访问。 ```csharp [ApiController] [Route("api/[controller]")] public class WeatherForecastController : ControllerBase { [HttpGet] public IEnumerable<WeatherForecast> Get() { // 返回数据 } [HttpGet("{id}")] public ActionResult<WeatherForecast> GetById(int id) { // 根据id获取数据 } [HttpPost] public IActionResult Create(WeatherForecast forecast) { // 创建新资源 return CreatedAtAction(nameof(GetById), new { id = forecast.Id }, forecast); } } ``` #### 5. Models 模型类代表数据结构。 ```csharp public class WeatherForecast { public int Id { get; set; } public DateTime Date { get; set; } public int TemperatureC { get; set; } public string Summary { get; set; } } ``` #### 6. Services 服务类包含业务逻辑,通过依赖注入注入到控制器。 ```csharp public interface IWeatherService { IEnumerable<WeatherForecast> GetForecasts(); } public class WeatherService : IWeatherService { public IEnumerable<WeatherForecast> Getcasts() { // 业务逻辑 } } ``` 在Program.cs中注册服务: ```csharp builder.Services.AddScoped<IWeatherService, WeatherService>(); ``` 在控制器中使用: ```csharp [ApiController] [Route("api/[controller]")] public class WeatherForecastController : ControllerBase { private readonly IWeatherService _weatherService; public WeatherForecastController(IWeatherService weatherService) { _weatherService = weatherService; } [HttpGet] public IEnumerable<WeatherForecast> Get() { return _weatherService.Getcasts(); } } ``` #### 7. Data(使用Entity Framework Core) 定义DbContext: ```csharp public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<WeatherForecast> WeatherForecasts { get; set; } } ``` 在Program.cs中注册: ```csharp builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); ``` #### 8. Middleware 自定义中间件示例: ```csharp public class RequestLoggingMiddleware { private readonly RequestDelegate _next; public RequestLoggingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { Console.WriteLine($"Request path: {context.Request.Path}"); await _next(context); Console.WriteLine($"Response status: {context.Response.StatusCode}"); } } ``` 在Program.cs中使用: ```csharp app.UseMiddleware<RequestLoggingMiddleware>(); ``` ### 总结 ASP.NET Core Web API项目结构清晰,各部分职责明确: - **Program.cs**:配置主机和中间件管道。 - **配置文件**:appsettings.json存储配置。 - **控制器**:处理HTTP请求。 - **模型**:定义数据结构。 - **服务**:实现业务逻辑。 - **数据访问**:通过EF Core与数据库交互。 - **中间件**:处理请求和响应的管道组件。 §§相关问题§§: 1. ASP.NET Core中的依赖注入是如何工作的? 2. 如何配置和使用身份验证(如JWT)在Web API中? 3.ASP.NET Core中,中间件的执行顺序是怎样的? 4. 如何使用Entity Framework Core进行数据库迁移(Migrations)?</think>### ASP.NET Core Web API 项目结构详解 ASP.NET Core Web API 项目采用清晰的层次化结构,以下是典型项目结构及各部分详细说明: #### 1. **项目根目录** ```bash MyWebApi/ ├── Properties/ ├── Controllers/ ├── Models/ ├── Services/ ├── Data/ ├── appsettings.json ├── Program.cs └── Startup.cs (旧版模板) ``` #### 2. **核心文件详解** ##### 2.1 Program.cs - 应用入口点 ```csharp var builder = WebApplication.CreateBuilder(args); // 添加服务到容器 builder.Services.AddControllers(); // 添加控制器支持 builder.Services.AddEndpointsApiExplorer(); // API文档支持 builder.Services.AddSwaggerGen(); // Swagger文档生成 var app = builder.Build(); // 配置HTTP请求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); // 开发环境启用Swagger app.UseSwaggerUI(); // 提供Swagger UI界面 } app.UseHttpsRedirection(); // HTTPS重定向 app.UseAuthorization(); // 授权中间件 app.MapControllers(); // 映射控制器路由 app.Run(); // 启动应用 ``` **功能说明**: - `.AddControllers()`:注册API控制器 - `UseSwagger()`:启用OpenAPI文档 - `MapControllers()`:自动映射控制器路由 ##### 2.2 appsettings.json - 配置文件 ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=.;Database=MyDB;Trusted_Connection=True;" }, "Jwt": { "Key": "your_super_secret_key", "Issuer": "your_issuer" } } ``` #### 3. **核心目录结构** ##### 3.1 Controllers/ - API端点 ```csharp [ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly IProductService _productService; // 依赖注入 public ProductsController(IProductService productService) { _productService = productService; } [HttpGet] public async Task<ActionResult<IEnumerable<Product>>> GetProducts() { var products = await _productService.GetAllProducts(); return Ok(products); // 200 OK响应 } [HttpGet("{id}")] public async Task<ActionResult<Product>> GetProduct(int id) { var product = await _productService.GetProductById(id); return product != null ? Ok(product) : NotFound(); // 404处理 } [HttpPost] public async Task<ActionResult<Product>> CreateProduct(ProductDto productDto) { var createdProduct = await _productService.CreateProduct(productDto); return CreatedAtAction(nameof(GetProduct), new { id = createdProduct.Id }, createdProduct); // 201 Created响应 } } ``` **路由特性**: - `[Route("api/[controller]")]`:自动生成路由(如 `/api/products`) - `[HttpGet]`/`[HttpPost]`:定义HTTP方法 - `{id}`:路由参数绑定 ##### 3.2 Models/ - 数据模型 ```csharp public class Product { public int Id { get; set; } [Required] [StringLength(100)] public string Name { get; set; } [Range(0.01, 10000)] public decimal Price { get; set; } [ForeignKey("Category")] public int CategoryId { get; set; } public Category Category { get; set; } } // DTO (数据传输对象) public class ProductDto { [Required] public string Name { get; set; } public decimal Price { get; set; } } ``` **数据注解**: - `[Required]`:必填字段验证 - `[StringLength]`:字符串长度限制 - `[Range]`:数值范围验证 ##### 3.3 Services/ - 业务逻辑 ```csharp public interface IProductService { Task<IEnumerable<Product>> GetAllProducts(); Task<Product> GetProductById(int id); Task<Product> CreateProduct(ProductDto productDto); } public class ProductService : IProductService { private readonly AppDbContext _context; public ProductService(AppDbContext context) { _context = context; } public async Task<IEnumerable<Product>> GetAllProducts() { return await _context.Products.Include(p => p.Category).ToListAsync(); } public async Task<Product> GetProductById(int id) { return await _context.Products.FindAsync(id); } public async Task<Product> CreateProduct(ProductDto productDto) { var product = new Product { Name = productDto.Name, Price = productDto.Price }; _context.Products.Add(product); await _context.SaveChangesAsync(); return product; } } ``` **依赖注入注册** (在Program.cs中): ```csharp builder.Services.AddScoped<IProductService, ProductService>(); ``` ##### 3.4 Data/ - 数据访问层 ```csharp public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置关系 modelBuilder.Entity<Product>() .HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId); } } ``` **数据库配置** (Program.cs): ```csharp builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); ``` #### 4. **高级特性示例** ##### JWT认证 ```csharp // Program.cs中添加 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); ``` ##### 自定义中间件 ```csharp public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) { _next = next; _logger = loggerFactory.CreateLogger<RequestLoggingMiddleware>(); } public async Task Invoke(HttpContext context) { _logger.LogInformation($"Request: {context.Request.Method} {context.Request.Path}"); await _next(context); _logger.LogInformation($"Response: {context.Response.StatusCode}"); } } // 注册中间件 (Program.cs) app.UseMiddleware<RequestLoggingMiddleware>(); ``` ### 项目关键点总结 1. **依赖注入**:内置IoC容器管理服务生命周期 2. **中间件管道**:请求处理流程的可扩展架构 3. **配置系统**:分层配置(环境变量 > appsettings.{env}.json > appsettings.json) 4. **环境管理**:通过`IHostEnvironment`区分开发/生产环境 5. **OpenAPI支持**:自动生成API文档 6. **异步编程**:全面支持async/await模式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值