Asp.net core 2.0 官网搬运系列-依赖注入(DI)

1. 什么是控制反转(IoC)和依赖注入(DI)?

使用过Java Spring或者Angular的同学肯定不陌生,不知道的建议google一下。

Martin Fowler has written an extensive article on Inversion of Control Containers and the Dependency Injection Pattern. Microsoft Patterns and Practices also has a great description of Dependency Injection.

2. 使用构造函数的DI

  • 构造函数必须是public
  • 只能有一个构造函数用来进行所有的服务注册
  • 构造函数可以接受没有DI的参数,但此时参数必须有默认值

3. core2.0 框架内置的服务:

Startup类中的ConfigureService方法定义应用中所有的服务。public void ConfigureServices(IServiceCollection services)

在初始时,IServiceCollection提供了一些内置服务:

Service Type Lifetime
Microsoft.AspNetCore.Hosting.IHostingEnvironment Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.Logging.ILogger Singleton
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient
Microsoft.AspNetCore.Http.IHttpContextFactory Transient
Microsoft.Extensions.Options.IOptions Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions Transient
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IApplicationLifetime Singleton

我们可以添加额外的服务:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity()
        .AddEntityFrameworkStores()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient();
    services.AddTransient();
}

ASP.NET中间件,如MVC等,使用“约定大于配置”原则,即AddServiceName的方式注册服务,如 AddMvc(),AddDbContext()

4. 注册自己的服务

services.AddTransient();
services.AddTransient();

第一个泛型参数是在构造函数中使用的类,一般情况下是一个接口;第二个参数是实例化出来的类,一般情况下是接口实现类。

AddTransient代表实例化类的生命周期:

  • Transient:服务在每次请求时被实例化,这种情况最适用于轻量的、无状态的服务
  • Scoped:服务在相同请求调用时被实例化一次
  • Singleton:单例类,整个应用生命周期中只实例化一次。

为了解释清楚上述概念,我们来看一个例子:

  1. 首先声明服务接口:
using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }
    public interface IOperationScoped : IOperation
    {
    }
    public interface IOperationSingleton : IOperation
    {
    }
    public interface IOperationSingletonInstance : IOperation
    {
    }
}
  1. 接口实现类Opreation,构造函数接受一个Guid,若没有提供则使用new Guid:
public class Operation
{
    public Guid _guid {get;private set;}
    public Operation(Guid guid)
    {
        _guid = guid==null?Guid.Empty:guid;
    }
}
  1. ConfigureServices中注册服务:
    services.AddScoped();
    services.AddTransient();
    services.AddScoped();
    services.AddSingleton();
    services.AddSingleton(new Operation(Guid.Empty));
    services.AddTransient();

额外注意上面的IOperationSingletonInstance, 它直接使用了一个空的GUID(全是0)实例化了Operation类,并将这个类进行注册。
同时,还要注意OperationService, 这个类如下:

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}

这个类依赖于所有的接口,目的是为了看清楚究竟每个服务的实例化状态

  1. 控制器:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // viewbag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;
            
            // operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}
  1. 视图(略)

  2. 结果:


    Asp.net core 2.0 官网搬运系列-依赖注入(DI)_第1张图片
    第一次请求

    Asp.net core 2.0 官网搬运系列-依赖注入(DI)_第2张图片
    第二次请求
  • 无论如何,Transient 每次都实例化新的(every controller and every service.)
  • Scoped:一次请求中总相同,不同请求中不同
  • singleton: 总是相同(不管你有没有在ConfigureServices中注册实例),也就是说,前面的
services.AddSingleton();
    services.AddSingleton(new Operation(Guid.Empty));

总会得到相同结果

5. Request Services

HttpContext暴露RequestServices,即所有的服务集合:

this.HttpContext.RequestServices.

但是,不主张用这种方式使用服务,而是应该使用DI的方法以实现解耦。

6. 服务设计原则

  • 建议先看看 S.O.L.I.D

  • 遵守SRP:the Single Responsibility Principle.单一职责原则

  • 比如操作数据库时,在controller中注入DbContext进行直接操作是可以的。但是使用repository层对数据库代码进行统一归放往往是一个更好的主意---这样,当DB层变化时,代码往往比较好改。

  • Dispose和IDisposable接口

// Services 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)
{
    // container will create the instance(s) of these types and will dispose them
    services.AddScoped();
    services.AddSingleton();
    services.AddSingleton(sp => new SomeServiceImplementation());

    // container did not create instance so it will NOT dispose it
    services.AddSingleton(new Service3());
    services.AddSingleton(new Service3());
}
  • DI is for objects that have complex dependencies. Controllers, services, adapters, and repositories are all examples of objects that might be added to DI.

  • Avoid storing data and configuration directly in DI. For example, a user's shopping cart shouldn't typically be added to the services container. Configuration should use the options pattern. Similarly, avoid "data holder" objects that only exist to allow access to some other object. It's better to request the actual item needed via DI, if possible.

  • Avoid static access to services.

  • Avoid service location in your application code.

  • Avoid static access to HttpContext.
    Remember, dependency injection is an alternative to static/global object access patterns. You will not be able to realize the benefits of DI if you mix it with static object access.

你可能感兴趣的:(Asp.net core 2.0 官网搬运系列-依赖注入(DI))