我想在ASP.NET Core中实现依赖注入(DI)。因此,在将此代码添加到ConfigureServices方法后,这两种方法都可以工作。

ASP.NET Core中的services.AddTransient和service.AddScoped方法之间有什么区别?

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

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

当前回答

DI容器一开始可能非常神秘,特别是在使用寿命方面。毕竟,容器使用反射使一切“正常工作”。这有助于思考容器在幕后为您实际完成的任务:构建对象图。

对于.NET web应用程序,使用DI容器的替代方法是用您自己的控制器激活器替换默认控制器激活器,它必须手动管理生存期并构建依赖关系图。出于学习目的,假设您有一个硬编码的控制器激活器,以便每次有web请求时返回一个特定的控制器:

// This class is created once per application during startup.  In DI terms, it is the
// "composition root."
public class DumbControllerActivator
{
    // Shared among all consumers from all requests
    private static readonly Singleton1 singleton1 = new Singleton1();
    private static readonly Singleton2 singleton2 = new Singleton2();

    // This method's responsibility is to construct a FooController and its dependecies.
    public FooController HandleFooRequest()
    {
        // Shared among all consumers in this request
        var scoped1 = new Scoped1();
        var scoped2 = new Scoped2(singleton1, scoped1);

        return new FooController(
            singleton1,
            scoped1,
            new Transient1(                     // Fresh instance
                singleton2,
                new Transient2(scoped2)),       // Fresh instance
            new Transient3(                     // Fresh instance
                singleton1,
                scoped1,
                new Transient1(                 // Fresh instance
                    singleton2,
                    new Transient2(scoped2)));  // Fresh instance
    }
}

激活器只创建每个单例实例一次,然后在应用程序的整个生命周期中保存它。每个使用者共享一个实例(甚至来自不同请求的使用者)。对于作用域依赖项,激活器为每个web请求创建一个实例。在该请求中,每个使用者共享一个实例,但从一个请求到另一个请求,实例是不同的。对于暂时依赖关系,每个使用者都有自己的私有实例。根本没有共享。

要深入了解DI,我强烈推荐《依赖注入原则、实践和模式》一书。我的答案基本上只是重复我在那里学到的东西。

其他回答

在.NET的依赖注入中,有三个主要的生命周期:

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

作用域内的每个请求创建一次作用域生存期服务。它相当于当前范围中的单例。例如,在MVC中,它为每个HTTP请求创建一个实例,但在同一web请求中的其他调用中使用相同的实例。

每次请求时都会创建短暂的生命周期服务。这种生命周期最适合于轻量级、无状态服务。

在这里,您可以找到和示例来了解不同之处:

ASP.NET 5 MVC6依赖注入,分6个步骤进行(由于链接无效,导致web存档链接)

依赖项注入就绪的ASP.NET:ASP.NET 5

这是官方文件的链接:

ASP.NET核心中的依赖注入

添加单体()

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

添加作用域()

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

添加过渡()

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

TL;博士

瞬态对象总是不同的;将向提供一个新实例每个控制器和每个服务。作用域对象在请求中是相同的,但在请求中不同不同的请求。单个对象对于每个对象和每个请求都是相同的。

为了进一步澄清,.NET文档中的这个示例显示了不同之处:

要演示这些生存期和注册选项之间的区别,请考虑一个简单的接口,该接口将一个或多个任务表示为具有唯一标识符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,它依赖于其他每个操作类型,因此在请求中可以清楚地看到,该服务是获得与控制器相同的实例,还是为每个操作类型获得一个新的实例。此服务所做的只是将其依赖项公开为财产,以便可以在视图中显示它们。

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。索引操作然后显示所有控制器和服务的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中是否提供了实例)

DI容器一开始可能非常神秘,特别是在使用寿命方面。毕竟,容器使用反射使一切“正常工作”。这有助于思考容器在幕后为您实际完成的任务:构建对象图。

对于.NET web应用程序,使用DI容器的替代方法是用您自己的控制器激活器替换默认控制器激活器,它必须手动管理生存期并构建依赖关系图。出于学习目的,假设您有一个硬编码的控制器激活器,以便每次有web请求时返回一个特定的控制器:

// This class is created once per application during startup.  In DI terms, it is the
// "composition root."
public class DumbControllerActivator
{
    // Shared among all consumers from all requests
    private static readonly Singleton1 singleton1 = new Singleton1();
    private static readonly Singleton2 singleton2 = new Singleton2();

    // This method's responsibility is to construct a FooController and its dependecies.
    public FooController HandleFooRequest()
    {
        // Shared among all consumers in this request
        var scoped1 = new Scoped1();
        var scoped2 = new Scoped2(singleton1, scoped1);

        return new FooController(
            singleton1,
            scoped1,
            new Transient1(                     // Fresh instance
                singleton2,
                new Transient2(scoped2)),       // Fresh instance
            new Transient3(                     // Fresh instance
                singleton1,
                scoped1,
                new Transient1(                 // Fresh instance
                    singleton2,
                    new Transient2(scoped2)));  // Fresh instance
    }
}

激活器只创建每个单例实例一次,然后在应用程序的整个生命周期中保存它。每个使用者共享一个实例(甚至来自不同请求的使用者)。对于作用域依赖项,激活器为每个web请求创建一个实例。在该请求中,每个使用者共享一个实例,但从一个请求到另一个请求,实例是不同的。对于暂时依赖关系,每个使用者都有自己的私有实例。根本没有共享。

要深入了解DI,我强烈推荐《依赖注入原则、实践和模式》一书。我的答案基本上只是重复我在那里学到的东西。

在寻找这个问题的答案后,我找到了一个很好的解释,并列举了一个我想与大家分享的例子。

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

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

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>

启动.cs

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

复制粘贴此代码,然后按视图中的创建按钮,然后在AddSingleton、AddScoped和AddTransient每次都会得到不同的结果,这可能会帮助您理解这一点。

AddSingleton()-顾名思义,AddSingletton()方法创建单人服务。Singleton服务在首次创建时创建请求。然后,所有后续的请求。所以一般来说,Singleton服务只创建一次每个应用程序,并且在整个应用寿命。AddTransient()-此方法创建一个Transient服务。一个新的每次请求瞬态服务时都会创建该服务的实例。AddScoped()-此方法创建作用域服务。的新实例作用域内的每个请求创建一次作用域服务。对于例如,在web应用程序中,它为每个http创建一个实例请求,但在同一调用中的其他调用中使用同一实例web请求。