通過創建ContainerBuilder並配置暴露的service(接口或者類型)來使用Autofac注冊我們的組件。
組件(Components) 可以通過反射, 對象實例,或者lambda表達式來創建. ContainerBuilder有一系列的Register()方法來實現組件的注冊。
ContainerBuilder中每個組件都能通過As()方法來暴露他們一個或多個service.
// Create the builder with which components/services are registered. var builder = new ContainerBuilder(); // Register types that expose interfaces... builder.RegisterType<ConsoleLogger>().As<ILogger>(); // Register instances of objects you create... var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>(); // Register expressions that execute to create objects... builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>(); // Build the container to finalize registrations // and prepare for object resolution. var container = builder.Build(); // Now you can resolve services using Autofac. For example, // this line will execute the lambda expression registered // to the IConfigReader service. using(var scope = container.BeginLifetimeScope()) { var reader = container.Resolve<IConfigReader>(); }
反射組件 Reflection Components
通過類型注冊
通過反射生成組件最典型的方法是通過類型注冊:
var builder = new ContainerBuilder(); builder.RegisterType<ConsoleLogger>(); builder.RegisterType(typeof(ConfigReader));
當使用基於反射的組件時,Autofac會自動使用容器中最可能獲取到的參數來構造實例你的類。
例如,你的類有3個構造函數
public class MyComponent { public MyComponent() { /* ... */ } public MyComponent(ILogger logger) { /* ... */ } public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ } }
現在使用如下方法注冊你的組件:
var builder = new ContainerBuilder(); builder.RegisterType<MyComponent>(); builder.RegisterType<ConsoleLogger>().As<ILogger>(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope()) { var component = container.Resolve<MyComponent>(); }
當你解析你的組件時,Autofac發現你已經注冊了ILogger,但是沒有注冊IConfigReader。在這個例子中,第二個構造函數會被執行,因為容器中具有最符合的參數。
注意:任何你注冊的Type必須都是實類型,這意味着你不能直接注冊抽象類或者接口。應為在解析組件時,對象可能會被new實例化,而接口或者抽象類是無法直接實例化的。
指定構造函數 Specifying a Constructor
你可以通過方法UsingConstructor手動指定使用哪個構造函數,其參數為指定構造函數的參數類型。
var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>();
注意你提供的參數必須能在容器中獲取,不然會出現錯誤。
實例組件 Instance Components
在很多情況下,你可能想要預先生成對象實例並將它注冊到容器中。你可以通過使用RegisterInstance方法來實現:
var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>();
當你這么做的時候,一些事情需要被考慮到。Autofac會自動處理對象的釋放,但是你很可能想自己控制對象的生命周期,而不是讓Autofac調用對象Dispose方法。在這種情況下,你需要使用ExternallyOwned方法:
var output = new StringWriter(); builder.RegisterInstance(output) .As<TextWriter>() .ExternallyOwned();
當我們集成Autofac到現有項目中時,可能會存在容器中一些組件用到的對象的單例寫法。RegisterInstance()也用來處理這種情況,你可以通過容器來注冊它們,而不是直接使用它們:
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
這確保了單例最后會被排除,並使用容器托管的對象來替代他。
表達式組件 Lambda Expression Components
反射是創建組件相當好的默認選擇,盡管當組件創建邏輯超出簡單構造函數調用后,事情可能會變混亂。
Autofac支持通過委托或者Lambda表達式來創建組件:
builder.Register(c => new A(c.Resolve<B>()));
提供的參數c是一個組件上下文對象(IComponentContext),在上下文中創建組件。你可以通過它從容器中解析出其他的值來輔助創建你的組件。使用它而不是使用閉包去訪問容器是很重要的,為了層疊容器能夠被正確支持。
使用此上下文參數能夠滿足額外的依賴項。在這個例子中,類型A構造函數要求的B類型參數可能還會依賴其他類型參數。表達式創建的組件暴露的默認服務是通過表達式返回類型來推斷的。
下面會給出一些例子:
復雜參數
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
屬性注入
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
通過參數值選擇實現方式
builder.Register<CreditCard>( (c, p) => { var accountId = p.Named<string>("accountId"); if (accountId.StartsWith("9")) { return new GoldCard(accountId); } else { return new StandardCard(accountId); } });
在這個例子中,CreditCard被兩個類實現,分別是GoldenCard和StandardCard,哪種類型被實例取決於輸入的卡號。
參數通過可選構造參數p來傳入,注冊方式可能像這樣:
var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));
范型組件
Autofac支持范型,通過使用RegisterGeneric()方法:
builder.RegisterGeneric(typeof(NHibernateRepository<>)) .As(typeof(IRepository<>)) .InstancePerLifetimeScope();
當一個匹配的Service類型被請求時,容器會自動映射它到一個相近的實現類型:
// Autofac將會返回NHibernateRepository<Task>類型 var tasks = container.Resolve<IRepository<Task>>();
指定類型服務的注冊將會覆蓋掉范型版本。
服務 vs 組件 Services vs. Components
當注冊組件時,我們需要告訴Autofac組件暴露了哪種服務。大多數情況下,會暴露組件自身的類型。
// This exposes the service "CallLogger" builder.RegisterType<CallLogger>();
組件僅能被其暴露的服務來解析出實例,這就意味着如下:
// This will work because the component // exposes the type by default: scope.Resolve<CallLogger>(); // 這個將會失敗 // tell the registration to also expose // the ILogger interface on CallLogger: scope.Resolve<ILogger>();
你也可以使用任何數量的服務來暴露你的組件:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>();
你可以通過你暴露的服務來解析出組件實例,但這同時意味着默認服務(組件自身類型)將被覆蓋:
// These will both work because we exposed // the appropriate services in the registration: scope.Resolve<ILogger>(); scope.Resolve<ICallInterceptor>(); // 失敗 // 默認服務被覆蓋 scope.Resolve<CallLogger>();
如果你想為組件暴露一系列服務,同時自身類型仍然可用,你需要使用AsSelf方法:
builder.RegisterType<CallLogger>() .AsSelf() .As<ILogger>() .As<ICallInterceptor>();
現在所有服務均可以解析:
// These will all work because we exposed // the appropriate services in the registration: scope.Resolve<ILogger>(); scope.Resolve<ICallInterceptor>(); scope.Resolve<CallLogger>();
默認注冊 Default Registrations
如果有多個提供相同service的組件被注冊,Autofac默認使用最后注冊的組件。
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();
在這個例子中,FileLogger將會成為ILogger的默認組件提供者,因為它是最后注冊的。
為了改寫這種行為,使用PreserveExistingDefaults()來修改:
builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();
在這種情況下,ConsoleLogger將會成為默認組件提供者。
配置文件注冊 Configuration of Registrations
你可以使用定義的XML或者代碼模塊(Module)來實現孕事批量注冊或者更改。也可以使用Autofac modules進行一些動態注冊生成或者條件注冊邏輯。具體請看:http://autofac.readthedocs.org/en/latest/configuration/index.html
動態注冊 Dynamically-Provided Registrations
Autofac modules是最簡單的方式來引入動態注冊邏輯或簡單交叉特性。例如,你可以使用module動態地附加一個Log4net實例到解析出的一個service上。請看:http://autofac.readthedocs.org/en/latest/examples/log4net.html
