标签:处理 binder auto each read ack 默认 而不是 list
这篇文章主要讲解asp.net core 依赖注入的一些内容。
ASP.NET Core支持依赖注入。这是一种在类和其依赖之间实现控制反转的一种技术(IOC).
1.原始的代码
依赖就是一个对象的创建需要另一个对象。下面的MyDependency是应用中其他类需要的依赖:
public class MyDependency { public MyDependency() { } public Task WriteMessage(string message) { Console.WriteLine( $"MyDependency.WriteMessage called. Message: {message}"); return Task.FromResult(0); } }
一个MyDependency类被创建使WriteMessage方法对另一个类可用。MyDependency类是IndexModel类的依赖(即IndexModel类的创建需要用到MyDependency类):
public class IndexModel : PageModel { MyDependency _dependency = new MyDependency(); public async Task OnGetAsync() { await _dependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } }
2.原始代码分析
IndexModel类创建了MyDependency类,并且直接依赖MyDependency实例。上面的代码依赖是有问题的,并且应该被避免(避免直接创建依赖的实例对象),
原因如下:
依赖注入解决那些问题:
3.下面是改良后的代码
这示例应用中,IMyDependency接口定义了一个方法:
public interface IMyDependency { Task WriteMessage(string message); }
接口被一个具体的类型,MyDependency实现:
public class MyDependency : IMyDependency { private readonly ILogger<MyDependency> _logger; public MyDependency(ILogger<MyDependency> logger) { _logger = logger; } public Task WriteMessage(string message) { _logger.LogInformation( "MyDependency.WriteMessage called. Message: {MESSAGE}", message); return Task.FromResult(0); } }
在示例中,IMydependency实例被请求和用于调用服务的WriteMessage方法:
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."); } }
4.改良代码分析及扩展讲解(使用DI)
MyDependency在构造函数中,要求有一个ILogger<TCategoryName>。用一种链式的方法使用依赖注入是很常见的。每个依赖依次再请求它自己需要的依赖。(即:MyDependency是一个依赖,同时,创建MyDependency又需要其他依赖:ILogger<TCategoryName>。)
IMyDependency和ILogger<TCategoryName>必须在service container中注册。IMyDependency是在Startup.ConfigureServices中注册。ILogger<TCategoryName>是被logging abstractions infrastructure注册,所以它是一种默认已经注册的框架提供的服务。(即框架自带的已经注册的服务,不需要再另外注册)
容器解析ILogger<TCategoryName>,通过利用泛型. 消除注册每一种具体的构造类型的需要。(因为在上面的例子中,ILogger中的泛型类型为MyDependency,但是如果在其他类中使用ILogger<>, 类型则是其他类型,这里使用泛型比较方便)
services.AddSingleton(typeof(ILogger<T>), typeof(Logger<T>));
这是它的注册的语句(框架实现的),其中的用到泛型,而不是一种具体的类型。
在示例应用中,IMyDependency service是用具体的类型MyDependency来注册的。这个注册包括服务的生命周期(service lifetime)。Service lifetimes随后会讲。
如果服务的构造函数要求一个内置类型,像string,这个类型可以被使用configuration 或者options pattern来注入:
public class MyDependency : IMyDependency
{
public MyDependency(IConfiguration config)
{
var myStringValue = config["MyStringKey"];
// Use myStringValue
}
...
}
或者 options pattern(注意:不止这些,这里简单举例)
Startup.ConfigureServices方法有责任定义应用使用的服务,包括平台功能,例如Entity Framework Core和ASP.NET Core MVC。最初,IServiceColletion提供给ConfigureServices下面已经定义的服务(依赖于怎样配置host):
当一个service colletion 扩展方法可以用来注册一个服务,习惯是用一个单独的Add{SERVICE_NAME} 扩展方法来注册服务所需要的所有服务。下面的代码是一个怎么使用扩展方法AddDbContext, AddIdentity,和AddMvc, 添加额外的服务到container:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); }
更多的信息:ServiceCollection Class
为每个注册的服务选择一个合适的生命周期。ASP.NET Core服务可以用下面的声明周期配置:
Transient、Scoped、Singleton
Transient(临时的)
临时的生命周期服务是在每次从服务容器中被请求时被创建。这个生命周期对于lightweight(轻量的),stateless(无状态的)服务比较合适。
Scoped(范围)
范围生命周期被创建,一旦每个客户端请求时(connection)
警告:当在中间件中使用范围服务时,注入服务到Invoke或者InvokeAsync方法。不要通过构造函数注入,因为那回强制服务表现的像是singleton(单例)。
Singleton(单独)
单独生命周期在第一次请求时被创建(或者说当ConfigureService运行并且被service registration指定时)。之后每一个请求都使用同一个实例。如果应用要求一个单独行为(singleton behavior),允许service container来管理服务生命周期是被推荐的。不要实现一个单例设计模式并且在类中提供用户代码来管理这个对象的生命周期。
警告:从一个singleton来解析一个范围服务(scoped service)是危险的。它可能会造成服务有不正确的状态,当处理随后的请求时。
服务可以被通过两种机制解析:
构造函数可以接受参数,不通过依赖注入提供,但是这些参数必须指定默认值。
当服务被通过IServiceProvider或者ActivatorUtilities解析时,构造函数注入要求一个公共的构造函数。
当服务被ActivatorUtilities解析时,构造函数注入要求一个合适的构造函数存在。构造函数的重载是被支持的,但是只有一个重载可以存在,它的参数可以被依赖注入执行(即:可以被依赖注入执行的,只有一个构造函数的重载)。
Entity Framework contexts 通常使用scoped lifetime ,添加到服务容器中(service container).因为web 应用数据库操作的范围适用于client request(客户端请求)。默认的生命周期是scoped,如果一个生命周期没有被AddDbContext<TContext>重载指定,当注册database context时。给出生命周期的服务不应该使用一个生命周期比服务的生命周期短的database context.
为了说明lifetime和registration options之间的不同,考虑下面的接口:这些接口表示的任务都是带有唯一标识的操作。取决于这些接口的操作服务的生命周期怎么配置,container提供了要么是同一个要么是不同的服务当被一个类请求时:
public interface IOperation { Guid OperationId { get; } } public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationSingletonInstance : IOperation { }
这些接口在一个Operation类中被实现。Operation 构造函数生成了一个GUID,如果GUID没被提供:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance { public Operation() : this(Guid.NewGuid()) { } public Operation(Guid id) { OperationId = id; } public Guid OperationId { get; private set; } }
OperationService依赖于其他的Operation 类型被注册。当OperationService被通过依赖注入请求,它要么接收每个服务的一个新实例要么接收一个已经存在的实例(在依赖服务的生命周期的基础上)。
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 SingletonInstanceOperation { get; } }
在Startup.ConfigureServices中,每个类型根据命名的生命周期被添加到容器中:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 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>(); }
IOperationSingletonInstance服务是一个特殊的实例,它的ID是Guid.Empty. 它是清楚的,当这个类型被使用(它的GUID都是0组成的)
示例应用说明了requests内的对象生命周期和两个requests之间的对象生命周期。示例应用的IndexModel请求IOperation的每个类型和OperationService。这个页面展示了所有的这个page model类的和服务的OperationId值,通过属性指定。
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."); } }
下面的输出展示了两个请求的结果:
从结果看出:
可以看出,Transient一直在变;Scoped 同一个client request请求内不变;Singleton一直不变;
用IServiceScopeFactory.CreateScope创建一个IServiceScope 来解析一个scoped service在应用的范围内。这个方式是有用的对于在Startup中得到一个scoped service 来运行初始化任务。下面的例子展示了MyScopedServcie怎样包含一个context,在Program.Main中:
public static void Main(string[] args) { var host = CreateWebHostBuilder(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."); } } host.Run(); }
当应用在开发环境运行时,默认的service provider 执行检查来验证:
root service provider 是当BuildServiceProvider被调用时被创建的。Root service provider的生命周期对应于应用/服务器 的生命周期,当provider随着应用启动并且当应用关闭时会被释放。
Scoped服务被创建它们的容器释放。如果scoped service在root container中被创建,服务的生命周期实际上是被提升为singleton,因为它只有当应用或者服务器关闭时才会被root container释放。验证servcie scopes 注意这些场景,当BuildServiceProvider被调用时。
来自HttpContext的ASP.NET Core request中的可用的services通过HttpContext.RequestServices集合来暴露。
Request Services代表应用中被配置的services和被请求的部分。当对象指定依赖,会被RequestService中的类型满足,而不是ApplicationServices中的。
通常,应用不应该直接使用那些属性。相反的,请求满足那个类型的的这些类,可以通过构造函数并且允许框架注入这些依赖。这使类更容易测试。
注意:请求依赖,通过构造函数参数来得到RequestServices集合更受欢迎。
最佳实践:
如果一个类似乎有很多注入的依赖,这通常是它有太多职责的信号,并且违反了Single Responsibility Principle(SRP)单一职责原则。尝试通过移动一些职责到一个新类来重构这个类。记住,Razor Pages page model classes和MVC controller classes应该专注于UI层面。Business rules和data access implementation细节应该在那些合适的分开的关系的类中。
容器为它创建的类调用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()); }
即,如果,类是被用户代码添加容器中的,不会自动释放。像下面这种直接new类的。
内置的service container意味着提供服务来满足框架和大多消费应用的需求。我们建议使用功能内置容器,除非你需要一个特殊的功能,内置容器不支持。有些功能在第三方容器支持,但是内置容器不支持:
下面的示例,使用Autofac替代内置容器:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // Add other framework services // Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
要使用第三方容器,Startup.ConfigureServices必须返回IServiceProvider.
public class DefaultModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<CharacterRepository>().As<ICharacterRepository>(); } }
在运行时,Autofac被用来解析类型和注入依赖。
创建线程安全的单例服务。如果一个单例服务对一个临时的服务有依赖,这个临时的服务可能需要要求线程安全根据它怎样被单例服务使用。
单例服务的工厂方法,例如AddSingleton<TService>(IServiceColletion, Func<IServiceProvider, TService>)的第二个参数,不需要线程安全。像一个类型的构造函数,它一次只能被一个线程调用。
错误的:
public void MyMethod() { var options = _services.GetService<IOptionsMonitor<MyOptions>>(); var option = options.CurrentValue.Option; ... }
正确的:
private readonly MyOptions _options; public MyClass(IOptionsMonitor<MyOptions> options) { _options = options.CurrentValue; } public void MyMethod() { var option = _options.Option; ... }
有时候的场景,可能需要忽略其中的建议。
DI是static/global object access patterns的可替代方式。如果你把它和static object access 方式混合使用,可能不能认识到DI的好处。
参考网址:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2
asp.net core 系列之Dependency injection(依赖注入)
标签:处理 binder auto each read ack 默认 而不是 list
原文地址:https://www.cnblogs.com/Vincent-yuan/p/11145717.html