如果你不熟悉ASP.NET Core依赖注入,先阅读文章: 在ASP.NET Core中使用依赖注入
构造函数注入常用于在服务构建上定义和获取服务依赖。例如:
public class ProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public void Delete(int id) { _productRepository.Delete(id); } }
ProductService 将 IProductRepository作为依赖注入到它的构造函数,然后在 Delete 方法内部使用这个依赖。
ASP.NET Core 的标准依赖注入容器不支持属性注入。但是你可以使用其他容器支持属性注入。例如:
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace MyApp { public class ProductService { public ILoggerLogger { get; set; } private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; Logger = NullLogger.Instance; } public void Delete(int id) { _productRepository.Delete(id); Logger.LogInformation( $"Deleted a product with id = {id}"); } } }
ProductService 定义了一个带公开setter的Logger 属性。
依赖注入容器可以设置 Logger属性,如果它可用(已经注册到DI容器)。
服务定位器模式是获取依赖关系的另外一种方式。例如:
public class ProductService { private readonly IProductRepository _productRepository; private readonly ILogger_logger; public ProductService(IServiceProvider serviceProvider) { _productRepository = serviceProvider .GetRequiredService (); _logger = serviceProvider .GetService >() ?? NullLogger.Instance; } public void Delete(int id) { _productRepository.Delete(id); _logger.LogInformation($"Deleted a product with id = {id}"); } }
ProductService 注入了 IServiceProvider 来解析并使用依赖。 如果请求的依赖之前没有被注册,那么GetRequiredService将会抛出异常。换句话说, 这种情况下,GetService只会返回null。
当你在构造函数内部解析服务时,它们会随着服务的释放而释放。因此,你不必关心构造函数内部已解析服务的释放问题(就像构造函数注入和属性注入)。
下面是服务在ASP.NET Core依赖注入中的生命周期:
DI容器会持续跟踪所有已经被解析的服务。当服务的生命周期终止时,它们会被释放并销毁:
在某些情况下,你可能需要在你的服务的某个方法中解析另一个服务。 这种情况下,请确保在使用后释放该服务。保障这个的最好方法是创建一个服务作用域。例如:
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_cache; public FileService() { _cache = new ConcurrentDictionary (); } public byte[] GetFileContent(string filePath) { return _cache.GetOrAdd(filePath, _ => { return File.ReadAllBytes(filePath); }); } }
FileService简单地缓存了文件内容以减少磁盘读取。这个服务应该被注册为一个单例,否则,缓存将无法按照预期工作。
Scoped 生命周期的服务看起来是一个不错的存储每个Web请求数据的好方法。因为ASP.NET Core为每个Web请求创建一个服务作用域。因此,如果你把一个服务注册为Scoped,那么它可以在一个Web请求期间被共享。例如:
public class RequestItemsService { private readonly Dictionary_items; public RequestItemsService() { _items = new Dictionary (); } public void Set(string name, object value) { _items[name] = value; } public object Get(string name) { return _items[name]; } }
如果你将RequestItemsService注册为Scoped,并注入到两个不同的服务,然后你可以得到一个从另外一个服务添加的项。因为它们会共享同一个RequestItemsService的实例。这就是我们对 Scoped服务的预期。
但是!!!事实并不总是如此。 如果你创建了一个子服务作用域并从子作用域解析RequestItemsService,然后你会得到一个RequestItemsService的新实例,并且不会按照你的预期工作。因此,Scoped服务并不总是意味着每个Web请求一个实例。
你可能认为你不会犯如此明显的错误(在子作用域内部解析另一个作用域)。但是,这并不是一个错误(一个很常规的用法)并且情况可能不会如此简单。如果你的服务之间有一个大的依赖关系,你不知道是否有人创建了子作用域并在其他注入的服务中解析了服务……最终注入了一个Scoped服务。
依赖注入刚开始看起来很容易使用,但是如果你不遵循一些严格的原则,则会有潜在的多线程问题和内存泄漏问题。我分享的这些实践指南基于我在开发ABP框架期间的个人经验。