ASP.NET Core 依赖注入最佳实践与技巧

ASP.NET Core 依赖注入最佳实践与技巧

原文地址:https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96 [正(ke)确(xue)上(shang)网(wang)]
posted by Halil İbrahim Kalkan Jul 12, 2018 · 7 min read

在这篇文章中,我将分享一下在ASP.NET Core应用程序中使用依赖注入的经验与建议。
主要分享的目的,基于以下几点原则:

有效的设计服务及它们的依赖关系

预防多线程问题

预防内存移除

预防潜在bugs

这篇文章的前提假设你已经对依赖注入和ASP.NET Core由基本的认识,如果还没有,首先请阅读ASP.NET Core Dependency Injection documentation。

基础 构造函数注入

构造函数注入(Constructor injection)用于声明和获取服务对服务构造的依赖关系。

public class ProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public void Delete(int id) { _productRepository.Delete(id); } }

ProductService在构造函数中注入了它的依赖IProductRepository,然后使用了它的Delete方法。

良好实践

在服务构造函数中显式定义所需的依赖项。这样,服务缺失依赖关系就不能构造。

将注入的依赖项赋值给一个只读(read only)字段/属性(防止在方法调用过程中无意的赋值了其他值)。

属性注入

ASP.NET Core的标配的依赖注入容器并不支持属性注入(property injection)。但是你可以。

using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace MyApp { public class ProductService { public ILogger<ProductService> Logger { get; set; } private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; Logger = NullLogger<ProductService>.Instance; } public void Delete(int id) { _productRepository.Delete(id); Logger.LogInformation( $"Deleted a product with id = {id}"); } } }

ProductService声明了一个开放了Setter的日志(Logger)属性。依赖注入容器能赋值一个可用的值给这个日志属性(前提是已经在依赖注入容器内注册过)。

良好实践

仅对可选依赖项使用属性注入。这意味着你的服务可以在不提供这些依赖项的情况下正常工作。

尽量使用空对象模式(如实例所示)。否则,在使用依赖项时始终做NULL检查。

服务定位(Service Locator)

服务定位(Service Locator)模式是另一种获取依赖项的方式。

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,并使用它解析了ProdProductServiService的依赖关系。如果在使用之前注入容器的话,使用GetRequiredService方法会抛异常。另一边,使用GetService则返回NULL。

当你在构造函数中解析(resolve)依赖服务时,他们随着服务本身的释放而释放,所以你大可不必关系构造函数注入的依赖项的释放(就像构造函数和属性注入一样)。

良好实践

尽可能不要使用服务定位(Service Locator)模式。因为这样使得服务的依赖关系隐式化(译注,++服务的依赖关系不是显示的注入,导致代码层面的服务依赖关系不明确,从构造函数看,只有一个IServiceProvider的依赖++)。这意味着在创建服务实例时不能显示的看到服务的依赖项。而这对于单元测试尤其重要,因为你可能想要模拟服务的一些依赖项。

尽可能使用构造函数解析服务依赖项。在服务方法中解析依赖项会让应用程序变得更复杂,更容易出错。接下来,我将介绍这些问题和解决方案。

服务生命周期

在ASP.NET Core依赖注入概念里面,有:

Transient服务,在请求或注入服务的时候,每次都创建新实例。

Scoped服务,在作用域内创建服务。在Web应用程序,每一个web请求都会创建一个新的独立的服务作用域范围。这意味着每个web请求通常都创建有作用域的服务

Singleton服务,每个依赖注入容器会创建一次单例服务。在每个应用程序只会创建一次单例服务,在应用的整个生命周期都可用。

依赖注入容器会跟踪所有解析出来的服务,在它们的生命周期结束后会释放掉这些服务。

如果服务有依赖项,这些依赖项也会自动释放。

如果服务已经实现了IDisposable接口,在服务被释放的时候也会自动调用Dispose方法。

良好实践

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxdxy.html