在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
此为系列文章,对MSDN ASP.NET Core 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解
服务注册方法 服务注册扩展方法提供了适用于各个场景下的重载。如下表格所示:
关于类型清理的更多信息,请参考the Disposal of services这一章节,对于多个实现的一个通用场景便是模拟类型用来测试。 只有在还没有注册的实现的时候, 在如下的代码中,第一行为
services.AddSingleton<IMyDependency, MyDependency>(); // The following line has no effect: services.TryAddSingleton<IMyDependency, DifferentDependency>(); 更多信息,请参考: TryAddEnumerable(ServiceDescriptor) 方法仅仅在还没有相同类型的实现时,才会注册一个服务。多个服务通过 在如下的示例中,第一行为 public interface IMyDep1 {} public interface IMyDep2 {} public class MyDep : IMyDep1, IMyDep2 {} services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>()); services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep2, MyDep>()); // Two registrations of MyDep for IMyDep1 is avoided by the following line: services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>()); 构造函数注入行为 可以通过两种方式来解析服务:
构造函数可以接受并非是DI提供的参数,但是必须为那些参数分配一个默认值。当服务通过 当服务通过 Entity Framework上下文 通常我们使用scoped生命周期来将实体框架上下文添加进服务容器中,这是因为wep app数据库操作通常对于每一次的客户端请求是scoped。当注册数据库上下文时,如果我们通过调用AddDbContext<TContext>重载而不指定生命周期的话,默认的生命周期便是scoped。给定生命周期的服务不应该使用比其生命周期更短的数据库上下文服务。 生命周期以及注册选项 为了演示生命周期以及注册选项的不同,考虑如下的一个接口,其将任务体现为带有唯一标识符的一个操作, public interface IOperation { Guid OperationId { get; } } public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationSingletonInstance : IOperation { } 这些接口在 public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance { public Operation() : this(Guid.NewGuid()) { } public Operation(Guid id) { OperationId = id; } public Guid OperationId { get; private set; } } 我们也注册了一个
public class OperationService { public OperationService( IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance instanceOperation) { TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = instanceOperation; } public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance { get; } } 在 public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddScoped<IMyDependency, MyDependency>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); // OperationService depends on each of the other Operation types. services.AddTransient<OperationService, OperationService>(); } 这个示例app演示了在一个请求之内以及多个请求之间对象的生命周期。示例app的IndexModel请求了各个类型的 public class IndexModel : PageModel { private readonly IMyDependency _myDependency; public IndexModel( IMyDependency myDependency, OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation) { _myDependency = myDependency; OperationService = operationService; TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = singletonInstanceOperation; } public OperationService OperationService { get; } public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; } public async Task OnGetAsync() { await _myDependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } } 以下输出显示了两个请求的结果:
第一次请求: Controller operations: Transient: d233e165-f417-469b-a866-1cf1935d2518 Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Instance: 00000000-0000-0000-0000-000000000000 OperationService operations: Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64 Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Instance: 00000000-0000-0000-0000-0000000000 第二次请求: Controller operations: Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0 Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Instance: 00000000-0000-0000-0000-000000000000 OperationService operations: Transient: c4cbacb8-36a2-436d-81c8-8c1b78808aaf Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Instance: 00000000-0000-0000-0000-000000000000 观察在一个请求和两次请求之间,哪个
从main函数中调用服务 使用IServiceScopeFactory.CreateScope 创建一个IServiceScope 来在app的域中解析一个scoped服务。这种方法可用来在startup中访问一个scoped服务,用以执行初始化任务。以下示例演示了如何在
using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; public class Program { public static async Task Main(string[] args) { var host = CreateHostBuilder(args).Build(); using (var serviceScope = host.Services.CreateScope()) { var services = serviceScope.ServiceProvider; try { var serviceContext = services.GetRequiredService<MyScopedService>(); // Use the context here } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "An error occurred."); } } await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } 作用域验证 当app运行于开发环境并调用CreateDefaultBuilder来创建宿主时,默认的服务器会执行一些检查以校验:
当BuildServiceProvider被调用时会创建根服务提供器。根服务提供器的生命周期对应于app的生命周期,它随着app的开始而开始,随着app的关闭而销毁。 scoped服务会被创建它们的服务所销毁。如果一个scoped服务在根容器中被创建,那么这个服务的生命周期便会提升到单例,这是因为只有当appg关闭时候它才会被根容器销毁。当 更多信息,请参考ASP.NET Core Web Host。 请求服务 在ASP.NET Core请求中可用的服务是通过HttpContext.RequestServices这个集合暴漏出来的。 请求服务代表着那些作为app的一部分被配置和请求的服务。当对象指定了一些依赖,它们会用在 通常来说,app不会直接使用这些属性。相反,会使用类的构造函数来请求这些类所需要的类型,并允许框架来注入这些依赖。这使得类更加容易进行测试。 注意:作为构造函数参数来请求依赖而不要直接访问 针对依赖注入设计服务 最佳实践是:
如果一个类似乎有太多的注入的依赖,这大体上可以说明这个类承担了太多的责任并违反了Single Responsibility Principle (SRP)。可以通过将其 一部分责任移到一个新的类中来重构它。请务必记住Razor Pages页面模型类以及MVC控制器类应该集中于UI概念。业务规则以及数据访问实现细节应该保持在另外一些合适的类中,他们是相互独立的概念。 服务的销毁 对于容器创建的IDisposable类型,它会调用Dispose方法。如果一个实例通过用户代码被添加进容器,那么它不会被自动销毁。
// Services that implement IDisposable: public class Service1 : IDisposable {} public class Service2 : IDisposable {} public class Service3 : IDisposable {} public interface ISomeService {} public class SomeServiceImplementation : ISomeService, IDisposable {} public void ConfigureServices(IServiceCollection services) { // The container creates the following instances and disposes them automatically: services.AddScoped<Service1>(); services.AddSingleton<Service2>(); services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation()); // The container doesn't create the following instances, so it doesn't dispose of // the instances automatically: services.AddSingleton<Service3>(new Service3()); services.AddSingleton(new Service3()); }
默认服务容器的替换 内置的服务容器被设计为服务于框架及大部分消费的app。我们推荐使用内置的服务容器,除非你需要一个特定的特性,而恰好内置的容器不支持它,比如:
下列第三方容器可以和ASP.NET Core app一起使用:
线程安全 创建线程安全的单例服务。如果一个单例服务依赖于一个transient服务,那么这个transient服务或许也需要线程安全,这取决于单例服务将如何使用它。 一个单独服务的工厂方法,比如AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>)方法的第二个参数,不需要是线程安全的。像是一个静态类型的构造方法,它保证被一个单独的线程调用一次。 一些建议
错误: public class MyClass() { public void MyMethod() { var optionsMonitor = _services.GetService<IOptionsMonitor<MyOptions>>(); var option = optionsMonitor.CurrentValue.Option; ... } } 正确: public class MyClass { private readonly IOptionsMonitor<MyOptions> _optionsMonitor; public MyClass(IOptionsMonitor<MyOptions> optionsMonitor) { _optionsMonitor = optionsMonitor; } public void MyMethod() { var option = _optionsMonitor.CurrentValue.Option; ... } }
像所有的推荐一样,你会遇到一些需要忽视一条推荐的情况,然而异常情况是比较少的--主要是框架本身内部的特殊情况。 DI是静态/全局访问模式的一种替换。如果你将它与静态访问对象混合起来使用,或许你会意识不到它所带来的益处。 其他资源
|
请发表评论