其实关于IOC,DI已经有了很多的文章,但是自己在使用中还是有很多困惑,而且相信自己使用下,印象还是会比较深刻的
关于这段时间一直在学习.net core,但是这篇文章是比较重要的,也是我自己觉得学习的东西非常多的,也得到了大神的指教,在这里和大家分享下
什么是IOC?
在做程序设计时,考虑到程序的耦合性,高扩展等问题,还是尽量需要将程序抽象化,各层的业务不再有实际的依赖关系,全部依赖于抽象也就是接口,在这种设计的情况下,接口的具体实现的创建工作最好交由IOC框架来做,或者自己扩展一个Ioc架构,完成一个构建工厂的功能,其实ico的工作就是一个产生对象的工厂,依赖于反射的技术
下面讲讲.net core,下面直接程序为core了,core框架内部包含自己的ioc框架,本文从两方面来讲,首先是自带的ioc,第二是第三方ioc(actofac),文章后面有源码
一.自带的IOC
1.定义接口以及实现
/// <summary> /// 动物类 /// </summary> public interface Animal { string Call(); } /// <summary> /// 狗狗类 /// </summary> public class Dog : Animal { public Dog() { this.Name = Guid.NewGuid().ToString(); } public string Name { get; set; } public string Call() { return this.Name; } }
2.注册到ioc中
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddTransient<Animal, Dog>(); //services.AddScoped<Animal, Dog>(); //services.AddSingleton<Animal, Dog>(); }
该方法在Startup.cs
3.在api中注入,并使用
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace CM.NetCoreIOC.Controllers { public class HomeController : Controller { Animal animal1; Animal animal2; public HomeController(Animal animal1, Animal animal2) { this.animal1 = animal1; this.animal2 = animal2; } public string Index() { return $"Animal 1 Name:{animal1.Call()} Animal 2 Name:{animal2.Call()}"; } } }
注意这里的需要提供构造函数,将需要注入的作为构造函数参数,访问接口得到结果,刷新下页面,然后两次结果不一样,而且每次的Animal1与Animal2不一样
这里的两个Animal不一样,什么原因?是因为我们注册的选择方法决定的,services.AddTransient,那有没有其他选项呢?有,如下,我们一个个来做实验
用Singleton注册
services.AddMvc(); //services.AddTransient<Animal, Dog>(); services.AddScoped<Animal, Dog>(); //services.AddSingleton<Animal, Dog>();
结果:
看出区别了吧,两次结果不一样,但是每次请求的Animal 1 与Animal2一样啊,是不是发现有了不同的应用场景,嘿嘿
用AddSingleton注册
services.AddMvc(); //services.AddTransient<Animal, Dog>(); //services.AddScoped<Animal, Dog>(); services.AddSingleton<Animal, Dog>();
是不是有发现了点什么?单例模式,创建单例的方式更加简单了
默认的使用其实很简单,也还比较方便
二.第三方IOC(autofac)
1.添加Nuget引用 Autofac ,Autofac.Extensions.DependencyInjection
2.修改Startup.cs文件, ConfigureServices 方法,从void变为 IServiceProvider
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); var builder = new ContainerBuilder(); builder.RegisterType(typeof(Dog)).As(typeof(Animal)) .InstancePerLifetimeScope() .PropertiesAutowired(); builder.Populate(services); return new AutofacServiceProvider(builder.Build()); }
运行得到结果:
两次不一样,每次的对象却是一样的,达到了我们的逾期效果,这里大家不知道有没有类似的疑问?为什么可以做到?
官网的说明,想要获取依赖注入的对象实例,有两种方法,自己也做了实验,如下,修改Startup.cs,修改Configure方法
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); var animal1 = ActivatorUtilities.GetServiceOrCreateInstance(app.ApplicationServices, typeof(Animal)); var animal2 = app.ApplicationServices.GetService(typeof(Animal)); }
调试看看两个对象animal1与animal2
两者内部还是依赖于IServiceProvider接口来实现的,autofac写了一个AutofacServiceProvider实现了IServiceProvider,从而替换掉内部默认的ServiceProvider,所以达到了效果
一直没有提的是core下面的ioc不支持属性注入,只能通过构造函数注入,也就是说core默认的ioc,你要注入,就要把参数全部写在构造函数的参数中,但是autofac是支持属性注入的,PropertiesAutowired就是已属性方式注入,那我们来试试,把HomeController的构造函数干掉看看
报错了,根本没有注入两个属性,怎么回事?.....不对我们根本还没注册Controller到autofac中,为什么会有对象自己生成啊,其实这里的情况是比较特殊的,如果我们这时候不是直接在Controller层做实验,其实已经完成了属性的注册了,因为这时候Controller的创建工作还不是autofac做的,从我没有注册就可以看出来,那是什么原因啊?我先注册看看
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); var builder = new ContainerBuilder(); builder.RegisterType(typeof(Dog)).As(typeof(Animal)) .InstancePerLifetimeScope() .PropertiesAutowired(); builder.RegisterType(typeof(HomeController)) .InstancePerLifetimeScope() .PropertiesAutowired(); builder.Populate(services); return new AutofacServiceProvider(builder.Build()); }
还是一样报错,开始查资料了,不是说autofac可以属性注入吗?
查了资料之后发现需要在ConfigureServices 方法加入一句代码 services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());这样才真正的替换为autufac,才支持属性注入
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); services.AddMvc(); var builder = new ContainerBuilder(); builder.RegisterType(typeof(Dog)).As(typeof(Animal)) .InstancePerLifetimeScope() .PropertiesAutowired(); builder.RegisterType(typeof(HomeController)) .InstancePerLifetimeScope() .PropertiesAutowired(); builder.Populate(services); return new AutofacServiceProvider(builder.Build()); }
运行效果:
终于成功了,但是我的Controller也做了相应的修改的
public class HomeController : Controller { public Animal animal1 { get; set; } public Animal animal2 { get; set; } //public HomeController(Animal animal1, Animal animal2) //{ // this.animal1 = animal1; // this.animal2 = animal2; //} public string Index() { return $"Animal 1 Name:{animal1.Call()} Animal 2 Name:{animal2.Call()}"; } }
属性必须提供get;set;方法,必须是public
回到上面的问题,必须要添加 services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()) ,这里的替换方法其实来源于
services.AddMvc().AddControllersAsServices();
AddControllerAsServices 源码
public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) { var feature = new ControllerFeature(); builder.PartManager.PopulateFeature(feature); foreach (var controller in feature.Controllers.Select(c => c.AsType())) { builder.Services.TryAddTransient(controller, controller); } builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); return builder; }
其实内部就是就是将IControllerAcivator替换为ServiceBasedControllerActivator,
通过查看源代码ASP.NET Core默认使用DefaultControllerActivator类对Controller进行创建工作;但是找到这个类的Create函数发布它其实调用的是ActivatorUtilities来创建对象的。前面也说过这个的话,在创建类型对象时,IServiceProvdier只负责对构造器中的参数进行查找注入,创建对象的操作还是由ActivatorUtilities来create出来的,这样也就没用利用上autofac替换的ServiceProvider,也就是说ActivatorUtilities并没有扩展点来使用我们提供的方法进行替换,所以才造成了无法注入的问题。
所以需要把Controller的创建权转接到autofac,把IControllerAcivator替换为ServiceBasedControllerActivator就可以了?下面是ServiceBasedControllerActivator的Create方法
public object Create(ControllerContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); }
这里的RequestServices就是IServiceProvider,所以到这里终于明白了,为什么一句代码就接管了controller的创建
至此,.net core ioc就写完了,但是autofac的使用以及ioc的内容还有很多东西要学习,将在其他文章来学习.