标签:repos div alc 作用域 pip lock wait rman 并行
在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用中使用依赖注入的一些经验和建议,并且将会讨论这些原则背后的动机是什么:
(1)有效地设计服务及其依赖关系。
(2)防止多线程问题。
(3)防止内存泄漏。
(4)防止潜在的错误。
在讨论该话题之前,了解什么是服务是生命周期至关重要,当组件通过依赖注入请求另一个组件时,它接收的实例是否对该组件实例是唯一的取决于生命周期。 因此,设置生存期决定了组件实例化的次数以及组件是否共享。
在ASP.Net Core 依赖注入有三种:
DI容器跟踪所有已解析的组件, 组件在其生命周期结束时被释放和处理:
重要的是要理解,如果将组件A注册为单例,则它不能依赖于使用Scoped或Transient生命周期注册的组件。更一般地说:
服务不能依赖于生命周期小于其自身的服务。
通常你希望将应用范围的配置注册为单例,数据库访问类,比如Entity Framework上下文被推荐以Scoped方式注入,以便可以重用连接。如果要并行运行的话,请记住Entity Framework上下文不能由两个线程共享,如果需要,最好将上下文注册为Transient,然后每个服务都获得自己的上下文实例,并且可以并行运行。
建议的做法:
尽可能将您的服务注册为瞬态服务。 因为设计瞬态服务很简单。 您通常不用关心多线程和内存泄漏,并且您知道该服务的寿命很短。
1、请谨慎使用Scoped,因为如果您创建子服务作用域或从非Web应用程序使用这些服务,则可能会非常棘手。
2、谨慎使用singleton ,因为您需要处理多线程和潜在的内存泄漏问题。
3、在singleton 服务中不要依赖transient 或者scoped 服务,因为如果当一个singleton 服务注入transient服务,这个 transient服务就会变成一个singleton服务,并且如果transient服务不是为支持这种情况而设计的,则可能导致问题。 在这种情况下,ASP.NET Core的默认DI容器已经抛出异常。
注册服务是ConfigureServices(IServiceCollection)
在您Startup
班级的方法中完成的。
以下是服务注册的示例:
services.Add(new ServiceDescriptor(typeof(IDataService), typeof(DataService), ServiceLifetime.Transient));
该行代码添加DataService
到服务集合中。服务类型设置为IDataService
如此,如果请求该类型的实例,则它们将获得实例DataService
。生命周期也设置为Transient,因此每次都会创建一个新实例。
ASP.NET Core提供了各种扩展方法,方便服务的注册,一下是最常用的方式,也是比较推荐的做法:
services.AddTransient<IDataService, DataService>();
简单吧,对于不同的生命周期,有类似的扩展方法,你可以猜测它们的名称。如果需要,你还可以注册单一类型(实现类型=服务类型)
services.AddTransient<DataService>();
services.AddTransient<DataService, DataService>();
在某些特殊情况下,您可能希望接管某些服务的实例化过程。在这种情况下,您可以使用下面的方法。例子:
services.AddTransient<IDataService, DataService>((ctx) => { IOtherService svc = ctx.GetService<IOtherService>(); //IOtherService svc = ctx.GetRequiredService<IOtherService>(); return new DataService(svc); });
单例组件的注入,可以这样做:
services.AddSingleton<IDataService>(new DataService());
有一个非常有意思的场景,DataService
实现两个接口,如果我们这样做:
验证结果:
我们将会得到两个实例,如果我们想共享一个实例,可以这样做:
验证结果:
如果组件具有依赖项,则可以从服务集合构建服务提供程序并从中获取必要的依赖项:
IServiceProvider provider = services.BuildServiceProvider(); IOtherService otherService = provider.GetRequiredService<IOtherService>(); var dataService = new DataService(otherService); services.AddSingleton<IDataService>(dataService); services.AddSingleton<ISomeInterface>(dataService);
但我们一般不会这样使用,也不建议这样使用。
现在我们已经注册了我们的组件,我们可以转向实际使用它们,如下:
构造函数注入用于在服务构造上声明和获取服务的依赖关系。 例如:
public class ProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public void Delete(int id) { _productRepository.Delete(id); } }
ProductService在其构造函数中将IProductRepository注入为依赖项,然后在Delete方法中使用它。
建议的做法:
服务定位器是另外一种获取依赖项的模式,例如:
public class ProductService { private readonly IProductRepository _productRepository; private readonly ILogger<ProductService> _logger; public ProductService(IServiceProvider serviceProvider) { _productRepository = serviceProvider .GetRequiredService<IProductRepository>(); _logger = serviceProvider .GetService<ILogger<ProductService>>() ?? NullLogger<ProductService>.Instance; } public void Delete(int id) { _productRepository.Delete(id); _logger.LogInformation($"Deleted a product with id = {id}"); } }
ProductService 注入了IServiceProvider ,并且使用它获取依赖项。如果你在使用某个依赖项之前没有注入,GetRequiredService 方法将会抛异常,相反GetService 会返回null。
解析构造函数中的服务时,将在释放服务时释放它们,所以,你不用关心释放/处理在构造函数中解析的服务(就像构造函数和属性注入一样)。
建议的做法:
(1)尽可能不使用服务定位器模式,因为该模式存在隐含的依赖关系,这意味着在创建服务实例时无法轻松查看依赖关系,但是该模式对单元测试尤为重要。
(2)如果可能,解析服务构造函数中的依赖项。 解析服务方法会使您的应用程序更加复杂且容易出错。 我将在下一节中介绍问题和解决方案。
再看一个综合的例子:
public class LoggingMiddleware { private readonly RequestDelegate _next; public LoggingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { Debug.WriteLine("Request starting"); await _next(ctx); Debug.WriteLine("Request complete"); } }
在中间件中注入组件有三种不同的方法:
1、构造函数
2、调用参数
3、HttpContext.RequestServices
让我们看看这三种方式注入的使用:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace WebAppPerformance { // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project public class LoggingMiddleware { private readonly RequestDelegate _next; private readonly IDataService _svc; public LoggingMiddleware(RequestDelegate next, IDataService svc) { _next = next; _svc = svc; } public async Task Invoke(HttpContext httpContext, IDataService svc2) { IDataService svc3 = httpContext.RequestServices.GetService<IDataService>(); Debug.WriteLine("Request starting"); await _next(httpContext); Debug.WriteLine("Request complete"); } } // Extension method used to add the middleware to the HTTP request pipeline. public static class LoggingMiddlewareExtensions { public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<LoggingMiddleware>(); } } }
中间件在应用程序生命周期中仅实例化一次,因此通过构造函数注入的组件对于所有通过的请求都是相同的。如果IDataService被注册为singleton,我们会在所有这些实例中获得相同的实例。
如果被注册为scoped,svc2
并且svc3
将是同一个实例,但不同的请求会获得不同的实例;如果在Transient 的情况下,它们都是不同的实例。
注意:我会尽量避免使用RequestServices
,只有在中间件中才使用它。
MVC过滤器中注入:
但是,我们不能像往常一样在控制器上添加属性,因为它必须在运行时获得依赖关系。
我们有两个选项可以在控制器或action级别添加它:
[TypeFilter(typeof(TestActionFilter))] public class HomeController : Controller { } // or [ServiceFilter(typeof(TestActionFilter))] public class HomeController : Controller { }
关键的区别在于,TypeFilterAttribute
将确定过滤器依赖性是什么,通过DI获取它们,并创建过滤器。ServiceFilterAttribute
试图从服务集合中找到过滤器!
为了[ServiceFilter(typeof(TestActionFilter))]
工作,我们需要更多配置:
public void ConfigureServices(IServiceCollection services) { services.AddTransient<TestActionFilter>(); }
现在ServiceFilterAttribute
可以找到过滤器了。
如果要全局添加过滤器:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(mvc => { mvc.Filters.Add(typeof(TestActionFilter)); }); }
这次不需要将过滤器添加到服务集合中,就像TypeFilterAttribute
在每个控制器上添加了一个过滤器一样。
在方法体内解析服务
在某些情况下,您可能需要在方法中解析其他服务。在这种情况下,请确保在使用后释放服务。确保这一点的最佳方法是创建scoped服务,例如:
public class PriceCalculator { private readonly IServiceProvider _serviceProvider; public PriceCalculator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public float Calculate(Product product, int count, Type taxStrategyServiceType) { using (var scope = _serviceProvider.CreateScope()) { var taxStrategy = (ITaxStrategy)scope.ServiceProvider .GetRequiredService(taxStrategyServiceType); var price = product.Price * count; return price + taxStrategy.CalculateTax(price); } } }
PriceCalculator 在其构造函数中注入IServiceProvider并将其分配给字段。然后,PriceCalculator在Calculate方法中使用它来创建子组件范围。它使用scope.ServiceProvider来解析服务,而不是注入的_serviceProvider实例。因此,从范围中解析的所有服务都将在using语句的末尾自动释放/处理。
建议的做法:
单例服务
单例服务通常用来保存应用程序的状态,缓存是应用程序状态的一个很好的例子,例如:
public class FileService { private readonly ConcurrentDictionary <string,byte []> _cache; public FileService() {_ cache = new ConcurrentDictionary <string,byte []>(); } public byte [] GetFileContent(string filePath) { return _cache.GetOrAdd(filePath,_ => { return File.ReadAllBytes(filePath); }); } }
FileService只是缓存文件内容以减少磁盘读取。此服务应注册为singleton。否则,缓存将无法按预期工作。
建议的做法:
Scoped生命周期首先似乎是存储每个Web请求数据的良好候选者。 因为ASP.NET Core会为每个Web请求创建一个服务范围【同一个http请求会在同一个域内】。 因此,如果您将服务注册为Scoped,则可以在Web请求期间共享该服务。 例:
public class RequestItemsService { private readonly Dictionary<string, object> _items; public RequestItemsService() { _items = new Dictionary<string, object>(); } public void Set(string name, object value) { _items[name] = value; } public object Get(string name) { return _items[name]; } }
如果你以scoped注入RequestItemsService 并将其注入到两个不同的服务中去,那么你可以从另外一个服务中获取添加的项,因为它们将共享相同的RequestItemsService实例,这也是我们所期望看到的。但是事实并不是我们想象的那样。如果你创建一个子域,并从子域中获取RequestItemsService ,那么你将会获取一个新的RequestItemsService 实例,并且这个新的实例并不会像你期望的那样工作。所以,scoped服务并不总是表示每个Web请求的实例。你可能认为自己不会出现这样的错误,但是,你并不能保证别人不会创建子域,并从中解析服务。
建议的做法:
依赖注入起初看起来很简单,但是如果你不遵循一些严格的原则,就会存在潜在的多线程和内存泄漏问题。如果有理解和翻译不对的地方,还请指出来。到底服务以哪种方式注册,还是要看具体的场景和业务需求,上面是一些建议,能遵守上面的建议,会避免一些不必要的问题。可能有些地方理解的还不是很深刻,只要在编码时有这种意识就非常好了,这也是我写这篇博客的原因。好了,就聊到这里,后面还会探讨ASP.Net Core MVC配置相关的源码,依赖注入是.Net Core中的核心,如果对依赖注入基础知识还不太明白的话,可以参考老A和腾飞两位大佬的博客:
https://www.cnblogs.com/artech/p/dependency-injection-in-asp-net-core.html
https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html
参考文章:
https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96
https://joonasw.net/view/aspnet-core-di-deep-dive
作者:郭峥
出处:http://www.cnblogs.com/runningsmallguo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
标签:repos div alc 作用域 pip lock wait rman 并行
原文地址:https://www.cnblogs.com/runningsmallguo/p/10234307.html