第 3 章 ASP.NET Core 核心特性
3.1 启动与宿主
ASP.NET Core 应用程序启动时,它首先会配置并运行其宿主,宿主主要用来启动、初始化应用程序,并管理其生命周期
ASP.NET Core 应用程序本质上就是控制台应用程序
代码语言:javascript复制public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
由 CreateDefaultBuilder 方法创建 IWebHostBuilder 对象时所包含的主要默认选项如下:
- 配置 Kestrel 服务器作为默认的 Web 服务器来负责处理 Web 请求与响应
- 使用当前目录作为应用程序的内容目录,该目录决定了 ASP.NET Core 查找内容文件的位置
- 从以 ASPNETCORE_ 开头的环境变量中以及命令行参数中加载配置项
- 从 appsetting.json、appsettings.{Environment}.json、用户机密(仅开发环境)、环境变量和命令行参数等位置加载应用配置
- 配置日志功能,默认添加控制台输出和调试输出
- 如果应用程序呗托管在 IIS 中,启动 IIS 集成,它会配置应用程序的主机地址和端口,并允许捕获启动错误等
CreateDefaultBuilder 方法中所包含的默认配置能够通过 IWebHostBuilder 接口提供的扩展方法进行修改或增加
如 ConfigureAppConfiguration,ConfigureKestrel,ConfigureLogging,UseContentRoot 和 UseUrls 等
代码语言:javascript复制 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true);
})
.UseEnvironment(EnvironmentName.Development)
.UseContentRoot(@"C:public")
.UseSetting("https_port", "8080")
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")
.UseStartup<Startup>();
ASP.NET Core 内置了对程序运行环境的支持,通过设置不同的环境,能够使应用程序在运行时获取相应的配置,从而具有不同的行为和逻辑
内部提供3个环境:
- Development:开发
- Staging:预演
- Production:生产
Kestrel 是轻量级、托管的、开源且跨平台的 Web 服务器,它作为 ASP.NET Core 的组成部分,能够使 ASP.NET Core 应用程序运行在任何平台上
当 Kestrel 作为 ASP.NET Core 的服务器时,它会在 ASP.NET Core 的进程内运行,并负责监听 HTTP 请求以及对每一次的请求返回 HTTP 响应
在实际生产环境部署应用程序时,推荐使用主流的 Web 服务器(如 IIS 和 Apache 等)放在 Kestrel 之前作为反向代理服务器,增加应用程序的安全性,也提供了负载均衡、过滤请求和 URL 重定向等功能
IWebHostBuilder 接口有多个扩展方法,其中有一个很重要的是 UseStartup 方法,它主要向应用程序提供用于配置启动的类,而指定的这个类应具有以下两个方法:
- ConfigureServices:用于向 ASP.NET Core 的依赖注入容器添加服务
- Configure:用于添加中间件,配置请求管道
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
3.2 中间件
所谓中间件,就是处理 HTTP 请求和响应的组件,本质上是一段用来处理请求与响应的代码,多个中间件之间的链式关系使之形成了管道
ASP.NET Core 中内置了多个中间件,它们主要包含 MVC 认证、错误、静态文件、HTTPS 重定向和跨域资源共享(CORS)等,ASP.NET Core 也允许向管道添加自定义中间件
上一节的 Configure 方法中就是添加中间件的地方
中间件的添加顺序将决定 HTTP 请求以及 HTTP 响应遍历它们的顺序
每一个中间件都是通过调用 IApplicationBuilder 接口的 Use 和 Run 方法添加到请求管道中的
下面的例子是使用 Run 方法来添加一个中间件,该中间件会输出与本次请求相关的信息
代码语言:javascript复制app.Run(async (context) =>
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("----REQUEST----");
sb.AppendLine($"Host: {context.Request.Host}");
sb.AppendLine($"Method: {context.Request.Method}");
sb.AppendLine($"Path: {context.Request.Path}");
sb.AppendLine($"Protocol: {context.Request.Protocol}");
foreach (var item in context.Request.Headers)
{
sb.AppendLine($" {item.Key}: {item.Value}");
}
await context.Response.WriteAsync(sb.ToString());
});
与 Run 方法不同的是,Use 方法在处理完请求后还会将请求传入下一个中间件,并由它继续处理
代码语言:javascript复制app.Use(async (context, next) =>
{
Console.WriteLine("中间件 A:开始");
await next();
Console.WriteLine("中间件 A:结束");
});
除了 Run 和 Use 方法外,IApplicationBuilder 还提供了 Map、MapWhen 以及 UseWhen 方法,它们都可以指定条件,并在条件满足时创建新的分支管道,同时在新的分支上添加并执行中间件
Map 会根据是否配置指定的请求路径来决定是否在一个新分支上继续执行后续的中间件,并且在新分支上执行完后,不再回到原来的管道上
MapWhen 则可以满足更复杂的条件,它会对 HttpContext 对象进行进行更细致的判断,然后决定是否进入新的分支继续执行指定的中间件
UseWhen 创建的分支在执行结束后会继续回到原来的管道上
代码语言:javascript复制app.Map(new PathString("/maptest"),
a => a.Use(async (context, next) =>
{
Console.WriteLine("中间件 B:开始");
await next(); // 下一个中间件
Console.WriteLine("中间件 B:结束");
}));
app.UseWhen(context => context.Request.Path.Value == "/maptest",
a => a.Use(async (context, next) =>
{
Console.WriteLine("中间件 B:开始");
await next(); // 下一个中间件
Console.WriteLine("中间件 B:结束");
}));
自定义中间件
代码语言:javascript复制public class HttpMethodCheckMiddleware
{
// 在管道中的下一个中间件
private readonly RequestDelegate _next;
/// <summary>
/// 在中间件的构造函数中,可以得到下一个中间件,并且还可以注入需要的服务,比如 IHostEnvironment
/// </summary>
/// <param name="requestDelegate"></param>
/// <param name="environment"></param>
public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostEnvironment environment)
{
this._next = requestDelegate;
}
/// <summary>
/// 对 HTTP 请求方法进行判断,如果符合条件则继续执行下一个中间件
/// 否则返回 400 Bad Request 错误,并在响应中添加自定义消息头用于说明错误原因
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task Invoke(HttpContext context)
{
var requestMethod = context.Request.Method.ToUpper();
if (requestMethod == HttpMethods.Get || requestMethod == HttpMethods.Head)
{
return _next(context);
}
else
{
context.Response.StatusCode = 400;
context.Response.Headers.Add("X-AllowHTTPWeb", new[] {"GET,HEAD"});
context.Response.WriteAsync("只支持 GET、HEAD 方法");
return Task.CompletedTask;
}
}
}
添加自定义中间件
代码语言:javascript复制app.UseMiddleware<HttpMethodCheckMiddleware>();
为了更加方便地使用中间件,可以为它创建一个扩展方法
代码语言:javascript复制public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseHttpMethodCheckMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HttpMethodCheckMiddleware>();
}
}
调用扩展方法
代码语言:javascript复制app.UseHttpMethodCheckMiddleware();