最近在研究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>();
}
}