前文《Unity2.0容器自動注冊機制》中,介紹了如何在 Unity 2.0 版本中使用 Auto Registration 自動注冊機制。在 Unity 3.0 版本中(2013年),新增了基於約定的自動注冊機制(Registration By Convention),以使 Unity 容器的裝配過程變得更加簡單,並且減少冗余代碼。
Convention over Configuration
Convention over Configuration 是現如今非常流行的設計風格,很多框架都在嘗試采納該風格,包括 ASP.NET MVC。簡要的說就是需要依賴某種預先定義的約定,類似於文件、目錄的命名,類或者系統依據約定做指定的事等。例如,在 ASP.NET MVC 中,所有 Controller 都必須包含 “Controller” 后綴,這樣框架才能找到該 Controller 並進行初始化。
約定風格使開發人員的生活變得輕松多了,我們無需再耗費精力在繁雜的配置上,轉而采用一些簡單的約定規則。
例如,有一個時常出現的模式。我們定義一個類 Foo 來作為接口 IFoo 的默認實現。在匈牙利命名法的編程風格中,標記 “I” 通常是接口的第一個字母。利用這一點,我們可以設定一個命名約定。
1 public interface IConvention 2 { 3 } 4 5 public class Convention : IConvention 6 { 7 }
基於此約定,我們可以掃描整個程序集來查找滿足約定的接口和類,並自動注冊進 Unity 容器。
Registration by Convention
早在2009年,Jeremy Miller 在 StructureMap 2.5 版本中即已引入了基於約定的自動注冊機制,通過擁抱 Convention over Configuration 設計范式來降低使用 Configuration 的開銷。
同年,Derek Greer 為 Unity 2.0 增加了 Convention-based Registration Extension 擴展。而微軟官方直到2013年 Unity 3.0 才增加此功能。
基於約定的自動注冊機制目的在於簡化類型注冊過程,減少類型注冊代碼。比起無數的 “container.RegisterType<IFoo, Foo>()”,可以直接通過掃描程序集,依據一些既定的規則來自動完成類型注冊。
1 using (var container = new UnityContainer()) 2 { 3 container.RegisterTypes( 4 AllClasses.FromAssembliesInBasePath(), 5 WithMappings.FromMatchingInterface, 6 WithName.Default, 7 WithLifetime.ContainerControlled); 8 }
支持的 Conventions
Convention 必須指明哪些可用類型應該被映射進 Unity 容器中。當前的 Unity 3.0 中的可用約定支持如下規則:
- 識別掃描被注冊類型所在的程序集:可以使用提供的程序集列表,使用當前已加載的程序集列表,使用在當前應用程序目錄中的所有程序集列表。可以進一步通過類型的名稱、后綴或者其他規則進行過濾,並且支持 LINQ 語法。這一步准備了可能被注冊的類型列表。默認情況下,所有系統程序集將被自動過濾掉。當然,也不是非要使用幫助類中所提供的類型加載方式,可以使用一個方法來獲取類型的枚舉集合,或者甚至直接使用一個數組。
- 可選項,指定哪些類型(典型的是接口或者抽象類)需要通過容器來映射到步驟 1 中的類型。目前的約定包括:
- 映射哪些遵循命名約定的類型。例如類 Foo 和 接口 IFoo 。
- 映射類型實現的所有接口。
- 映射類型實現的所有接口,並且這些接口必須與類型定義在同一程序集內。
- 可選項,指定是否使用命名注冊(Named Registration)。也可以使用類型的名稱作為注冊名稱。
- 可選項,指定注冊類型時使用哪種生命周期管理器(Lifetime Manager)。
- 可選項,確定是否有類型成員需要注入(Injected Parameter)。
所有約定通過 RegisterTypes 方法來表述:
1 public static IUnityContainer RegisterTypes( 2 this IUnityContainer container, 3 IEnumerable<Type> types, 4 Func<Type, IEnumerable<Type>> getFromTypes = null, 5 Func<Type, string> getName = null, 6 Func<Type, LifetimeManager> getLifetimeManager = null, 7 Func<Type, IEnumerable<InjectionMember>> getInjectionMembers = null, 8 bool overwriteExistingMappings = false);
RegisterTypes 方法簽名描述:
Parameter |
Description |
---|---|
Types |
This parameter is an enumerable collection of types that you want to register with the container. These are the types that you want to register directly or create mappings to. You can create this collection by providing a list of types directly or by using one of the methods of the built-in AllClasses helper class: for example, the method FromLoadedAssemblies loads all of the available types from the currently loaded assemblies. You can use LINQ to filter this enumeration. |
getFromTypes |
This optional parameter identifies the types you want to map from in the container. The built-in WithMappings helper class provides several options for this mapping strategy: for example, theMatchingInterface property creates mappings where there are interfaces and implementations that follow the naming conventionITenant and Tenant. |
getName |
This optional parameter enables you to control whether to create default registrations or named registrations for the types. The built-in helper class WithName, enables you to choose between using default registrations or named registrations that use the type name. |
getLifeTimeManager |
This optional parameter enables you to select from the built-in lifetime managers. |
getInjectionMembers |
This optional parameter enables you to provide definitions for any injection members for the types that you are registering. |
overwriteExistingMappings |
This optional parameter enables you to control how the method behaves if it detects an attempt to overwrite an existing mapping in the Unity container. By default, the RegisterTypes method throws an exception if it detects such an attempt. If this parameter is true, the method silently overwrites an existing mapping with a new one based on the values of the other parameters. |
簡單示例:
1 var container = new UnityContainer(); 2 3 container.AddNewExtension<Interception>(); 4 container.RegisterTypes( 5 AllClasses.FromLoadedAssemblies().Where( 6 t => t.Namespace == "OtherUnitySamples"), 7 WithMappings.FromMatchingInterface, 8 getInjectionMembers: t => new InjectionMember[] 9 { 10 new Interceptor<VirtualMethodInterceptor>(), 11 new InterceptionBehavior<LoggingInterceptionBehavior>() 12 });
自定義約定類
Unity 通過提供注冊約定抽象類 RegistrationConvention 來提供擴展性。
1 public abstract class RegistrationConvention 2 { 3 public abstract Func<Type, IEnumerable<Type>> GetFromTypes(); 4 public abstract Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers(); 5 public abstract Func<Type, LifetimeManager> GetLifetimeManager(); 6 public abstract Func<Type, string> GetName(); 7 public abstract IEnumerable<Type> GetTypes(); 8 }
我們可以自定義實現 RegistrationConvention :
1 class CustomizedConvention : RegistrationConvention 2 { 3 public override Func<Type, IEnumerable<Type>> GetFromTypes() 4 { 5 return t => t.GetInterfaces(); 6 } 7 8 public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers() 9 { 10 return null; 11 } 12 13 public override Func<Type, LifetimeManager> GetLifetimeManager() 14 { 15 return t => new ContainerControlledLifetimeManager(); 16 } 17 18 public override Func<Type, string> GetName() 19 { 20 return t => t.Name; 21 } 22 23 public override IEnumerable<Type> GetTypes() 24 { 25 yield return typeof(Foo); 26 yield return typeof(Bar); 27 } 28 }
1 var container = new UnityContainer(); 2 container.RegisterTypes(new CustomizedConvention());
顯示已注冊類型信息
可以通過顯示枚舉容器的 Registrations 屬性來獲得所有的注冊信息。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var container = new UnityContainer(); 6 container.RegisterTypes(new CustomizedConvention()); 7 8 Console.WriteLine("Container has {0} Registrations:", 9 container.Registrations.Count()); 10 foreach (ContainerRegistration item in container.Registrations) 11 { 12 Console.WriteLine(item.GetMappingAsString()); 13 } 14 15 Console.ReadKey(); 16 } 17 } 18 19 public interface IFoo { } 20 public class Foo : IFoo { } 21 public interface IBar { } 22 public class Bar : IBar { } 23 24 static class ContainerRegistrationsExtension 25 { 26 public static string GetMappingAsString( 27 this ContainerRegistration registration) 28 { 29 string regName, regType, mapTo, lifetime; 30 31 var r = registration.RegisteredType; 32 regType = r.Name + GetGenericArgumentsList(r); 33 34 var m = registration.MappedToType; 35 mapTo = m.Name + GetGenericArgumentsList(m); 36 37 regName = registration.Name ?? "[default]"; 38 39 lifetime = registration.LifetimeManagerType.Name; 40 if (mapTo != regType) 41 { 42 mapTo = " -> " + mapTo; 43 } 44 else 45 { 46 mapTo = string.Empty; 47 } 48 lifetime = lifetime.Substring( 49 0, lifetime.Length - "LifetimeManager".Length); 50 return String.Format( 51 "+ {0}{1} '{2}' {3}", regType, mapTo, regName, lifetime); 52 } 53 54 private static string GetGenericArgumentsList(Type type) 55 { 56 if (type.GetGenericArguments().Length == 0) return string.Empty; 57 string arglist = string.Empty; 58 bool first = true; 59 foreach (Type t in type.GetGenericArguments()) 60 { 61 arglist += first ? t.Name : ", " + t.Name; 62 first = false; 63 if (t.GetGenericArguments().Length > 0) 64 { 65 arglist += GetGenericArgumentsList(t); 66 } 67 } 68 return "<" + arglist + ">"; 69 } 70 }
輸出結果:
參考資料