概述
ASP.NET Core可以说是处处皆注入,本文从基础角度理解一下原生DI容器,及介绍下怎么使用并且如何替换官方提供的默认依赖注入容器。
什么是依赖注入
百度百科中对于依赖注入的定义:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
依赖反转前
那么在依赖反转之前或者叫控制反转之前,直接依赖是怎么工作的呢,这里ClassA直接依赖ClassB,而ClassB又直接依赖ClassC,任何一处的变动都会牵一发而动全身,不符合软件工程的设计原则。
依赖反转后
应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转) 。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。
依赖项反转是生成松散耦合应用程序的关键一环,因为可以将实现详细信息编写为依赖并实现更高级别的抽象,而不是相反 。 因此,生成的应用程序的可测试性、模块化程度以及可维护性更高。 遵循依赖关系反转原则可实现依赖关系注入 。
何谓容器
如果你用过Spring,就知道其庞大而全能的生态圈正是因为有了它包含各种各样的容器来做各种事情,其本质也是一个依赖反转工厂,那么不知道你注意到没有,控制反转后产生依赖注入,这样的工作我们可以手动来做,那么如果注入的服务成千上万呢,那怎么玩呢?那么问题来了,控制反转了,依赖的关系也交给了外部,现在的问题就是依赖太多,我们需要有一个地方来管理所有的依赖,这就是容器的角色。
容器的主要职责有两个:绑定服务与实例之间的关系(控制生命周期)。获取实例并对实例进行管理(创建和销毁)。
ASP.NET Core里依赖注入是怎么实现的
在.Net Core里提供了默认的依赖注入容器IServiceCollection,它是一个轻量级容器。核心组件为两个IServiceCollection和IServiceProvider,IServiceCollection负责注册,IServiceProvider负责提供实例。
使用两个核心组件前导入命名空间Microsoft.Extensions.DependencyInjection.
默认的ServiceCollection有以下三个方法:
IServiceCollection serviceCollection=new ServiceCollection();
#三个方法都是注册实例,只不过实例的生命周期不一样。
#单例模式,只有一个实例
serviceCollection.AddSingleton<ILoginService, EFLoginService>();
#每次请求都是同一个实例,比如EntityFramework.Context
serviceCollection.AddScoped<ILoginService, EFLoginService>();
#每次调用都是不同的实例
serviceCollection.AddTransient<ILoginService, EFLoginService>();
#接口声明
public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
{
}
#默认的ServiceCollection实际上是一个提供了ServiceDescriptor的List。
public class ServiceCollection : IServiceCollection, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor>{private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();public int Count{get{return this._descriptors.Count;}}public bool IsReadOnly{get{return false;}}public ServiceDescriptor this[int index]{get{return this._descriptors[index];}set{this._descriptors[index] = value;}}public void Clear(){this._descriptors.Clear();}public bool Contains(ServiceDescriptor item){return this._descriptors.Contains(item);}public void CopyTo(ServiceDescriptor[] array, int arrayIndex){this._descriptors.CopyTo(array, arrayIndex);}public bool Remove(ServiceDescriptor item){return this._descriptors.Remove(item);}public IEnumerator<ServiceDescriptor> GetEnumerator(){return (IEnumerator<ServiceDescriptor>) this._descriptors.GetEnumerator();}void ICollection<ServiceDescriptor>.Add(ServiceDescriptor item){this._descriptors.Add(item);}IEnumerator IEnumerable.GetEnumerator(){return (IEnumerator) this.GetEnumerator();}public int IndexOf(ServiceDescriptor item){return this._descriptors.IndexOf(item);}public void Insert(int index, ServiceDescriptor item){this._descriptors.Insert(index, item);}public void RemoveAt(int index){this._descriptors.RemoveAt(index);}}
三个方法对应的生命周期值,在枚举ServiceLifeTime中定义:
public enum ServiceLifetime
{Singleton,Scoped,Transient,
}
三个方法确切来说是定义在扩展方法ServiceCollectionServiceExtensions中定义:
#这里我列出个别方法,详细可参看源码
#导入Microsoft.Extensions.DependencyInjection
public static class ServiceCollectionServiceExtensions
{public static IServiceCollection AddTransient(this IServiceCollection services,Type serviceType,Type implementationType){if (services == null)throw new ArgumentNullException(nameof (services));if (serviceType == (Type) null)throw new ArgumentNullException(nameof (serviceType));if (implementationType == (Type) null)throw new ArgumentNullException(nameof (implementationType));//这里注入时指定ServiceLifetime.Transientreturn ServiceCollectionServiceExtensions.Add(services, serviceType, implementationType, ServiceLifetime.Transient);}public static IServiceCollection AddScoped(this IServiceCollection services,Type serviceType,Type implementationType){if (services == null)throw new ArgumentNullException(nameof (services));if (serviceType == (Type) null)throw new ArgumentNullException(nameof (serviceType));if (implementationType == (Type) null)throw new ArgumentNullException(nameof (implementationType));//这里注入时指定ServiceLifetime.Scopedreturn ServiceCollectionServiceExtensions.Add(services, serviceType, implementationType, ServiceLifetime.Scoped);}public static IServiceCollection AddSingleton(this IServiceCollection services,Type serviceType,Type implementationType){if (services == null)throw new ArgumentNullException(nameof (services));if (serviceType == (Type) null)throw new ArgumentNullException(nameof (serviceType));if (implementationType == (Type) null)throw new ArgumentNullException(nameof (implementationType));//这里注入时指定ServiceLifetime.Singletonreturn ServiceCollectionServiceExtensions.Add(services, serviceType, implementationType, ServiceLifetime.Singleton);}
}
ASP.NET Core里依赖注入是怎样运行的
在Startup中初始化
ASP.NET Core在Startup.ConfigureService中注入指定服务,可以从方法参数IServiceCollection中看出,这里还有个方法services.AddMvc(), 这个MVC框架本身自己注入的服务,定义在MvcServiceCollectionExtesnsions类中。
#Startup
public void ConfigureServices(IServiceCollection services)
{services.AddMvc();services.AddSingleton<ILoginService, EFLoginService>();
}
在构造函数中注入
官方推荐在构造器中注入,这里也是为了体现显示依赖。
public class AccountController
{private ILoginService _loginService;public AccountController(ILoginService loginService){_loginService = loginService;}
}
如何替换其他容器
前面提到原生的依赖注入容器只是一个轻量级容器,但是功能真的很有限,比如属性注入、方法注入、子容器、lazy对象初始化支持。为何不好好借鉴一下Spring强大的背景呢,所以这里我们用Autofac替换系统默认的依赖注入容器。先引用命名空间Autofac、Autofac.Extensions.DependencyInjection。我本机环境使用的.Net Core3.0。3.0不能修改直接修改Startup的ConfigureService方法了,直接修改ConfigureService方法返回值会抛出异常ConfigureServices returning an System.IServiceProvider isn't supported. 这里可以参考Autofac文档,已经有说明。
修改Startup
#直接声明方法ConfigureContainer
public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){services.AddControllersWithViews();services.AddMvc();}public void ConfigureContainer(ContainerBuilder containerBuilder){containerBuilder.RegisterType<EFLoginService>().As<ILoginService>();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");app.UseHsts();}app.UseStaticFiles();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");});}}
修改Program
#导入命名空间Autofac.Extensions.DependencyInjections,然后调用UseServiceProviderFactory
public class Program{public static void Main(string[] args){CreateHostBuilder(args).Build().Run();}public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }).UseServiceProviderFactory(new AutofacServiceProviderFactory());}
参考链接
https://docs.microsoft.com/zh-cn/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion
https://www.cnblogs.com/loogn/p/10566510.html
https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html