AddTransient,AddScoped 和 AddSingleton 服务有何区别?

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}

答案

TL; DR

瞬态对象总是不同的。为每个控制器和每个服务提供一个新实例。

范围对象在一个请求中是相同的,但在不同的请求中是不同的。

每个对象和每个请求的单例对象都是相同的。

为了进一步说明,来自asp.net docs 的此示例显示了不同之处:

为了说明这些生存期和注册选项之间的区别,请考虑一个简单的接口,该接口将一个或多个任务表示为具有唯一标识符OperationId 。根据我们为该服务配置生存期的方式,容器将为请求的类提供相同或不同的服务实例。为了明确要求哪个生存期,我们将为每个生存期创建一个类型的选项:

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
    {
    }
}

我们使用单个类Operation来实现这些接口,该类在其构造函数中接受Guid ,或者如果未提供Guid则使用新的Guid

using System;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Classes
{
    public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance
    {
        Guid _guid;
        public Operation() : this(Guid.NewGuid())
        {

        }
        public Operation(Guid guid)
        {
            _guid = guid;
        }

        public Guid OperationId => _guid;
    }
}

接下来,在ConfigureServices ,根据命名寿命将每种类型添加到容器中:

services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

请注意, IOperationSingletonInstance服务正在使用具有已知 ID 为Guid.Empty的特定实例,因此使用此类型时将很清楚。我们还注册了一个OperationService ,它依赖于其他每个Operation类型,因此对于每个Operation类型,该请求中都将清楚此服务是与控制器实例相同,还是获得一个新实例。该服务所做的全部工作就是将其依赖项公开为属性,以便可以在视图中显示它们。

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;
        }
    }
}

为了演示对应用程序的各个单独请求之内和之间的对象生存期,该示例包括一个OperationsController ,它请求每种IOperation类型以及一个OperationService 。然后,“ Index操作将显示所有控制器和服务的OperationId值。

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();
        }
    }
}

现在,对此控制器操作提出了两个单独的请求: 第一个请求

第二个请求

观察请求中和请求之间哪个OperationId值不同。

  • 瞬态对象总是不同的。为每个控制器和每个服务提供一个新实例。

  • 范围对象在一个请求中是相同的,但在不同的请求中是不同的

  • 每个对象和每个请求的单例对象都是相同的(无论是否在ConfigureServices提供了实例)

在 dotnet 的依赖注入中,有 3 个主要生命周期:

Singleton ,它将在整个应用程序中创建一个实例。它第一次创建实例,并在所有调用中重用相同的对象。

范围内的生存期服务在范围内为每个请求创建一次。在当前范围内等效于 Singleton。例如。在 MVC 中,每个 HTTP 请求创建 1 个实例,但在同一 Web 请求中的其他调用中使用相同的实例。

每次请求时都会创建瞬态生存期服务。此生命周期最适合轻量级无状态服务。

在这里您可以找到示例,以了解两者之间的区别:

http://dotnetliberty.com/index.php/2015/10/15/asp-net-5-mvc6-dependency-injection-in-6-steps/

https://codewala.net/2015/04/30/your-dependency-injection-ready-asp-net-asp-net-5/

这是官方文档的链接:

https://docs.asp.net/zh_CN/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options

当必须注入多个相同类型的对象时,可以在 ASP.NET MVC 核心 DI 中使用瞬态,作用域和单例定义对象创建过程。如果您是依赖注入的新手,可以观看此DI IOC 视频

您可以看到以下控制器代码,其中我已在构造函数中请求了两个 “IDal” 实例。 Transient,Scoped 和 Singleton 定义将以 “_dal” 和 “_dal1” 注入相同实例还是以不同实例注入。

public class CustomerController : Controller
    {
        IDal dal = null;
        public CustomerController(IDal _dal
                                ,IDal _dal1)
        {
            dal = _dal;
            // DI of MVC core
            // inversion of control
        }
}

瞬态:- 在瞬态中,新对象实例将被注入单个 Request 和 Response 中。下面是我显示 GUID 值的快照图像。

在此处输入图片说明

范围:- 在范围内,同一对象实例将注入单个请求和响应中。

在此处输入图片说明

Singleton:- 在 Singleton 中,将在所有请求和响应中注入相同的对象。在这种情况下,将创建该对象的一个全局实例。

下面是一个简单的图表,从视觉上解释了上面的基本原理。

MVC DI图片

上面的图像是我在孟买接受ASP.NET MVC 培训时由 SBSS 团队绘制的,这非常感谢 SBSS 团队创建了上面的图像。

  • Singleton 是应用程序域生存期内的单个实例。
  • 作用域是作用域请求期间的单个实例,这意味着 ASP.NET 中的每个HTTP请求。
  • 瞬态是每个代码请求的单个实例。

通常,代码请求应通过构造函数参数进行,如

public MyConsumingClass(IDependency dependency)

我想在 @akazemis 的答案中指出,DI 上下文中的 “服务” 并不意味着 RESTful 服务;服务是提供功能的依赖项的实现。

AddSingleton()

当首次请求该服务时,AddSingleton()创建该服务的单个实例,并在需要该服务的所有位置重用该实例。

AddScoped()

在每个 http 请求的范围服务中,我们都获得一个新实例。但是,在同一个 http 请求中,如果在多个位置(如视图和控制器中)都需要服务,则将为该 http 请求的整个范围提供相同的实例。但是,每个新的 http 请求都将获得该服务的新实例。

AddTransient()

对于临时服务,每次请求服务实例时,无论是在同一 http 请求的范围内还是跨不同的 http 请求,都会提供一个新实例。

在寻找了这个问题的答案之后,我找到了一个精妙的解释,并举了一个例子,我想与您分享。

您可以在此处观看展示差异的视频

在此示例中,我们具有以下给定代码:

public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee Add(Employee employee);
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MockEmployeeRepository : IEmployeeRepository
{
    private List<Employee> _employeeList;

    public MockEmployeeRepository()
    {
        _employeeList = new List<Employee>()
    {
        new Employee() { Id = 1, Name = "Mary" },
        new Employee() { Id = 2, Name = "John" },
        new Employee() { Id = 3, Name = "Sam" },
    };
    }

    public Employee Add(Employee employee)
    {
        employee.Id = _employeeList.Max(e => e.Id) + 1;
        _employeeList.Add(employee);
        return employee;
    }

    public IEnumerable<Employee> GetAllEmployees()
    {
        return _employeeList;
    }
}

家庭控制器

public class HomeController : Controller
{
    private IEmployeeRepository _employeeRepository;

    public HomeController(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    [HttpGet]
    public ViewResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create(Employee employee)
    {
        if (ModelState.IsValid)
        {
            Employee newEmployee = _employeeRepository.Add(employee);
        }

        return View();
    }
}

建立检视

@model Employee
@inject IEmployeeRepository empRepository

<form asp-controller="home" asp-action="create" method="post">
    <div>
        <label asp-for="Name"></label>
        <div>
            <input asp-for="Name">
        </div>
    </div>

    <div>
        <button type="submit">Create</button>
    </div>

    <div>
        Total Employees Count = @empRepository.GetAllEmployees().Count().ToString()
    </div>
</form>

启动文件

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IEmployeeRepository, MockEmployeeRepository>();
}

复制粘贴此代码,然后在视图中按 “创建” 按钮,然后在AddSingletonAddScopedAddTransient之间AddSingletonAddScoped都会得到不同的结果,这可能有助于您理解以下说明:

AddSingleton() - 顾名思义,AddSingleton()方法创建一个 Singleton 服务。首次请求时会创建一个 Singleton 服务。然后,所有后续请求都使用同一实例。因此,通常,每个应用程序仅创建一次 Singleton 服务,并且在整个应用程序生命周期内都使用单个实例。

AddTransient() - 此方法创建一个 Transient 服务。每次请求时,都会创建一个新的瞬态服务实例。

AddScoped() - 此方法创建一个范围服务。在范围内,每个请求都会创建一个范围服务的新实例。例如,在 Web 应用程序中,每个 HTTP 请求创建 1 个实例,但在同一 Web 请求中的其他调用中使用相同的实例。

如所描述的在这里 (该链接是非常有用的)用一个例子,

接口与具体类型之间的映射定义为,每次您请求 IContryService 类型时,您都会获得 CountryService 的新实例。在这种情况下,这就是瞬态的意思。您还可以添加单例映射(使用 AddSingleton)和作用域映射(使用 AddScoped)。在这种情况下,作用域是指作用域为 HTTP 请求,这也意味着它在当前请求运行时是单例。您还可以使用方法 AddInstance 将现有实例添加到 DI 容器中。这些是注册 IServiceCollection 的几乎完整方法