ASP.NET Core 01基础知识概述
Program.cs
使用 Web 模板创建的 ASP.NET Core 应用包含 Program.cs 文件中的应用程序启动代码。
Program.cs 文件中包含:
- 已配置应用所需的服务。
- 应用的请求处理管道定义为一系列中间件组件。
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapGet("/hi", () => "Hello!");
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
依赖关系注入(服务)
ASP.NET Core 包含依赖关系注入 (DI),可在应用中提供配置的服务。 使用以上代码中的 WebApplicationBuilder.Services、builder.Services 将服务添加到 DI 容器。 实例化 WebApplicationBuilder 时,会添加许多框架提供的服务。 在以下代码中,builder 是一个 WebApplicationBuilder:
WebApplicationBuilder
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var app = builder.Build();
通常使用构造函数注入从 DI 解析服务。 DI 框架在运行时提供此服务的实例。
构造函数注入从 DI 解析服务
public class IndexModel : PageModel
{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;
public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel> logger)
{
_context = context;
_logger = logger;
}
public IList<Movie> Movie { get;set; }
public async Task OnGetAsync()
{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}
中间件
请求处理管道由一系列中间件组件组成。 每个组件在 HttpContext 上执行操作,调用管道中的下一个中间件或终止请求。
按照惯例,通过调用 Use{Feature} 扩展方法,向管道添加中间件组件。
中间件
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
主机
ASP.NET Core 应用在启动时构建主机。 主机封装应用的所有资源,例如:
HTTP 服务器实现
- 中间件组件
- Logging
- 依赖关系注入 (DI) 服务
- Configuration
有三个不同的主机:
- .NET WebApplication 主机,也称为最小主机。
- .NET 通用主机
- ASP.NET Core Web 主机
建议使用 .NET WebApplication 主机,并在所有 ASP.NET Core 模板中使用。 .NET WebApplication 主机和 .NET 通用主机共享许多相同的接口和类。 ASP.NET Core Web 主机仅用于支持后向兼容性。
WebApplication 主机实例化
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var app = builder.Build();//实例化
WebApplicationBuilder.Build 方法使用一组默认选项配置主机,例如:
- 将 Kestrel 用作 Web 服务器并启用 IIS 集成。
- 从 appsettings.json、环境变量、命令行参数和其他配置源中加载配置。
- 将日志记录输出发送到控制台并调试提供程序。
服务器
ASP.NET Core 应用使用 HTTP 服务器实现侦听 HTTP 请求。 服务器对应用的请求在表面上呈现为一组由 HttpContext 组成的请求功能。
Windows
ASP.NET Core 提供以下服务器实现:
- Kestrel 是跨平台 Web 服务器。 Kestrel 通常使用 IIS 在反向代理配置中运行。 在 ASP.NET Core 2.0 或更高版本中,Kestrel 可作为面向公众的边缘服务器运行,直接向 Internet 公开。
- IIS HTTP 服务器适用于使用 IIS 的 Windows。 借助此服务器,ASP.NET Core 应用和 IIS 在同一进程中运行。
- HTTP.sys是适用于不与 IIS 一起使用的 Windows 的服务器。
Linux
ASP.NET Core 提供 Kestrel 跨平台服务器实现。 在 ASP.NET Core 2.0 或更高版本中,Kestrel 可作为面向公众的边缘服务器运行,直接向 Internet 公开。 Kestrel 通常使用 Nginx 或 Apache 在反向代理配置中运行。
配置
ASP.NET Core 提供了配置框架,可以从配置提供程序的有序集中将设置作为名称/值对。 可将内置配置提供程序用于各种源,例如 .json 文件、.xml 文件、环境变量和命令行参数。 可编写自定义配置提供程序以支持其他源。
默认情况下,ASP.NET Core 应用配置为从 appsettings.json、环境变量和命令行等读取内容。 加载应用配置后,来自环境变量的值将替代来自 appsettings.json 的值。
环境
执行环境(例如 Development、Staging 和 Production)在 ASP.NET Core 中可用。 通过设置 ASPNETCORE_ENVIRONMENT 环境变量来指定应用的运行环境。 ASP.NET Core 在应用启动时读取该环境变量,并将该值存储在 IWebHostEnvironment 实现中。 通过依赖关系注入 (DI),可以在应用中任何位置实现此操作。
app.Environment.IsDevelopment()
Logging
ASP.NET Core 支持适用于各种内置和第三方日志记录提供程序的日志记录 API。 可用的提供程序包括:
- 控制台
- 调试
- Windows 事件跟踪
- Windows 事件日志
- TraceSource
- Azure 应用服务
- Azure Application Insights
若要创建服务,请从依赖关系注入 (DI) 解析 ILogger
日志
public class IndexModel : PageModel
{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;
public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel> logger)
{
_context = context;
_logger = logger;
}
public IList<Movie> Movie { get;set; }
public async Task OnGetAsync()
{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}
路由
路由是映射到处理程序的 URL 模式。 处理程序通常是 Razor 页面、MVC 控制器中的操作方法或中间件。 借助 ASP.NET Core 路由,可以控制应用使用的 URL。
以下由 ASP.NET Core Web 应用程序模板生成的代码调用 UseRouting:
app.UseRouting();
错误处理
ASP.NET Core 具有用于处理错误的内置功能,例如:
- 开发人员异常页
- 自定义错误页
- 静态状态代码页
- 启动异常处理
发出 HTTP 请求
IHttpClientFactory 的实现可用于创建 HttpClient 实例。 工厂可以:
- 提供一个中心位置,用于命名和配置逻辑 HttpClient 实例。 例如,注册并配置 github 客户端以访问 GitHub。 注册并配置默认客户端以实现其他目的。
- 支持多个委托处理程序的注册和链接,以生成出站请求中间件管道。 此模式类似于 ASP.NET Core 的入站中间件管道。 此模式提供了一种用于管理 HTTP 请求相关问题的机制,包括缓存、错误处理、序列化以及日志记录。
- 与 Polly 集成,这是用于瞬时故障处理的常用第三方库。
- 管理基础 HttpClientHandler 实例的池和生存期,避免手动管理 HttpClient 生存期时可能出现的常见 DNS 问题。
- 通过 ILogger 添加可配置的日志记录体验,用于记录通过工厂创建的客户端发送的所有请求。
内容根
内容根目录是指向以下内容的基路径:
- 托管应用的可执行文件 (.exe)。
- 构成应用程序的已编译程序集 (.dll)。
- 应用使用的内容文件,例如:Razor 文件(.cshtml、.razor)、配置文件(.json、.xml)、数据文件 (.db)
- Web 根目录,通常是 wwwroot 文件夹。
Web 根
Web 根目录是公用静态资源文件的基路径,例如:
- 样式表 (.css)
- JavaScript (.js)
- 图像(.png、.jpg)
默认情况下,静态文件仅从 Web 根目录及其子目录提供。 Web 根目录路径默认为 {content root}/wwwroot。 在构建主机时设置路径,可指定不同的 Web 根目录。
在 Razor.cshtml 文件中,~/ 指向 Web 根。 以 ~/ 开头的路径称为虚拟路径。
WebApplicationBuilder 源代码
WebApplicationBuilder 源代码
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// A builder for web applications and services.
/// </summary>
public sealed class WebApplicationBuilder
{
private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder";
private readonly HostBuilder _hostBuilder = new();
private readonly BootstrapHostBuilder _bootstrapHostBuilder;
private readonly WebApplicationServiceCollection _services = new();
private readonly List<KeyValuePair<string, string>> _hostConfigurationValues;
private WebApplication? _builtApplication;
internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null)
{
Services = _services;
var args = options.Args;
// Run methods to configure both generic and web host defaults early to populate config from appsettings.json
// environment variables (both DOTNET_ and ASPNETCORE_ prefixed) and other possible default sources to prepopulate
// the correct defaults.
_bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties);
// Don't specify the args here since we want to apply them later so that args
// can override the defaults specified by ConfigureWebHostDefaults
_bootstrapHostBuilder.ConfigureDefaults(args: null);
// This is for testing purposes
configureDefaults?.Invoke(_bootstrapHostBuilder);
// We specify the command line here last since we skipped the one in the call to ConfigureDefaults.
// The args can contain both host and application settings so we want to make sure
// we order those configuration providers appropriately without duplicating them
if (args is { Length: > 0 })
{
_bootstrapHostBuilder.ConfigureAppConfiguration(config =>
{
config.AddCommandLine(args);
});
}
_bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
{
// Runs inline.
webHostBuilder.Configure(ConfigureApplication);
// Attempt to set the application name from options
options.ApplyApplicationName(webHostBuilder);
});
// Apply the args to host configuration last since ConfigureWebHostDefaults overrides a host specific setting (the application name).
_bootstrapHostBuilder.ConfigureHostConfiguration(config =>
{
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
// Apply the options after the args
options.ApplyHostConfiguration(config);
});
Configuration = new();
// Collect the hosted services separately since we want those to run after the user's hosted services
_services.TrackHostedServices = true;
// This is the application configuration
var (hostContext, hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration, _hostBuilder);
// Stop tracking here
_services.TrackHostedServices = false;
// Capture the host configuration values here. We capture the values so that
// changes to the host configuration have no effect on the final application. The
// host configuration is immutable at this point.
_hostConfigurationValues = new(hostConfiguration.AsEnumerable());
// Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder
var webHostContext = (WebHostBuilderContext)hostContext.Properties[typeof(WebHostBuilderContext)];
// Grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
Environment = webHostContext.HostingEnvironment;
Logging = new LoggingBuilder(Services);
Host = new ConfigureHostBuilder(hostContext, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
Services.AddSingleton<IConfiguration>(_ => Configuration);
}
/// <summary>
/// Provides information about the web hosting environment an application is running.
/// </summary>
public IWebHostEnvironment Environment { get; }
/// <summary>
/// A collection of services for the application to compose. This is useful for adding user provided or framework provided services.
/// </summary>
public IServiceCollection Services { get; }
/// <summary>
/// A collection of configuration providers for the application to compose. This is useful for adding new configuration sources and providers.
/// </summary>
public ConfigurationManager Configuration { get; }
/// <summary>
/// A collection of logging providers for the application to compose. This is useful for adding new logging providers.
/// </summary>
public ILoggingBuilder Logging { get; }
/// <summary>
/// An <see cref="IWebHostBuilder"/> for configuring server specific properties, but not building.
/// To build after configuration, call <see cref="Build"/>.
/// </summary>
public ConfigureWebHostBuilder WebHost { get; }
/// <summary>
/// An <see cref="IHostBuilder"/> for configuring host specific properties, but not building.
/// To build after configuration, call <see cref="Build"/>.
/// </summary>
public ConfigureHostBuilder Host { get; }
/// <summary>
/// Builds the <see cref="WebApplication"/>.
/// </summary>
/// <returns>A configured <see cref="WebApplication"/>.</returns>
public WebApplication Build()
{
// Wire up the host configuration here. We don't try to preserve the configuration
// source itself here since we don't support mutating the host values after creating the builder.
_hostBuilder.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(_hostConfigurationValues);
});
var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration);
// Wire up the application configuration by copying the already built configuration providers over to final configuration builder.
// We wrap the existing provider in a configuration source to avoid re-bulding the already added configuration sources.
_hostBuilder.ConfigureAppConfiguration(builder =>
{
builder.Add(chainedConfigSource);
foreach (var (key, value) in ((IConfigurationBuilder)Configuration).Properties)
{
builder.Properties[key] = value;
}
});
// This needs to go here to avoid adding the IHostedService that boots the server twice (the GenericWebHostService).
// Copy the services that were added via WebApplicationBuilder.Services into the final IServiceCollection
_hostBuilder.ConfigureServices((context, services) =>
{
// We've only added services configured by the GenericWebHostBuilder and WebHost.ConfigureWebDefaults
// at this point. HostBuilder news up a new ServiceCollection in HostBuilder.Build() we haven't seen
// until now, so we cannot clear these services even though some are redundant because
// we called ConfigureWebHostDefaults on both the _deferredHostBuilder and _hostBuilder.
foreach (var s in _services)
{
services.Add(s);
}
// Add the hosted services that were initially added last
// this makes sure any hosted services that are added run after the initial set
// of hosted services. This means hosted services run before the web host starts.
foreach (var s in _services.HostedServices)
{
services.Add(s);
}
// Clear the hosted services list out
_services.HostedServices.Clear();
// Add any services to the user visible service collection so that they are observable
// just in case users capture the Services property. Orchard does this to get a "blueprint"
// of the service collection
// Drop the reference to the existing collection and set the inner collection
// to the new one. This allows code that has references to the service collection to still function.
_services.InnerCollection = services;
var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers;
if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider))
{
// Something removed the _hostBuilder's TrackingChainedConfigurationSource pointing back to the ConfigurationManager.
// This is likely a test using WebApplicationFactory. Replicate the effect by clearing the ConfingurationManager sources.
((IConfigurationBuilder)Configuration).Sources.Clear();
}
// Make builder.Configuration match the final configuration. To do that, we add the additional
// providers in the inner _hostBuilders's Configuration to the ConfigurationManager.
foreach (var provider in hostBuilderProviders)
{
if (!ReferenceEquals(provider, chainedConfigSource.BuiltProvider))
{
((IConfigurationBuilder)Configuration).Add(new ConfigurationProviderSource(provider));
}
}
});
// Run the other callbacks on the final host builder
Host.RunDeferredCallbacks(_hostBuilder);
_builtApplication = new WebApplication(_hostBuilder.Build());
// Mark the service collection as read-only to prevent future modifications
_services.IsReadOnly = true;
// Resolve both the _hostBuilder's Configuration and builder.Configuration to mark both as resolved within the
// service provider ensuring both will be properly disposed with the provider.
_ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>();
return _builtApplication;
}
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
{
Debug.Assert(_builtApplication is not null);
// UseRouting called before WebApplication such as in a StartupFilter
// lets remove the property and reset it at the end so we don't mess with the routes in the filter
if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder))
{
app.Properties.Remove(EndpointRouteBuilderKey);
}
if (context.HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Wrap the entire destination pipeline in UseRouting() and UseEndpoints(), essentially:
// destination.UseRouting()
// destination.Run(source)
// destination.UseEndpoints()
// Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication);
// Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already
if (_builtApplication.DataSources.Count > 0)
{
// If this is set, someone called UseRouting() when a global route builder was already set
if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder))
{
app.UseRouting();
}
else
{
// UseEndpoints will be looking for the RouteBuilder so make sure it's set
app.Properties[EndpointRouteBuilderKey] = localRouteBuilder;
}
}
// Wire the source pipeline to run in the destination pipeline
app.Use(next =>
{
_builtApplication.Run(next);
return _builtApplication.BuildRequestDelegate();
});
if (_builtApplication.DataSources.Count > 0)
{
// We don't know if user code called UseEndpoints(), so we will call it just in case, UseEndpoints() will ignore duplicate DataSources
app.UseEndpoints(_ => { });
}
// Copy the properties to the destination app builder
foreach (var item in _builtApplication.Properties)
{
app.Properties[item.Key] = item.Value;
}
// Remove the route builder to clean up the properties, we're done adding routes to the pipeline
app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey);
// reset route builder if it existed, this is needed for StartupFilters
if (priorRouteBuilder is not null)
{
app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder;
}
}
private sealed class LoggingBuilder : ILoggingBuilder
{
public LoggingBuilder(IServiceCollection services)
{
Services = services;
}
public IServiceCollection Services { get; }
}
}
}