一、什么是依赖注入(Denpendency Injection)
1.1依赖
当一个类需要另一个类协作来完成工作的时候就产生了依赖。比如我们在AccountController这个控制器需要完成和用户相关的注册、登录 等事情。其中的登录我们由EF结合Idnetity来完成,所以我们封装了一个EFLoginService。这里AccountController就有一个ILoginService的依赖。
1.2 什么是注入
public class AccountController:Controller { private ILoginService<ApplicationUser> _loginService; //构造函数实例化需要的依赖 public AccountController() { _loginService = new EFLoginService() } }
public class AccountController:Controller { private ILoginService<ApplicationUser> _loginService; //构造函数实例化需要的依赖 public AccountController(ILoginService<ApplicationUser> loginService) { _loginService = loginService; } }
1.3 为什么要反转?
之前是用个SQL Server现在替换为mysql。实例化的时候选择mysql的实例就好
var controller = new AccountController(new SqlServer()); controller.Login(userName, password); // 用mysql来替换原来的sqlserver登录 var controller = new AccountController(new Mysql()); controller.Login(userName, password);
1.4 何为容器
如果我们需要配置统一管理依赖关系,就需要容器了。要不一个个class手动new很坑的
二、.NET Core DI
2.1 实例的注册
IServiceCollection 负责注册
IServiceProvider 负责提供实例
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddTransient<ILoginService, EFLoginService>() services.AddSingleton<ILoginService, EFLoginService>() services.AddScoped<ILoginService, EFLoginService>(); }
ServiceCollection的默认实现是提供一个ServiceDescriptor的List
public interface IServiceCollection : IList<ServiceDescriptor> { }
我们上面的AddTransient、AddSignletone和Scoped方法是IServiceCollection的扩展方法, 都是往这个List里面添加ServiceDescriptor。
2.2 实例的生命周期之单例
我们上面看到了,.NET Core DI 为我们提供的实例生命周其包括三种:
Transient: 每一次GetService都会创建一个新的实例
Scoped: 在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内)
Singleton :整个应用程序生命周期以内只创建一个实例
public interface IOperation { Guid OperationId { get; } } public interface IOperationSingleton : IOperation { } public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public class Operation : IOperationSingleton, IOperationTransient, IOperationScoped { private Guid _guid; public Operation() { _guid = Guid.NewGuid(); } public Operation(Guid guid) { _guid = guid; } public Guid OperationId => _guid; }
我们对IOperationSingleton注册了三次,最后获取两次,大家要注意到我们获取到的始终都是我们最后一次注册的那个给了一个Guid的实例,前面的会被覆盖。
2.3 实例生命周期之Tranisent
2.4 实例生命周期之Scoped
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IOperationScoped, Operation>(); services.AddTransient<IOperationTransient, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); var provider = services.BuildServiceProvider(); using (var scope1 = provider.CreateScope()) { var p = scope1.ServiceProvider; var scopeobj1 = p.GetService<IOperationScoped>(); var transient1 = p.GetService<IOperationTransient>(); var singleton1 = p.GetService<IOperationSingleton>(); var scopeobj2 = p.GetService<IOperationScoped>(); var transient2 = p.GetService<IOperationTransient>(); var singleton2 = p.GetService<IOperationSingleton>(); Console.WriteLine($"scope1: { scopeobj1.OperationId }," +$"transient1: {transient1.OperationId}, " +$"singleton1: {singleton1.OperationId}"); Console.WriteLine($"scope2: { scopeobj2.OperationId }, " +$"transient2: {transient2.OperationId}, " +$"singleton2: {singleton2.OperationId}"); } Console.WriteLine("---------------------------------------------------------"); using (var scope1 = provider.CreateScope()) { var p = scope1.ServiceProvider; var scopeobj1 = p.GetService<IOperationScoped>(); var transient1 = p.GetService<IOperationTransient>(); var singleton1 = p.GetService<IOperationSingleton>(); var scopeobj2 = p.GetService<IOperationScoped>(); var transient2 = p.GetService<IOperationTransient>(); var singleton2 = p.GetService<IOperationSingleton>(); Console.WriteLine($"scope1: { scopeobj1.OperationId }," + $"transient1: {transient1.OperationId}, " + $"singleton1: {singleton1.OperationId}"); Console.WriteLine($"scope2: { scopeobj2.OperationId }, " + $"transient2: {transient2.OperationId}, " + $"singleton2: {singleton2.OperationId}"); }
三、DI在ASP.NET Core中的应用
3.1在Startup类中初始化
public void ConfigureServices(IServiceCollection services) { //启动Razor页面 services.AddMvc(); services.AddDbContext<wolfTestContext>(options => options.UseSqlServer(Configuration.GetConnectionString("WolfTest"))); services.Configure<other.RedisConfigure>(Configuration.GetSection("Redis")); services.AddTransient<ISmsSender, SmsSender>(); services.AddScoped<ITodoRepository, TodoRepository>(); // Register application services. services.AddScoped<ICharacterRepository, CharacterRepository>(); }
3.2 Controller中使用
一般可以通过构造函数或者属性来实现注入,但是官方推荐是通过构造函数。这也是所谓的显式依赖。
我们只要在控制器的构造函数里面写了这个参数,ServiceProvider就会帮我们注入进来。这一步是在Mvc初始化控制器的时候完成的。
public class HomeController : Controller { private readonly RedisConfigure _redisConfigure; private readonly wolfTestContext _testContext; private readonly string _mailTo = Startup.Configuration["subsection:suboption1"]; private readonly ISmsSender _smsSender; private readonly ILogger<HomeController> _logger; public HomeController(IOptions<RedisConfigure> setting , wolfTestContext context , ISmsSender smsSender, ILogger<HomeController> logger) { _redisConfigure = setting.Value; _testContext = context; _smsSender = smsSender; _logger = logger; } }
3.3 View中使用
@using MilkStone.Services; @model MilkStone.Models.AccountViewModel.LoginViewModel @inject ILoginService<ApplicationUser> loginService <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head></head> <body> @loginService.GetUserName() </body> </html>
3.4 通过 HttpContext来获取实例
HttpContext下有一个RequestedService同样可以用来获取实例对象,不过这种方法一般不推荐。同时要注意GetService<>这是个范型方法,默认如果没有添加Microsoft.Extension.DependencyInjection的using,是不用调用这个方法的。
HttpContext.RequestServices.GetService<ILoginService<ApplicationUser>>();