天天看點

IOC之Autofac

最近在研究Nopcommerce,發現其内部IOC架構使用了autofac,簡單的了解了一下,記錄一下簡單的一些用法。初淺了解下來,有一個很直覺的感受,就是對代碼幾乎沒什麼侵入性,其他性能之類沒有測試,但據說性能極佳。

1. 構造函數注入,如下示例(後面所有說明都是在以下的示例基礎上進行的)

public interface IRepository
    {
        void Update();
    }
    public class FirstRepository : IRepository
    {
        public void Update()
        {
            Console.WriteLine("FirstRepository.Update");
        }
    }
    public class SecondRepository : IRepository
    {
        public void Update()
        {
            Console.WriteLine("SecondRepository.Update");
        }
    }

    public class ConcreteController
    {
        private IRepository _repository;
        public ConcreteController(IRepository repository)
        {
            _repository = repository;
        }

        public void Update()
        {
            _repository.Update();
        }
    }
           

調用如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();//也可直接寫成builder.RegisterType<ConcreteController>()
            builder.RegisterType<FirstRepository>().As<IRepository>();//注冊FirstRepository為IRespository預設實作
            IContainer container = builder.Build();
            //預設以類參數最多的構造方法進行構造,構造參數根據類型從注冊清單中尋找注冊的實作類,此處擷取的是FirstRepository
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

對同一接口多次注冊時,會以最後一次注冊的實作類為預設實作類,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();//也可直接寫成builder.RegisterType<ConcreteController>()
            builder.RegisterType<FirstRepository>().As<IRepository>();
            builder.RegisterType<SecondRepository>().As<IRepository>();//IRepository的預設實作類為SecondRepository
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//SecondRepository.Update
           

如果有這樣的一個需求:如果前面注冊過了接口的實作類,則以之前注冊過的實作類為預設值,如果沒有注冊過則以此次注冊的實作類為預設值,可以通過以下方式實作:

//如果之前存在預設值則以之前注冊的值為預設值,否則以此次注冊的實作類為預設值
            builder.RegisterType<SecondRepository>().As<IRepository>().PreserveExistingDefaults();
            ...
            controller.Update();//FirstRepository.Update
           

在注冊時,可以通過委托的方式,預先指定一個接口的實作類。即采用ContainerBuilder.Register<T>(委托)的方式來進行實作,如下:

ContainerBuilder builder = new ContainerBuilder();
            //通過Register方法,預先指定一個擷取實作的委托來注冊接口的實作類
            builder.Register<IRepository>(r => new FirstRepository());
            IContainer container = builder.Build();
            IRepository repository = container.Resolve<IRepository>();//FirstRepository
            repository.Update();//FirstRepository.Update
           

autofac構造注入時,在通過無參的IContainer.resolver擷取實作類時,會預設通過實作類最多的參數進行構造,構造方法的參數值為該參數類型注冊的實作類。如果該參數值的Type沒有在autofac進行注冊,則直接報錯,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            IContainer container = builder.Build();
            //ConcreteController的構造方法中需要一個IRepository參數,但autofac檢索不到IRepository的實作類,注入失敗,報錯
            //此處不光是針對參數值是引用類型,對于值類型同樣存在這樣的問題,例如把參數換成int i,同樣注入失敗,報錯
            ConcreteController controller = container.Resolve<ConcreteController>();//error
           

對于這樣的問題,可以通過在Resolve 時,自行裝配,如下:

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            IContainer container = builder.Build();
            //之前沒有對ConcreteController構造方法中的參數repository類型IRepository進行注冊,但可在解析提取時自動注入
            //通過NamedParameter,第一個參數指定參數名稱,第二個參數為傳遞的參數值
            ConcreteController controller = container.Resolve<ConcreteController>(new NamedParameter("repository", new FirstRepository()));
            controller.Update();//FirstRepository.Update
           

除了在Resolver時指定參數值,也可以注冊該類時預設指定構造方法的參數值。

ContainerBuilder builder = new ContainerBuilder();
            //在注冊時就先指定構造方法參數的預設值,即為SecondRepository
            builder.RegisterType<ConcreteController>().WithParameter(new NamedParameter("repository", new SecondRepository()));
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//SecondRepository.Update
           

如果在注冊時指定了參數預設值,在Resolver時指定了參數值,則以Resolver指定的參數值為準,如下:

ContainerBuilder builder = new ContainerBuilder();
            //在注冊時就先指定構造方法參數的預設值,即為SecondRepository
            builder.RegisterType<ConcreteController>().WithParameter(new NamedParameter("repository", new SecondRepository()));
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>(new NamedParameter("repository", new FirstRepository()));
            controller.Update();//FirstRepository.Update
           

我們之前談的一直是一個構造方法的情況,假如現在一個實作類有多個構造方法時,autofac會以構造方法參數最多的那個進行注入構造,如下,我們在ConcreteController中新增一個構造方法:

public ConcreteController(IRepository repository, string name)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }

        ConcreteController controller = container.Resolve<ConcreteController>();//沒有調用兩個參數的構造方法
           

運作後,發現autofac并沒有調用ConcreteController中的兩個參數的構造方法進行構造,奇怪了?仔細一瞧,問題就出現在第二個參數string name,還記得之前我們說過,在構造注入時,會檢索string在autofac注冊的實作類,但此時顯示是檢索不到(且該參數沒有設定預設值例如 string name=null),如果ConcreteController隻存在這個構造方法,則會報錯,但是還有另一個單個參數的構造方法,是以在試圖通過最多參數方法注入不成功時,會轉向另一個構造方法,依次下去,直至成功注入為止。我們再換一個方法,新增一個接口,一個類,同時修改ConcreteController第二個構造方法,如下:

public interface IAnotherRepository
    {

    }
    public class AnotherRepository : IAnotherRepository
    {

    }

     public class ConcreteController
    {
        ...
        public ConcreteController(IRepository repository, IAnotherRepository another)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }
        ...
    }
     ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();//此處調用了兩個參數的構造方法
           

autofac預設調用實作類參數最多的構造方法進行構造,但如果實作類中有好幾個參數個數相同的構造方法,autofac如何調用? 例如ConcreteController中有兩個如下的構造方法:

public class ConcreteController
    {
        ...

        public ConcreteController(IRepository repository, string name = null)
        {

        }
        public ConcreteController(IRepository repository, IAnotherRepository another)
        {
            Console.WriteLine("two args contructor was invoked");
            _repository = repository;
        }

        ...
    }

    
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>();
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();//error報錯,autofac不知道調用哪個構造方法
           

這種情況如何解決?可以通過在注冊實作類時,顯示指定哪個構造方法(當存在不同參數的構造方法時,亦可通過此方法來指定不同參數的構造方法作為預設值),如下:

ContainerBuilder builder = new ContainerBuilder();
            //預設指定了public ConcreteController(IRepository repository, string name = null){} 為注入時調用的構造方法
            builder.RegisterType<ConcreteController>().UsingConstructor(typeof(IRepository), typeof(string));
            builder.RegisterType<SecondRepository>().As<IRepository>();
            builder.RegisterType<AnotherRepository>().As<IAnotherRepository>();
            IContainer container = builder.Build();
            //調用public  ConcreteController(IRepository repository, string name = null){}
            ConcreteController controller = container.Resolve<ConcreteController>();
           

2. 一個接口存在多個實作類時

a、ContainerManager.Register<實作類>().Named<接口>(名稱) / IContainer.ResolveNamed<接口>(名稱)

ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<FirstRepository>().Named<IRepository>("first");
            builder.RegisterType<SecondRepository>().Named<IRepository>("second");
            IContainer container = builder.Build();
            IRepository first = container.ResolveNamed<IRepository>("first");//FirstRepository
            IRepository second = container.ResolveNamed<IRepository>("second");//SecondRepository
           

      當普通注冊和Name方式共同存在時,如下:

ContainerBuilder builder = new ContainerBuilder();

            builder.RegisterType<ThirdRepository>().As<IRepository>();
            builder.RegisterType<FirstRepository>().Named<IRepository>("first");
            builder.RegisterType<SecondRepository>().Named<IRepository>("second");

            IContainer container = builder.Build();

            IRepository first = container.ResolveNamed<IRepository>("first");//FirstRepository
            IRepository second = container.ResolveNamed<IRepository>("second");//SecondRepository
            //普通方式和Named方式并不沖突
            IRepository third = container.Resolve<IRepository>();//ThirdRepository
           

b、ContainerManager.Register<實作類>().Keyed<接口>(object) / IContainer.ResolveKeyed<接口>(object)

public enum RepositoryStyle : int
    {
        First,
        Seond,
        Third
    }

      ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ThirdRepository>().As<IRepository>();
            builder.RegisterType<FirstRepository>().Keyed<IRepository>(RepositoryStyle.First);
            builder.RegisterType<SecondRepository>().Keyed<IRepository>(RepositoryStyle.Seond);
            IContainer container = builder.Build();
            IRepository first = container.ResolveKeyed<IRepository>(RepositoryStyle.First);//FirstRepository
            IRepository second = container.ResolveKeyed<IRepository>(RepositoryStyle.Seond);//SecondRepository
            IRepository third = container.Resolve<IRepository>();//ThirdRepository
           

3.  屬性注入

autofac不提倡屬性注入,屬性注入意味着該屬性的set必須向外公開,同時就意味着屬性可人為的在外部進行修改。這樣就可能造成一些隐患(被外部非本意的更改),autofac提倡構造方法進行注入,注入後在外部都不可更改。意味着幹淨,無污染

把ConcreteController的類修改一下,如下:

public class ConcreteController
    {
        public IRepository Repository { get; set; }
        public void Update()
        {
            Repository.Update();
        }
    }

            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConcreteController>().AsSelf();
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            //autofac預設不會進行屬性注入
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//NullReferenceException,提示Repository為null
           

方法1:在注冊實作類時,通過委托指定實作類的構造(構造過程中顯示進行注入),如下:

ContainerBuilder builder = new ContainerBuilder();
            //在注冊ConcreteController時,通過委托建立實作類時顯式的對屬性進行注入
            builder.Register<ConcreteController>(r => new ConcreteController() { Repository = r.Resolve<IRepository>() });
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

 方法2:使用PropertiesAutowired為所有屬性自動注入

ContainerBuilder builder = new ContainerBuilder();
            //自動注入所有屬性
            builder.RegisterType<ConcreteController>().PropertiesAutowired();
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

 方法3:手工為指定屬性注入

ContainerBuilder builder = new ContainerBuilder();
            //手工顯示為指定屬性名稱的屬性注入
            builder.RegisterType<ConcreteController>().WithProperty("Repository", new FirstRepository());
            builder.RegisterType<FirstRepository>().As<IRepository>();
            IContainer container = builder.Build();
            ConcreteController controller = container.Resolve<ConcreteController>();
            controller.Update();//FirstRepository.Update
           

4. 非泛形的Register/RegisterType/Resolve方法,RegisterType<接口>() 相當于 RegisterType(typeof(接口)) 

ContainerBuilder builder = new ContainerBuilder();
            //作用和builder.RegisterType<FirstRepository>().As<IRepository>();一樣的
            builder.RegisterType(typeof(FirstRepository)).As(typeof(IRepository));
            IContainer container = builder.Build();
            //作用和IRepository obj = container.Resolve<IRepository>();
            IRepository obj = container.Resolve(typeof(IRepository)) as IRepository; //FirstRepository
           

5. 開放的泛型類型

public interface IRepository<T>
    {
        void Update(T t);
    }

    public class FirstRepository<T> : IRepository<T>
    {
        public void Update(T t)
        {

            Console.WriteLine("FirstRepository.Update " + t.ToString());
        }
    }
    public class SeondRepository<T> : IRepository<T>
    {
        public void Update(T t)
        {
            Console.WriteLine("SeondRepository.Update " + t.ToString());
        }
    }
     ContainerBuilder builder = new ContainerBuilder();
            //注冊泛形接口時,使用RgisterGeneric方法
            builder.RegisterGeneric(typeof(FirstRepository<>)).As(typeof(IRepository<>));
            IContainer container = builder.Build();

            IRepository<string> repository = container.Resolve<IRepository<string>>();
            repository.Update("hehe"); 
           

6. 自動裝配

   a、自動為某個接口注冊實作類

ContainerBuilder builder = new ContainerBuilder();
            //自動掃描目前程式集中實作了IRepository的實作類,将其中一個實作了IRepository的實作類進行裝配
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).As<IRepository>();
            IContainer container = builder.Build();
            IRepository r = container.Resolve<IRepository>();//傳回其中一個實作了IRepository的類執行個體
           

   b、自動為所有接口注冊實作類

ContainerBuilder builder = new ContainerBuilder();
            //自動掃描目前程式集,并将所有實作了接口的類注冊為該接口的實作類(選取其中的一個實作類)
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();//傳回其中一個實作了IRepository的類執行個體,此處傳回ThirdRepository
            IAnotherRepository obj2 = container.Resolve<IAnotherRepository>();//傳回其中一個實作了IAnotherRepository的類執行個體,此處傳回AnotherRepositor
           

   c、篩選注冊

ContainerBuilder builder = new ContainerBuilder();
            //自動掃描目前程式集,搜尋所有實作了IRepository且類名包含"Second"的類,并将其中的一個注冊為IRepository的實作類
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => t.Name.Contains("Second")).As<IRepository>();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();//傳回SecondRepository
            IAnotherRepository obj2 = container.Resolve<IAnotherRepository>();//error,因為沒有為IAnotherRepository注冊實作類
           

7. 執行個體生命周期

   a、ContainerBuilder.Register<實作類>().As<接口>().InstancePerDependency() 用于控制對象的生命周期,每次加載執行個體時都是建立一個執行個體,預設就是這種方式

ContainerBuilder builder = new ContainerBuilder();
            //傳回的都是建立的執行個體
            builder.RegisterType<FirstRepository>().As<IRepository>().InstancePerDependency();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();
            IRepository obj2 = container.Resolve<IRepository>();
            Console.WriteLine(ReferenceEquals(obj1, obj2));//false
           

   b、ContainerBuilder.Register<實作類>().As<接口>().InstancePerDependency()

ContainerBuilder builder = new ContainerBuilder();
            //傳回的都是同一個執行個體
            builder.RegisterType<FirstRepository>().As<IRepository>().SingleInstance();
            IContainer container = builder.Build();
            IRepository obj1 = container.Resolve<IRepository>();
            IRepository obj2 = container.Resolve<IRepository>();
            Console.WriteLine(ReferenceEquals(obj1, obj2));//true
           

8. MVC中的應用

MVC用于DI的一個類是DependencyResolver,其中的Current和CurrentCache的靜态屬性是一個IDependencyResolver。通過該接口的兩個方法  object GetService(Type serviceType); IEnumerable<object> GetServices(Type serviceType)來擷取實作類。DependencyResolver預設的IDependencyResolver是DefaultDependcyResolver其内部簡單的通過反射建立Type的執行個體。現在我們利用autofac來代替DefaultDependencyResolver。

首先先添加Autofac.Integration.Mvc程式集,其中定義了個實作了IDependencyResolver的類AutofacDependencyResolver,其内部就是使用autofac來實作控制反轉的

/// <summary>
    /// 倉儲接口
    /// </summary>
    public interface IRepository
    {
        IEnumerable<object> Get(string id);
        bool Update(object entity);
        bool Save(object entity);
        bool Delete(object entity);
    }
    /// <summary>
    /// 倉儲實作
    /// </summary>
    public class ConcreteRepository : IRepository
    {
        public IEnumerable<object> Get(string id)
        {
            return new List<object>();
        }

        public bool Update(object entity)
        {
            return true;
        }

        public bool Save(object entity)
        {
            return true;
        }

        public bool Delete(object entity)
        {
            return true;
        }
    }

    
    /// <summary>
    /// 控制器
    /// </summary>
    public class HomeController : Controller
    {
        IRepository repository;

        public HomeController(IRepository repository)
        {
            this.repository = repository;
        }

        public ActionResult Index()
        {

            var list = repository.Get("");//repository為ConcreteRepository
            return View();
        }
    }


    /// <summary>
    /// Global
    /// </summary>
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ContainerBuilder builder = new ContainerBuilder();
            RegisterContainer(builder);
            //Autofac.Integration.Mvc中定義的擴充方法,注冊所有控制器本身
            //類似一個個去注冊Controller,例如 builder.RegisterType<HomeController>().AsSelf();   
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            IContainer container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

        }
        /// <summary>
        /// 注冊
        /// </summary>
        /// <param name="builder"></param>
        private void RegisterContainer(ContainerBuilder builder)
        {
            //可以為所有接口注冊實作類builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();
            builder.RegisterType<ConcreteRepository>().As<IRepository>();
            
        }
    }