其实关于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的内容还有很多东西要学习,将在其他文章来学习.
来源:oschina
链接:https://my.oschina.net/u/4258124/blog/4041510