应用启动
使用 Web 模板创建的 ASP.NET Core 应用程序,应用程序启动代码位于Program.cs文件中。
目前应用启动代码支持:
- Razor Pages
- MVC controllers with views
- Web API with controllers
- Minimal APIs
ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。
依赖关系注入概述
依赖项是指另一个对象所依赖的对象。
在以下示例中,MyDependency 类是 IndexModel 类的依赖项:
点击查看代码
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
该类创建并直接依赖于 MyDependency 类。 代码依赖项(如前面的示例)会产生问题,应避免使用,原因如下:
- 要用不同的实现替换 MyDependency,必须修改 IndexModel 类。
- 如果 MyDependency 具有依赖项,则必须由 IndexModel 类对其进行配置。 在具有多个依赖于 MyDependency 的类的大型项目中,配置代码将分散在整个应用中。
- 这种实现很难进行单元测试。
依赖关系注入通过以下方式解决了这些问题:
- 使用接口或基类将依赖关系实现抽象化。
- 在服务容器中注册依赖关系。 ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。 服务通常已在应用的 Program.cs 文件中注册。
- 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。
优化后的代码
public interface IMyDependency
{
void WriteMessage(string message);
}
public class MyDependency : IMyDependency
{
private readonly ILogger<IMyDependency> _logger;
public MyDependency2(ILogger<IMyDependency> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency.WriteMessage Message: {message}");
}
}
//Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();//注入
var app = builder.Build();
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
通过使用 DI 模式:
- 不使用具体类型 MyDependency,仅使用它实现的 IMyDependency 接口。 这样可以轻松地更改实现,而无需修改控制器或 Razor 页面。
- 不创建 MyDependency 的实例,这由 DI 容器创建。
在依赖项注入术语中,服务:
- 通常是向其他对象提供服务的对象,如 IMyDependency 服务。
- 与 Web 服务无关,尽管服务可能使用 Web 服务。
使用扩展方法注册服务组
ASP.NET Core 框架使用一种约定来注册一组相关服务。 约定使用单个 Add{GROUP_NAME} 扩展方法来注册该框架功能所需的所有服务。 例如,AddControllers 扩展方法会注册 MVC 控制器所需的服务。
下面代码演示如何使用扩展方法将其他服务添加到容器中:
点击查看代码
using ConfigSample.Options;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));
return services;
}
}
}
//使用
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
var app = builder.Build();
注意:每个 services.Add{GROUP_NAME} 扩展方法添加并可能配置服务。 例如,AddControllersWithViews 会添加带视图的 MVC 控制器所需的服务,AddRazorPages 会添加 Razor Pages 所需的服务。 建议应用遵循在 Microsoft.Extensions.DependencyInjection 命名空间中创建扩展方法的命名约定。 在 Microsoft.Extensions.DependencyInjection 命名空间中创建扩展方法后,可以:
- 封装服务注册组。
- 提供对服务的便捷 IntelliSense 访问。
服务生存期
可以使用以下任一生存期注册服务:
- 暂时
- 作用域
- 单例
暂时
暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 向 **AddTransient **注册暂时性服务。
在处理请求的应用中,在请求结束时会释放暂时服务。
范围内
对于 Web 应用,指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。 向 **AddScoped **注册范围内服务。
在处理请求的应用中,在请求结束时会释放有作用域的服务。
使用 Entity Framework Core 时,默认情况下 AddDbContext 扩展方法使用范围内生存期来注册 DbContext 类型。
单例
创建单例生命周期服务的情况如下:
- 在首次请求它们时进行创建;或者
- 在向容器直接提供实现实例时由开发人员进行创建。 很少用到此方法。
不要实现单一实例设计模式,或提供代码来释放单一实例。 向 **AddSingleton **注册单一实例服务。 单一实例服务必须是线程安全的,并且通常在无状态服务中使用。
在处理请求的应用中,当应用关闭并释放 ServiceProvider 时,会释放单一实例服务。 由于应用关闭之前不释放内存,因此请考虑单一实例服务的内存使用
服务注册方法
框架提供了适用于特定场景的服务注册扩展方法:
方法 | 自动对象 (object) 释放 | 多种实现 | 传递参数 |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()示例:services.AddSingleton<IMyDep, MyDep>(); | 是 | 是 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})示例:services.AddSingleton |
是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>()示例:services.AddSingleton |
是 | 否 | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})示例:services.AddSingleton |
否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION})示例:services.AddSingleton(new MyDep(99)); | 否 | 否 | 是 |
要在中间件中使用范围内服务,请使用以下方法之一:
- 将服务注入中间件的 Invoke 或 InvokeAsync 方法。 使用构造函数注入会引发运行时异常,因为它强制使范围内服务的行为与单一实例类似。 生存期和注册选项部分中的示例演示了 方法。
- 使用基于工厂的中间件。 使用此方法注册的中间件按客户端请求(连接)激活,这也使范围内服务可注入中间件的构造函数。
构造函数注入行为
服务可使用以下方式来解析:
- IServiceProvider
- ActivatorUtilities:
- 创建未在容器中注册的对象。
- 用于某些框架功能。
构造函数可以接受非依赖关系注入提供的参数,但参数必须分配默认值。
当服务由 IServiceProvider 或 ActivatorUtilities 解析时,构造函数注入需要 public 构造函数。
当服务由 ActivatorUtilities 解析时,构造函数注入要求只存在一个适用的构造函数。 支持构造函数重载,但其参数可以全部通过依赖注入来实现的重载只能存在一个。
实体框架上下文
默认情况下,使用设置了范围的生存期将实体框架上下文添加到服务容器中,因为 Web 应用数据库操作通常将范围设置为客户端请求。 要使用其他生存期,请使用 AddDbContext 重载来指定生存期。 给定生存期的服务不应使用生存期比服务生存期短的数据库上下文。
生存期和注册选项
- 暂时性对象始终不同。 IndexModel 和中间件中的临时 OperationId 值不同。
- 范围内对象对给定请求而言是相同的,但在每个新请求之间不同。
- 单一实例对象对于每个请求是相同的。
在应用启动时解析服务
作用域验证
请求服务
ASP.NET Core 请求中的服务及其依赖项是通过 HttpContext.RequestServices 公开的。
框架为每个请求创建一个范围,RequestServices 公开限定范围的服务提供程序。 只要请求处于活动状态,所有作用域服务都有效。
设计能够进行依赖关系注入的服务
在设计能够进行依赖注入的服务时:
- 避免有状态的、静态类和成员。 通过将应用设计为改用单一实例服务,避免创建全局状态。
- 避免在服务中直接实例化依赖类。 直接实例化会将代码耦合到特定实现。
- 不在服务中包含过多内容,确保设计规范,并易于测试。
如果一个类有过多注入依赖项,这可能表明该类拥有过多的责任并且违反了单一责任原则 (SRP)。 尝试通过将某些职责移动到一个新类来重构类。
服务释放
容器为其创建的 IDisposable 类型调用 Dispose。 从容器中解析的服务绝对不应由开发人员释放。 如果类型或工厂注册为单一实例,则容器自动释放单一实例。
不由服务容器创建的服务
builder.Services.AddSingleton(new Service1());
- 服务实例不是由服务容器创建的。
- 框架不会自动释放服务。
- 开发人员负责释放服务。
默认服务容器替换
内置的服务容器旨在满足框架和大多数消费者应用的需求。 我们建议使用内置容器,除非你需要的特定功能不受它支持,例如:
属性注入
基于名称的注入
子容器
自定义生存期管理
对迟缓初始化的 Func
基于约定的注册
以下第三方容器可用于 ASP.NET Core 应用:
- Autofac
- DryIoc
- Grace
- LightInject
- Lamar
- Stashbox
- Unity
- Simple Injector
建议
- 不支持基于async/await 和 Task 的服务解析。 由于 C# 不支持异步构造函数,因此请在同步解析服务后使用异步方法。
- 避免在服务容器中直接存储数据和配置。 例如,用户的购物车通常不应添加到服务容器中。 配置应使用 选项模型。 同样,避免“数据持有者”对象,也就是仅仅为实现对另一个对象的访问而存在的对象。 最好通过 DI 请求实际项。
- 避免静态访问服务。 例如,避免将 IApplicationBuilder.ApplicationServices 捕获为静态字段或属性以便在其他地方使用。
- 使 DI 工厂保持快速且同步。
- 避免使用服务定位器模式。 例如,可以改为使用 DI 时,不要调用 GetService 来获取服务实例。
- 要避免的另一个服务定位器变体是注入需在运行时解析依赖项的工厂。 这两种做法混合了控制反转策略。
- 避免在 ConfigureServices 中调用 BuildServiceProvider。 当开发人员想要在 ConfigureServices 中解析服务时,通常会调用 BuildServiceProvider。
- 可释放的暂时性服务由容器捕获以进行释放。 如果从顶级容器解析,这会变为内存泄漏。
- 启用范围验证,确保应用没有捕获范围内服务的单一实例。 有关详细信息,请参阅作用域验证。
像任何一组建议一样,你可能会遇到需要忽略某建议的情况。 例外情况很少见,主要是框架本身内部的特殊情况。
DI 是静态/全局对象访问模式的替代方法。 如果将其与静态对象访问混合使用,则可能无法意识到 DI 的优点。
框架提供的服务
服务类型 | 生存期 |
---|---|
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | 暂时 |
IHostApplicationLifetime | 单例 |
IWebHostEnvironment | 单例 |
Microsoft.AspNetCore.Hosting.IStartup | 单例 |
Microsoft.AspNetCore.Hosting.IStartupFilter | 暂时 |
Microsoft.AspNetCore.Hosting.Server.IServer | 单例 |
Microsoft.AspNetCore.Http.IHttpContextFactory | 暂时 |
Microsoft.Extensions.Logging.ILogger |
单例 |
Microsoft.Extensions.Logging.ILoggerFactory | 单例 |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | 单例 |
Microsoft.Extensions.Options.IConfigureOptions |
暂时 |
Microsoft.Extensions.Options.IOptions |
单例 |
System.Diagnostics.DiagnosticSource | 单例 |
System.Diagnostics.DiagnosticListener | 单例 |