为什么要使用依赖注入框架(Dependence Injection)
-
借助依赖注入框架,我们可以轻松管理类之间的依赖,帮助我们在构建应用时遵循原则,确保代码的可维护性和可扩展性。
- ASP.NET Core的整个架构中,依赖注入框架提供了对象创建和生命周期管理的核心能力,各个组件相互协作,并实现控制反转(IOC)。
组件包:
体现了一个经典的设计模式:接口实现分离模式
以为着我们的组件只需要依赖抽象的接口,当使用时注入它的具体实现即可,意味着我们可以在未来做任意的扩展。
- Microsoft.Extensions.DependencyInjection.Abstractions
- Microsoft.Extensions.DependencyInjection
核心类型:
- IServiceCollection -负责服务的注册
- ServiceDescriptor -每一个服务注册的信息
- IServiceProvider -必有的容器
- IServiceScope -表示容器的生命周期
生命周期:ServiceLifeTime
- 单例 Singleton
在我们整个根容器的生命周期内都是一个单例。(例如我们用控制台启用一个Web Application,只要不关闭此控制台容器,则单例一直存在。)
- 作用域 Scoped
在容器或子容器中,随着容器的生命周期而改变,若容器释放,则该依赖对象也会释放。(如对Web接口每次进行一次请求,则会获得一个实例,在重新请求后,实例会变化。)
- 瞬时(暂时)Transient
每一次请求都能获得一个新的对象。
我们注册三个不同生命周期的实例并实现它们各自的抽象接口,来验证一下:
第一次请求:
第二次请求:
注册方式:
- 直接注册:
services.AddSingleton<IMySingletonService, MySingletonService>(); services.AddScoped<IMyScopedService, MyScopedService>(); services.AddTransient<IMyTransientService, MyTransientService>();
- 尝试注册:
services.TryAddEnumerable(ServiceDescriptor.Singleton<ISample, Sample>()); services.TryAddEnumerable(ServiceDescriptor.Scoped<ISample, Sample>()); services.TryAddEnumerable(ServiceDescriptor.Transient<ISample, Sample>());
它的优点在于我们想拓展我们的服务时,在同一个接口需要多种实现时,可以避免出现重复的实现。
- 移除或替换注册:
services.RemoveAll(ISample);
services.Replace(ServiceDescriptor.Scoped<ISample, Sample>());
获取依赖注入的方法:
- 通过构造函数注入依赖 -推荐在在服务大部分接口都需要使用的情况下
- 通过[FromService]注解 -此服务在某个接口需要使用的情况下
从 main 调用服务:
使用 IServiceScopeFactory.CreateScope 创建 IServiceScope 以解析应用范围内的作用域服务。 此方法可以用于在启动时访问有作用域的服务以便运行初始化任务(如在初始化数据库数据中使用)。
以下示例演示如何访问范围内 IMyDependency
服务并在 Program.Main
中调用其 WriteMessage
方法(官方文档示例):
public class Program { public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); using (var serviceScope = host.Services.CreateScope()) { var services = serviceScope.ServiceProvider; try { var myDependency = services.GetRequiredService<IMyDependency>(); myDependency.WriteMessage("Call services from main"); } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "An error occurred."); } } host.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
自己项目中的用法(为数据库设置种子数据):
public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); using var scope = host.Services.CreateScope(); var services = scope.ServiceProvider; var loggerFactory = services.GetRequiredService<ILoggerFactory>(); try { var myContext = services.GetRequiredService<MyContext>(); MyContextSeed.SeedAsync(myContext, loggerFactory).Wait(); } catch (Exception e) { var logger = loggerFactory.CreateLogger<Program>(); logger.LogError(e, "Error occurred when seeding the Database"); } host.Run(); }
作用域对注入的影响:
我们在请求服务时,不同的作用域和依赖注入的方式的不同使得容器对服务的释放方式也不同。
我们创建一个实现类,它实现了它的抽象接口以及IDisposable接口(它的主要职责是在释放时输出该服务的HashCode):
- Transient
首先在StartUp注册服务,然后将注册的服务注入到Controller中的两个对象。
Controller
[HttpGet]
public IActionResult GetDisposed(
[FromServices] IOrderService orderService1,
[FromServices] IOrderService orderService2)
{
Console.WriteLine("Interface Request Ending");
return NoContent();
}
运行后发现,这两个对象的HashCode是不同的,瞬时服务在每一次获取的时候都会获取一个新的对象,并且释放时机是在请求结束后释放的。
Tips:不建议在根容器中获取瞬时对象,因为根容器获取的服务只有在应用程序结束才能释放。为了防止资源不能及时回收的问题,应避免此用法。
- Scoped
首先在StartUp注册服务.
Controller:
[HttpGet] public IActionResult GetDisposed( [FromServices] IOrderService orderService1, [FromServices] IOrderService orderService2) { Console.WriteLine("------------1-------------"); using (IServiceScope scope = HttpContext.RequestServices.CreateScope()) { var service1= scope.ServiceProvider.GetService<IOrderService>(); var service2 = scope.ServiceProvider.GetService<IOrderService>(); } Console.WriteLine("------------2-------------"); Console.WriteLine("Interface Request Ending"); return NoContent(); }
在此代码中的HttpContext.RequestServices是指当前Web请求的容器,也就是应用程序根容器的一个子容器。所以在using语句块中在子容器作用域中又获取了一次服务,在using语句块结束后会自动释放。
那么我们从代码中可以看出在外层容器中我们请求了两次服务,内层容器中请求了两次服务。那是否会出现四次服务呢?
结果是只出现了两个不同的对象,说明在每一个作用域中,我们都只能获取相同的对象。
- Singleton
结果显而易见,我们无法释放我们的服务。因为我们的单例模式是注册在我们的根容器中的,只有在整个程序退出时去释放容器所管理的IDisposable的对象。