ASP.NET Core 依赖注入
前言
👨💻👨🌾📝记录学习成果,以便温故而知新
今天主要学了一下VUE3还有C#相关,就以一篇C#的学习内容收官之作吧。
之所以是标题中有“ASP.NET Core”,是因为在看“ASP.NET Core”相关文档时,感觉需要把依赖注入这块的知识稍微理一下,有个粗浅的认识。
本篇的读者首先自己需要对“依赖注入”有一定的认知,因为这个知识点具有烦、绕、啰嗦等特定,所以这不是本篇的重点。
本文的演示环境是Win10,目标框架是.NET Core 2.1,建的是ASP.NET Core Web 应用程序。不同的环境可能会有差别。
依赖注入
依赖(Dependency)关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
这是类图对于“依赖”这个知识点的定义与说明。
“注入”可以理解为依赖声明的对象不用用户自己构造,由框架来处理构造。
“注入”的对象通常是提供服务的,而不是提供属性的。
一、注册服务
注册服务还是看官网的这篇。
可以理解为建立“声明”与构造的关系。
方法 | 自动对象释放 | 多种实现 | 传递参数 |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() 示例: services.AddSingleton<IMyDep, MyDep>(); | 是 | 是 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) 示例: services.AddSingleton(sp => new MyDep()); services.AddSingleton(sp => new MyDep(99)); | 是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>() 示例: services.AddSingleton<MyDep>(); | 是 | No | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) 示例: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); | 否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION}) 示例: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); | 否 | No | 是 |
这里是借用的官网的一个表格,更多请参看API。
这里展示一下一个接口多个实现并且多次注册的情况。
IAdd.cs
namespace TestDI.Service
{
public interface IAdd
{
string Id { get; }
}
public class Add1 : IAdd
{
public Add1()
{
Id = $"{nameof(Add1)}:{Guid.NewGuid().ToString("N")}";
}
public string Id { get; }
}
public class Add2 : IAdd
{
public Add2()
{
Id = $"{nameof(Add2)}:{Guid.NewGuid().ToString("N")}";
}
public string Id { get; }
}
}
测试文件TestAddController.cs
namespace TestDI.Models.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestAddController : ControllerBase
{
public TestAddController(IAdd add, IEnumerable<IAdd> adds)
{
Trace.Assert(add is Add2);
Console.WriteLine(add.Id);
var addArray = adds.ToArray();
foreach (var e in addArray)
{
Console.WriteLine(e.Id);
}
}
// GET: api/TestAdd/Test
[HttpGet("Test")]
public IActionResult Test()
{
return Content("Test");
}
}
}
在Startup.cs文件ConfigureServices中的注册代码:
services.AddSingleton<IAdd, Add1>();
services.AddSingleton<IAdd, Add1>();
services.AddSingleton<IAdd, Add2>();
调用测试方法后控制台输出如下图:
再次调用测试方法控制台输出如下图:
原来两次调用Controller的构造函数都执行了。可以看出一个接口可以多次注册相同的实现与不同的实现,如果接口注入的话,以最后一次注册为准。
二、作用域
官网的这篇同样讲到了作用域。
服务生存期:
- 暂时
- 作用域
- 单例
暂时:
暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 向 AddTransient 注册暂时性服务。
在处理请求的应用中,在请求结束时会释放暂时服务。
作用域:
对于 Web 应用,指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。 向 AddScoped 注册范围内服务。
在处理请求的应用中,在请求结束时会释放有作用域的服务。
默认情况下在开发环境中,从具有较长生存期的其他服务解析服务将引发异常。
单例:
创建单例生命周期服务的情况如下:
- 在首次请求它们时进行创建;
- 或者在向容器直接提供实现实例时由开发人员进行创建。 很少用到此方法。
来自依赖关系注入容器的服务实现的每一个后续请求都使用同一个实例。 如果应用需要单一实例行为,则允许服务容器管理服务的生存期。 不要实现单一实例设计模式,或提供代码来释放单一实例。 服务永远不应由解析容器服务的代码释放。 如果类型或工厂注册为单一实例,则容器自动释放单一实例。向 AddSingleton 注册单一实例服务。 单一实例服务必须是线程安全的,并且通常在无状态服务中使用。
在处理请求的应用中,当应用关闭并释放 ServiceProvider 时,会释放单一实例服务。 由于应用关闭之前不释放内存,因此请考虑单一实例服务的内存使用。
暂时与单例是两个极端,一般也不会去发挥。作用域是有一定的可塑性的。
1.Startup.cs
在Startup.cs文件代码研究作用域。准备文件如下:
IOperation.cs
namespace TestDI.Service
{
public interface IOperation
{
string OperationId { get; }
}
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
}
Operation.cs
namespace TestDI.Service
{
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
public Operation()
{
Console.WriteLine("Operation");
OperationId = Guid.NewGuid().ToString("N");
}
public string OperationId { get; }
}
}
Startup.cs文件ConfigureServices中的注册及测试代码
services.AddTransient<Operation>();
using (ServiceProvider sp = services.BuildServiceProvider())
{
var ts1 = sp.GetRequiredService<Operation>();
var ts2 = sp.GetRequiredService<Operation>();
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine($"AddTransient 注入: {object.ReferenceEquals(ts1, ts2)}");
}
//作用域
services.AddScoped<Operation>();
using (ServiceProvider AddScoped = services.BuildServiceProvider())
{
var se = new Operation(); //注意长生命周期 不要引用比它短周期
using (IServiceScope scope = AddScoped.CreateScope())
{
var t1 = scope.ServiceProvider.GetService<Operation>();
var t2 = scope.ServiceProvider.GetService<Operation>();
Console.WriteLine($"AddScoped 注入: {object.ReferenceEquals(t1, t2)}");
se = t2;
}
//作用域
using (IServiceScope scope2 = AddScoped.CreateScope())
{
var ts1 = scope2.ServiceProvider.GetService<Operation>();
var ts2 = scope2.ServiceProvider.GetService<Operation>();
Console.WriteLine($"AddScoped2 注入: {object.ReferenceEquals(se, ts2)}");
}
}
项目运行后控制台输出如下图:
红框中的False说明作用域起到了作用。
2.IServiceScopeFactory
通过IServiceScopeFactory注入创建作用域
测试文件TestActionScopeController.cs:
namespace TestDI.Models.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestActionScopeController : ControllerBase
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public TestActionScopeController(IServiceScopeFactory serviceScopeFactory)
{
Console.WriteLine(nameof(TestActionScopeController));
(_serviceScopeFactory) = (serviceScopeFactory);
}
// GET: api/TestActionScope/Test1
[HttpGet("Test1")]
public IActionResult TestActionScope1()
{
using (IServiceScope scope = _serviceScopeFactory.CreateScope())
{
Console.WriteLine("scope1:" + scope.ServiceProvider.GetHashCode());
var operationTransient = scope.ServiceProvider.GetRequiredService<IOperationTransient>();
Console.WriteLine("operationTransient.OperationId :" + operationTransient.OperationId);
var operationScoped = scope.ServiceProvider.GetRequiredService<IOperationScoped>();
Console.WriteLine("operationScoped.OperationId :" + operationScoped.OperationId);
var operationSingleton = scope.ServiceProvider.GetRequiredService<IOperationSingleton>();
Console.WriteLine("operationSingleton.OperationId :" + operationSingleton.OperationId);
}
using (IServiceScope scope = _serviceScopeFactory.CreateScope())
{
Console.WriteLine("scope2:" + scope.ServiceProvider.GetHashCode());
var operationTransient = scope.ServiceProvider.GetRequiredService<IOperationTransient>();
Console.WriteLine("operationTransient.OperationId :" + operationTransient.OperationId);
var operationScoped = scope.ServiceProvider.GetRequiredService<IOperationScoped>();
Console.WriteLine("operationScoped.OperationId :" + operationScoped.OperationId);
var operationSingleton = scope.ServiceProvider.GetRequiredService<IOperationSingleton>();
Console.WriteLine("operationSingleton.OperationId :" + operationSingleton.OperationId);
}
return Content("Test1");
}
// GET: api/TestActionScope/Test2
[HttpGet("Test2")]
public IActionResult Test2([FromServices] ActionScopeService1 actionScopeService1,
[FromServices] ActionScopeService2 actionScopeService2)
{
actionScopeService1.showActionScope("Test1 show1");
actionScopeService1.showActionScope("Test1 show1");
actionScopeService2.showActionScope("Test2 show1");
actionScopeService2.showActionScope("Test2 show2");
return Content("Test2");
}
}
}
其中的Test1是测试本节的,Test2是测试下节的。说明一下,如果有的代码已经出现过,为了避免重复就不重复展示了。
执行测试代码后控制台输出如下图:
红框中的ID不一样,说明这里的作用域也起到了作用。
再执行一次测试代码控制台输出如下图:
红框中的输出说明Controller的构造函数执行两次,而前一章中Controller也调用过两次。这似乎说明Controller注册的服务生存期是作用域(Scope),由于目前尚未找到官网的说明,这里只能暂且作一私下的推论。
3.Controller注入不同的服务
Controller注入两个不同的服务,二服务中注入了相同的服务,有点套娃的味道。
ActionScopeService1.cs:
namespace TestDI.Service
{
public class ActionScopeService1
{
private readonly IOperationTransient _IOperationTransient;
private readonly IOperationScoped _IOperationScoped;
private readonly IOperationSingleton _IOperationSingleton;
public ActionScopeService1(IOperationTransient operationTransient,
IOperationScoped operationScoped,
IOperationSingleton operationSingleton)
{
Console.WriteLine("ActionScopeService1");
(_IOperationTransient, _IOperationScoped, _IOperationSingleton)
= (operationTransient, operationScoped, operationSingleton);
}
public void showActionScope1(string msg)
{
Console.WriteLine(msg + "->showActionScope1");
Console.WriteLine("_IOperationTransient.OperationId :" + _IOperationTransient.OperationId);
Console.WriteLine("_IOperationScoped.OperationId :" + _IOperationScoped.OperationId);
Console.WriteLine("_IOperationSingleton.OperationId :" + _IOperationSingleton.OperationId);
}
public void showActionScope2(string msg)
{
Console.WriteLine(msg + "->showActionScope2");
Console.WriteLine("_IOperationTransient.OperationId :" + _IOperationTransient.OperationId);
Console.WriteLine("_IOperationScoped.OperationId :" + _IOperationScoped.OperationId);
Console.WriteLine("_IOperationSingleton.OperationId :" + _IOperationSingleton.OperationId);
}
}
}
ActionScopeService2.cs:
namespace TestDI.Service
{
public class ActionScopeService2
{
private readonly IOperationTransient _IOperationTransient;
private readonly IOperationScoped _IOperationScoped;
private readonly IOperationSingleton _IOperationSingleton;
public ActionScopeService2(IOperationTransient operationTransient,
IOperationScoped operationScoped,
IOperationSingleton operationSingleton)
{
Console.WriteLine("ActionScopeService2");
(_IOperationTransient, _IOperationScoped, _IOperationSingleton)
= (operationTransient, operationScoped, operationSingleton);
}
public void showActionScope1(string msg)
{
Console.WriteLine(msg + "->showActionScope1");
Console.WriteLine("_IOperationTransient.OperationId :" + _IOperationTransient.OperationId);
Console.WriteLine("_IOperationScoped.OperationId :" + _IOperationScoped.OperationId);
Console.WriteLine("_IOperationSingleton.OperationId :" + _IOperationSingleton.OperationId);
}
public void showActionScope2(string msg)
{
Console.WriteLine(msg + "->showActionScope2");
Console.WriteLine("_IOperationTransient.OperationId :" + _IOperationTransient.OperationId);
Console.WriteLine("_IOperationScoped.OperationId :" + _IOperationScoped.OperationId);
Console.WriteLine("_IOperationSingleton.OperationId :" + _IOperationSingleton.OperationId);
}
}
}
测试代码片段:
// GET: api/TestActionScope/Test2
[HttpGet("Test2")]
public IActionResult Test2([FromServices] ActionScopeService1 actionScopeService1,
[FromServices] ActionScopeService2 actionScopeService2)
{
actionScopeService1.showActionScope1("Service1.1");
actionScopeService1.showActionScope1("Service1.2");
actionScopeService1.showActionScope2("Service1.1");
actionScopeService1.showActionScope2("Service1.2");
actionScopeService2.showActionScope1("Service2.1");
actionScopeService2.showActionScope1("Service2.2");
actionScopeService2.showActionScope2("Service2.1");
actionScopeService2.showActionScope2("Service2.2");
return Content("Test2");
}
注册服务代码如下:
services.AddScoped<ActionScopeService1>();
//services.AddTransient<ActionScopeService1>();
services.AddScoped<ActionScopeService2>();
调用测试代码控制台输出如下图:
从截图可以看出Controller与两个Service的构造函数都只执行了一次,貌似也就是注入了一次。
再一次调用测试代码,控制台输出如下图:
两次调用的对比可以看出“暂时”与“作用域”生存期的服务都变了,“单例”生存期的服务一直都没有变。
下面改一下注册服务代码如下:
//services.AddScoped<ActionScopeService1>();
services.AddTransient<ActionScopeService1>();
services.AddScoped<ActionScopeService2>();
调用测试代码控制台输出如下图:
再调用测试代码一次控制台输出如下图:
这里发现与没有改注册服务代码前的效果是差不多的,没有体现出**services.AddTransient<ActionScopeService1>();**效果。
这次修改一下调用代码,ActionScopeService1注入两次:
// GET: api/TestActionScope/Test2
[HttpGet("Test2")]
public IActionResult Test2([FromServices] ActionScopeService1 actionScopeService1,
[FromServices] ActionScopeService1 actionScopeService2)
{
actionScopeService1.showActionScope1("Service1.1");
actionScopeService1.showActionScope1("Service1.2");
actionScopeService1.showActionScope2("Service1.1");
actionScopeService1.showActionScope2("Service1.2");
actionScopeService2.showActionScope1("Service2.1");
actionScopeService2.showActionScope1("Service2.2");
actionScopeService2.showActionScope2("Service2.1");
actionScopeService2.showActionScope2("Service2.2");
return Content("Test2");
}
执行后控制台输出如下图:
这次ActionScopeService1构造函数执行了两次,原因是services.AddTransient();。
再改成services.AddScoped<ActionScopeService1>();,执行后控制台输出如下图:
可以看出同一“作用域”生存期内ActionScopeService1构造函数只执行了一次。
三、服务释放
服务释放是官网这篇里提到。
服务释放这里只做简单的代码演示不做全面展开。
Service1.cs文件:
namespace TestDI.Service
{
public class Service1 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service1: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
}
Service2.cs文件:
namespace TestDI.Service
{
public class Service2 : IDisposable
{
private bool _disposed;
public void Write(string message)
{
Console.WriteLine($"Service2: {message}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
}
Service3.cs文件:
namespace TestDI.Service
{
public interface IService3
{
void Write(string message);
}
public class Service3 : IService3, IDisposable
{
private bool _disposed;
public Service3(string myKey)
{
MyKey = myKey;
}
public string MyKey { get; }
public void Write(string message)
{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}
public void Dispose()
{
if (_disposed)
return;
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
}
注册代码:
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<IService3>(sp => new Service3("MyKey"));
调用代码TestDisposableController.cs文件:
namespace TestDI.Models.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestDisposableController : ControllerBase
{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;
public TestDisposableController(Service1 service1, Service2 service2, IService3 service3)
{
Console.WriteLine("TestDisposableController");
_service1 = service1;
_service2 = service2;
_service3 = service3;
}
// GET: api/TestDisposable/Test
[HttpGet("Test")]
public IActionResult Test()
{
_service1.Write("TestDisposableController.Test1");
_service2.Write("TestDisposableController.Test1");
_service3.Write("TestDisposableController.Test1");
return Content("Test");
}
}
}
调用测试代码控制台输出如下图:
红框中输出说明声生存期为“作用域”为的服务在作用域范围外释放了。