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
    评论
ASP.NET Core 提供了内置的身份验证和授权功能,可以轻松地实现用户的登录和登出功能。 要实现登录功能,首先需要在 ConfigureServices 方法中添加身份验证服务: ```csharp services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; }) .AddCookie(options => { options.LoginPath = "/Account/Login/"; options.LogoutPath = "/Account/Logout/"; }) .AddGoogle(options => { options.ClientId = Configuration["Authentication:Google:ClientId"]; options.ClientSecret = Configuration["Authentication:Google:ClientSecret"]; }); ``` 上面的代码中,我们添加了 Cookie 身份验证服务,并指定了登录和登出的路径。同时,我们还添加了 Google 身份验证服务,并设置了 ClientId 和 ClientSecret。 接着,在 Configure 方法中启用身份验证中间件: ```csharp app.UseAuthentication(); ``` 现在,我们可以在 AccountController 中添加 Login 和 Logout 的动作方法: ```csharp public IActionResult Login(string returnUrl = "/") { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = "/") { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { _logger.LogInformation("User logged in."); return RedirectToLocal(returnUrl); } if (result.RequiresTwoFactor) { return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe }); } if (result.IsLockedOut) { _logger.LogWarning("User account locked out."); return RedirectToAction(nameof(Lockout)); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return View(model); } } // If we got this far, something failed, redisplay form return View(model); } [HttpPost] public async Task<IActionResult> Logout() { await _signInManager.SignOutAsync(); _logger.LogInformation("User logged out."); return RedirectToAction(nameof(HomeController.Index), "Home"); } ``` 上面的代码中,我们首先添加了一个 Get 请求的 Login 方法,用于显示登录页面。接着,我们添加了一个 Post 请求的 Login 方法,用于处理用户提交的登录表单。在这个方法中,我们调用了 _signInManager.PasswordSignInAsync 方法进行身份验证,并根据不同的结果进行相应的处理。最后,我们还添加了一个 Logout 方法,用于处理用户的登出请求。 现在,我们只需要在视图中添加相应的表单,就可以实现登录和登出功能了: ```html <form asp-action="Login" asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post"> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <div class="checkbox"> <label asp-for="RememberMe"> <input asp-for="RememberMe" /> @Html.DisplayNameFor(m => m.RememberMe) </label> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Login</button> </div> </form> <a asp-action="Logout" asp-controller="Account">Logout</a> ``` 上面的代码中,我们添加了一个登录表单和一个登出链接,用于演示登录和登出功能。其中,登录表单使用了 asp-for 和 asp-validation-for 标签帮助器,可以自动生成相应的 HTML 元素和验证信息。登出链接使用了 asp-action 和 asp-controller 标签帮助器,可以自动生成相应的 URL。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值