asp.net core 四 IOC&DI Autofac

时间秒杀一切 提交于 2020-11-28 04:32:13
其实关于IOC,DI已经有了很多的文章,但是自己在使用中还是有很多困惑,而且相信自己使用下,印象还是会比较深刻的
关于这段时间一直在学习.net core,但是这篇文章是比较重要的,也是我自己觉得学习的东西非常多的,也得到了大神的指教,在这里和大家分享下
什么是IOC?
  控制反转(Inversion of Control,英文缩写为IoC)把创建对象的权利交给框架,是框架的重要特征,并非 面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称 DI)和依赖查找(Dependency Lookup),上面的来源于百度
  在做程序设计时,考虑到程序的耦合性,高扩展等问题,还是尽量需要将程序抽象化,各层的业务不再有实际的依赖关系,全部依赖于抽象也就是接口,在这种设计的情况下,接口的具体实现的创建工作最好交由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的内容还有很多东西要学习,将在其他文章来学习.
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!