Last Saturday(04/04/09), I had the fortune to attend the Pittsburgh .NET Code Camp(
http://codecamppgh.com/codecamp.aspx) and present my session on the Unity Application Block- Dependency Injection and Inversion of Control. There were several excellent questions posed to me by attendees (who seemed to be really thinking about how to use Unity and dependency injection in their apps). However, the one question that really caused me to dig a little deeper into the Unity API was whether it is possible to register types in the Unity container and then programmatically specify which constructor will be used when the object is resolved?
By design, Unity will choose the constructor with the most parameters when more than one constructor is available (unless a constructor is annotated with the [InjectionConstructor] attribute). However, after a little digging, I determined that by utilizing the Unity Fluent Interface, it is possible to resolve for a specific interface and have a specific constructor called when the object is created.
5 using Microsoft.Practices.Unity;
6
7 namespace ConstructorSelectionFluentInterface
8 {
9 classProgram
10 {
11 staticvoid Main(string[] args)
12 {
13 IUnityContainer container = newUnityContainer();
14
15 IMapView mapView = newMapView();
16 ILogger logger = newConsoleLogger();
17
18 container.RegisterType<IMapDataSource, MapDataSource>("One").Configure<InjectedMembers>().ConfigureInjectionFor<MapDataSource>("One", newInjectionConstructor(mapView));
19
20 container.RegisterType<IMapDataSource, MapDataSource>("Two").Configure<InjectedMembers>().ConfigureInjectionFor<MapDataSource>("Two", newInjectionConstructor(mapView, logger));
21
22 var mapDataSource = container.Resolve<IMapDataSource>("One");
23 mapDataSource.GetMapData();
24
25 mapDataSource = container.Resolve<IMapDataSource>("Two");
26 mapDataSource.GetMapData();
27
28 mapDataSource = container.Resolve<IMapDataSource>("One");
29 mapDataSource.GetMapData();
30
31 }
32 }
33 }
Using the Unity Fluent Interface, it is possible to specify the parameters to be injected into the constructor for a given type. The key, however, is to provide specific names when executing the registration. This will provide the necessary identification so that later when the objects are resolved, the names can be used to not only specify the object requested, but also which constructor will be used when instantiating the object.
7 namespace ConstructorSelectionFluentInterface
8 {
9 publicinterfaceILogger
10 {
11 void Log(string value);
12 }
13 }
6 namespace ConstructorSelectionFluentInterface
7 {
8 publicinterfaceIMapDataSource
9 {
10 void GetMapData();
11 }
12 }
6 namespace ConstructorSelectionFluentInterface
7 {
8 publicinterfaceIMapView
9 {
10 }
11 }
7 namespace ConstructorSelectionFluentInterface
8 {
9 publicclassConsoleLogger:ILogger
10 {
11
12 #region ILogger Members
13
14 publicvoid Log(string value)
15 {
16 Console.WriteLine(value);
17 }
18
19 #endregion
20 }
21 }
6 namespace ConstructorSelectionFluentInterface
7 {
8 publicclassMapDataSource : IMapDataSource
9 {
10 privateILogger logger;
11 privateIMapView mapView;
12
13 public MapDataSource()
14 {
15
16 }
17
18 public MapDataSource(IMapView mapView)
19 {
20 this.mapView = mapView;
21 }
22
23 public MapDataSource(IMapView mapView, ILogger logger)
24 {
25 this.mapView = mapView;
26 this.logger = logger;
27
28 }
29
30 #region IMapDataSource Members
31
32 publicvoid GetMapData()
33 {
34 if (this.logger != null)
35 this.logger.Log("Logger is not null");
36 else
37 Console.WriteLine("logger is null!");
38 }
39
40 #endregion
41 }
42 }
6 namespace ConstructorSelectionFluentInterface
7 {
8 publicclassMapView : IMapView
9 {
10 }
11 }
As an aside, this technique is also useful in the event that you decide not to use Unity any longer since there are no Unity specific attributes in the class.
While I tend to favor the registration of types in a more simplistic and consistent manner, it’s nice to see that Unity provides viable options for developers who are thinking “out of the box.”
Hope this helps,
John Blumenauer