ASP.NET Core 依赖注入(DI)
ASP.NET Core的底层设计支持和使用依赖注入。ASP.NET Core 应用程序可以利用内置的框架服务将服务注入到启动类的方法中,并且应用程序服务也可以配置注入。由ASP.NET Core 提供的默认服务容器提供了最小功能集,并不是取代其他容器。
1.浅谈依赖注入
依赖注入(Dependency injection,DI)是一种实现对象和依赖者之间松耦合的技术,将类用来执行其操作的这些对象以注入的方式提供给该类,而不是直接实例化依赖项或者使用静态引用。一般情况,类会通过构造函数声明器2依赖关系,允许他们遵循显示依赖原则。这种方法称为“构造函数注入”。
当类的设计使用DI思想时,他们的耦合更加松散,因为他们没有对他们的合作者直接硬编码的依赖。这遵循“依赖倒置原则”,其中指出,高层模块不应该依赖于底层模块:两者都依赖于抽象。
类要求在他们构造时向其提供抽象(通常是接口),而不是引用特定的实现。提取接口的依赖关系和提供接口的实现作为参数也是“策略设计模式”的一个示例。
当一个类被用来创建类及其相关的依赖关系时,这个成为容器(containers),或者称为控制反转(Inversion of Control, IoC)容器,或者依赖注入容器。容器本质上是一个工厂,负责提供向它请求的类型的实例。如果一个给定类型声明它具有依赖关系,并且容器已经被配置为其提供依赖关系,那么它将把创建依赖关系作为创建请求实例的一部分。除了创建对象的依赖关系外,容器通常还会管理应用程序中对象的生命周期。
ASP.NET Core 包含一个默认支持构造函数注入的简单内置容器,ASP.NET 的容器指的是它管理的类型services,可以在Startup类的ConfigureServices方法中配置内置容器的服务。
2. 使用ASP.NET Core提供的服务
Startup类的ConfigureServices方法负责定义应用程序将使用的服务,包括平台自带的功能,比如,Entity Framework Core 和 ASP.NET Core MVC。除了IServiceCollection提供的几个服务之外,可以使用一些扩展方法(AddDbContext,AddMvc,AddTransient等)向容器添加和注册额外服务:
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext<AccessManagementContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), providerOptions => providerOptions.EnableRetryOnFailure())); services.AddTransient<ICompanyServices, CompanyServices>(); }
ASP.NET Core 提供的功能和中间件,遵循约定使用一个单一的AddService扩展方法来注册所有该功能所需的服务。
3.注册自己的服务
我们可以按照 services.AddTransient<ICompanyServices, CompanyServices>(); 这种写法注册自己的服务。第一个范型类型表示将要从容器中请求的类型(通常是一个接口)。第二个范型类型表示将由容器实例化并且用于完成请求的具体类型。
AddTransient 方法用于将抽象类型映射到为每一个需要它的对象分别实例化的具体服务。为注册的每一个服务选择合适的生命周期很重要,后面会介绍到。
下面是示例是注册自己的服务:
1.接口
public interface IAccountServices { Task<List<AccountViewModel>> GetList(); }
2.实现类
public class AccountServices:IAccountServices { AccessManagementContext _context; public AccountServices(AccessManagementContext context) { _context = context;//在构造函数中注入 } public async Task<List<Account>> GetList() { try { var query = _context.Account.ToListAsync(); return query ; } catch (Exception ex) { return null; } } }
3.在ConfigureServices中注册自定义的服务和EF上下文AccessManagementContext
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddDbContext<AccessManagementContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), providerOptions => providerOptions.EnableRetryOnFailure())); services.AddTransient<IAccountServices,AccountServices>(); }
4.在Controller构造函数中依赖注入
public class AccountController : Controller { private IAccountServices _accountServices; public AccountController(IAccountServices accountServices) { _accountServices = accountServices; } // GET: Account public async Task<ActionResult> Index() { var vms = await _accountServices.GetList(); return View(vms); }
4.服务的生命周期和注册选项
ASP.NET 服务生命周期:
1.Transient 瞬时
Transient 生命周期服务在他们每次请求时被创建。适合轻量级,无状态的服务。
2.Scoped 作用域
Scoped生命周期在每次请求时创建一次。
3.Singleton 单例
Singleton 生命周期服务在它们第一次请求时创建,并且每个后续请求使用相同的实例。
服务可以用多种方式在容器中注册,除了之前的注册方法,还可以指定一个工厂,它将被用来创建需要的实例。后面会详细介绍其他的注册方法。
下面用一个简单的示例介绍每个生命周期:
1.创建接口:
namespace MVCTest.Interfaces { public interface IOperation { /// <summary> /// 唯一标识 /// </summary> Guid OperationId { get; } } public interface IOperationTransient: IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationInstance : IOperation { } }
2.实现类
/// <summary> /// 实现所有接口 /// </summary> public class Operation: IOperation, IOperationTransient, IOperationScoped, IOperationSingleton, IOperationInstance { public Operation() { OperationId = Guid.NewGuid(); } public Operation(Guid operationId) { if (operationId == null) { OperationId = Guid.NewGuid(); } OperationId = operationId; } public Guid OperationId { get; } }
3.注册到容器
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationInstance, Operation>(); services.AddTransient<OperationServices, OperationServices>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
4.上面还注册了 OperationServices ,用来测试单例模式(单例生命周期服务中所有请求使用第一次实例化的服务)和 作用域生命周期服务在每次请求时只创建一次,不管几个地方用到实例
public class OperationServices { public IOperationTransient OperationTransient { get; } public IOperationScoped OperationScoped { get; } public IOperationSingleton OperationSingleton { get; } public IOperationInstance OperationInstance { get; } public OperationServices(IOperationTransient operationTransient, IOperationScoped operationScoped, IOperationSingleton operationSingleton, IOperationInstance operationInstance) { OperationTransient = operationTransient; OperationScoped = operationScoped; OperationSingleton = operationSingleton; OperationInstance = operationInstance; } }
5.在Controller中使用
public class OperationController : Controller { public IOperationTransient OperationTransient { get; } public IOperationScoped OperationScoped { get; } public IOperationSingleton OperationSingleton { get; } public IOperationInstance OperationInstance { get; } public OperationServices _operationServices; public OperationController(IOperationTransient operationTransient, IOperationScoped operationScoped, IOperationSingleton operationSingleton, IOperationInstance operationInstance, OperationServices operationServices) { OperationTransient = operationTransient; OperationScoped = operationScoped; OperationSingleton = operationSingleton; OperationInstance = operationInstance; _operationServices = operationServices; } // GET: Operation public ActionResult Index() { ViewBag.OperationTransient = OperationTransient; ViewBag.OperationScoped = OperationScoped; ViewBag.OperationSingleton = OperationSingleton; ViewBag.OperationInstance = OperationInstance; ViewBag._operationServices = _operationServices; return View(); } }
6.Index显示
@{ ViewData["Title"] = "Index"; } <div> <h1>Controller Operations</h1> <h2>OperationTransient: @ViewBag.OperationTransient.OperationId</h2> <h2>OperationScoped: @ViewBag.OperationScoped.OperationId</h2> <h2>OperationSingleton: @ViewBag.OperationSingleton.OperationId</h2> <h2>OperationInstance: @ViewBag.OperationInstance.OperationId</h2> </div> <div> <h1>Services Operations</h1> <h2>OperationTransient: @ViewBag._operationServices.OperationTransient.OperationId</h2> <h2>OperationScoped: @ViewBag._operationServices.OperationScoped.OperationId</h2> <h2>OperationSingleton: @ViewBag._operationServices.OperationSingleton.OperationId</h2> <h2>OperationInstance: @ViewBag._operationServices.OperationInstance.OperationId</h2> </div>
7.运行结果
可以看到,单例生命周期服务每一次请求的标识一样。作用域生命周期的服务,在一次请求中使用的同一个实例,第二次请求创建新的实例。
5.请求服务
来自HttpContext的一次ASP.NET 请求中,可用的服务是通过RequestServices集合公开的。
请求服务将你配置的服务和请求描述为应用程序的一部分。在子的对象指定依赖之后,这些满足要求的对象可通过查找RequestServices中对应的类型得到,而不是ApplicationServices。
6.设计依赖注入服务
在自定义的服务中,避免使用静态方法和直接实例化依赖的类型,而是通过依赖注入请求它。(New is Glue)
如果类有太多的依赖关系被注入时,通常表明你的类试图做的太多(违反了单一职责原则),需要转移一些职责。
同样,Controller类应该重点关注UI,因此业务逻辑和数据访问等细节应该在其他类中。
7.使用Autofac容器