我想在 ASP.NET Core 中实现 dependency injection (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>();
}
TL;博士
瞬态对象总是不同的;为每个控制器和每个服务提供一个新实例。作用域对象在一个请求中是相同的,但在不同的请求中是不同的。对于每个对象和每个请求,单例对象都是相同的。
为了更清楚地说明,.NET documentation 中的这个示例显示了不同之处:
为了演示这些生命周期和注册选项之间的区别,请考虑一个简单的接口,该接口将一个或多个任务表示为具有唯一标识符 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
的特定实例,因此在使用此类型时会很清楚。我们还注册了一个依赖于每个其他 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;
}
}
}
为了演示对应用程序的单独请求之内和之间的对象生命周期,示例包括一个请求每种 IOperation
类型的 OperationsController
以及一个 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();
}
}
}
现在对该控制器操作发出两个单独的请求:
https://i.stack.imgur.com/ilUB6.png
https://i.stack.imgur.com/X2QCd.png
观察请求中和请求之间的哪个 OperationId
值不同。
瞬态对象总是不同的;为每个控制器和每个服务提供一个新实例。
作用域对象在一个请求中是相同的,但在不同的请求中是不同的
Singleton 对象对于每个对象和每个请求都是相同的(无论在 ConfigureServices 中是否提供了实例)
在 .NET 的依赖注入中有三个主要的生命周期:
Singleton 在整个应用程序中创建单个实例。它第一次创建实例并在所有调用中重用相同的对象。
作用域生命周期服务在作用域内的每个请求创建一次。它相当于当前范围内的单例。例如,在 MVC 中,它为每个 HTTP 请求创建一个实例,但它在同一个 Web 请求的其他调用中使用相同的实例。
每次请求时都会创建瞬态生命周期服务。这个生命周期最适合轻量级、无状态的服务。
在这里您可以找到示例以查看差异:
ASP.NET 5 MVC6 Dependency Injection in 6 Steps(由于死链接导致的网络存档链接)
Your Dependency Injection ready ASP.NET : ASP.NET 5
这是官方文档的链接:
Dependency injection in ASP.NET Core
使用哪一个
短暂的
因为每次创建它们都会使用更多的内存和资源,并且会对性能产生负面影响
将此用于具有很少或没有状态的轻量级服务。
范围
当您想在请求中维护状态时,这是更好的选择。
辛格尔顿
这些服务中的内存泄漏会随着时间的推移而增加。
内存效率也很高,因为它们一旦在任何地方重用就被创建了。
在需要维护应用程序范围状态的地方使用单例。应用程序配置或参数、日志服务、数据缓存是您可以使用单例的一些示例。
将不同生命周期的服务注入另一个服务
切勿将 Scoped & Transient 服务注入 Singleton 服务。 (这有效地将瞬态或作用域服务转换为单例。)永远不要将瞬态服务注入作用域服务(这会将瞬态服务转换为作用域。)
Transient
推荐用于“状态很少或没有状态的轻量级服务”。在这种情况下为什么不单例?因为它是无状态的,所以只实例化那个小服务并多次使用它不是更好吗?即使服务实例化很便宜,如果你做很多次,开销也会增加。使用单例,它保持不变
https://i.stack.imgur.com/M0le5.jpg
更新:图片参考:ASP.NET Core Service Lifetimes (Infographic),作者:@WaqasAnwar
services.AddTransient<IProductService, ProductService>();
。我有一项服务,内存中的计数为 193!该服务仅具有无状态方法,是否应将其限定为范围而不是瞬态,以便我只能为所有控制器创建一个?
AddScoped<IProductService, ProductService>();
。但是对于所有请求的一个实例,请使用 AddSingelton<IProductService, ProductService>();
Transient, scoped 和 singleton 定义了 ASP.NET MVC core DI(Dependency Injection) 中需要注入多个相同类型的对象时的对象创建过程。如果您不熟悉依赖项注入,您可以看到此 DI IoC video。
您可以看到下面的控制器代码,其中我在构造函数中请求了两个“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
}
}
瞬态:在瞬态中,新的对象实例将被注入到单个请求和响应中。下面是我显示 GUID 值的快照图像。
https://i.stack.imgur.com/Xszmo.png
Scoped:在 scoped 中,相同的对象实例将被注入到单个请求和响应中。
https://i.stack.imgur.com/pSm3C.png
单例:在单例中,将在所有请求和响应中注入相同的对象。在这种情况下,将创建对象的一个全局实例。
下面是一个简单的图表,它直观地解释了上述基本原理。
https://i.stack.imgur.com/2b9Lq.png
上图是我拍ASP.NET MVC training in Mumbai时SBSS团队绘制的。非常感谢 SBSS 团队创建了上述图像。
new TService
。 Scoped 将为该“范围”缓存它的第一次初始化(大多数情况下为 http 请求)。 Singleton 将在应用程序的整个生命周期内只缓存一个实例,就这么简单。上面的图太复杂了。
添加单例()
AddSingleton() 在首次请求时创建服务的单个实例,并在需要该服务的所有地方重用相同的实例。
AddScoped()
在作用域服务中,对于每个 HTTP 请求,我们都会获得一个新实例。但是,在同一个 HTTP 请求中,如果在多个地方需要服务,例如在视图和控制器中,则为该 HTTP 请求的整个范围提供相同的实例。但是每个新的 HTTP 请求都会获得一个新的服务实例。
添加瞬态()
对于瞬态服务,每次请求服务实例时都会提供一个新实例,无论它是在同一个 HTTP 请求的范围内还是跨不同的 HTTP 请求。
单例是应用程序域生命周期的单个实例。
Scoped 是作用域请求期间的单个实例,这意味着 ASP.NET 中的每个 HTTP 请求。
Transient 是每个代码请求的单个实例。
通常代码请求应通过构造函数参数进行,如
public MyConsumingClass(IDependency dependency)
我想在@akazemis 的回答中指出,DI 上下文中的“服务”并不意味着 RESTful 服务;服务是提供功能的依赖项的实现。
在寻找这个问题的答案后,我找到了一个精彩的解释,并附有一个我想与你分享的例子。
您可以观看演示差异的视频HERE
在这个例子中,我们有这个给定的代码:
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() - 顾名思义,AddSingleton() 方法创建一个 Singleton 服务。首次请求时会创建 Singleton 服务。然后,所有后续请求都使用相同的实例。因此,一般来说,每个应用程序只创建一次 Singleton 服务,并且在整个应用程序生命周期中都会使用该单个实例。 AddTransient() - 此方法创建一个 Transient 服务。每次请求时都会创建一个 Transient 服务的新实例。 AddScoped() - 此方法创建一个 Scoped 服务。范围内的每个请求都会创建一个 Scoped 服务的新实例。例如,在 Web 应用程序中,它为每个 http 请求创建 1 个实例,但在同一 Web 请求的其他调用中使用相同的实例。
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,我强烈推荐这本书 Dependency Injection Principles, Practices, and Patterns。我的回答基本上只是重复我在那里学到的东西。
瞬态:每次请求实例时都会提供一个新实例,无论它是在同一个http请求的范围内还是在不同的http请求之间。
Scoped:我们在给定的 http 请求范围内获得相同的实例,但在不同的 http 请求中获得一个新实例。
Singleton:只有一个实例。当第一次请求服务时创建一个实例,并且该单个实例单个实例将被整个应用程序中的所有后续 http 请求使用。
可能是生命周期的最佳例证通过 DbContext 与 EntityFramework/Core 一起发挥作用。
建议将 DbContext 和与 DbContext 交互的存储库与 Scoped 生命周期连接起来,因为 DbContext 显然是一个有状态的构造。所以你不想使用单例,因为你最终会遇到各种并发问题。你不想使用 Transient 因为 DbContext 不是线程安全的。请记住,瞬态适用于处理无状态对象/类的用例。
而且由于大多数存储库都是由控制器调用的,因此使用 Scoped 生命周期确实很有意义。可以想象,作为事务的一部分,可以在单个操作方法期间多次调用 DbContext。
本文没有直接讨论这些生命周期,但很好地解释了为什么 Scoped 生命周期最适合 DbContext。
https://mehdi.me/ambient-dbcontext-in-ef6/?msclkid=00251b05d01411ec8d85d232374f26d5