IoC containers are great at reducing complex dependencies in an application and the Dependency Injection design pattern has existed for quite some time. Although IoC containers are quite popular in the Java world but it’s taken some time for good IoC containers to emerge for .Net. Even MS has jumped on the bandwagon and is showing some love for IoC by giving us Unity as an IoC container.
Today there are many, many IoC containers to choose from. That’s the one thing I really like about the ALT.Net community, varied implementations and depth of choice that allows us to pick what implementation really fits our need. We have excellent IoC containers like Castle Windsor, StructureMap, NInject, Spring.Net, Unity… the list goes on and on. You can take a look at Scott Hanselman’s list of IoC containers available for .Net to get an idea of the depth of choice available: List of .NET Dependency Injection Containers (IOC)
I have more or less settled for Castle Windsor as my choice of IoC container for my projects and have used it quite a lot but recently I have been interested in stuff that Jeremy Miller has been doing to StructureMap. Some of the deep functionality added to StructureMap and the ability to do some really neat logging and debugging is very interesting. The fluent interface of StructureMap is also quite clean and something I would really like to use since I’m quite sick of XML (I know Windsor has a fluent interface and am aware of Binsdor, Aynde’s awesome DSL implementation for specifying Windsor container configuration. I cannot use Binsor quite yet in our projects because of external dependencies to Boo and although Windsor has a pretty good fluent interface I like the idea of the Registry in StructureMap)
I have been toying around with the preview release, 2.4.9, of StructureMap and have been itching to try it out. But then I realized that I need a way to insulate my application from the IoC container choice I make, well since the application shouldn’t really have to know the concrete IoC container that will be used. All the application should care about is getting a IoC container component which provides Dependency Injection semantics.
Hence the need for a wrapper that the application will use as a generic IoC container. The wrapper will encapsulate the IoC container of our choice, allowing us to swap out IoC container implementations if we need to. So in this post I’m going to provide an implementation of this wrapper… ingeniously called IoC :)
Some foundation work before we defining the IoC wrapper:
Okay, before I start on the IoC wrapper, I need some infrastructure work done. Ideally when application starts, the IoC container is created and all components are registered at that time. This IoC container instance then is stored for the life time of the application. If our project is a web site then the IoC container instance should be stored in the HttpApplication. Now I must admit the thought of this component came from Aynde’s implementation of LocalData in Rhino Commons (https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-commons/Rhino.Commons.Clr/LocalDataImpl/LocalData.cs), but the LocalData implementation doesn’t allow me to store information on the HttpApplication level and uses ThreadStatic to store thread speciffic data.
That won’t work for me since I want to use something similar to LocalData to store the IoC container instance on either the application level, i.e. App Domain level if it’s a client app or the HttpApplication if its a web site, or at the thread / pre-request level. To tackle this scenario I started out by first implementing a Storage class:
13 public abstract class Storage
14 {
15 #region methods
16 /// <summary>
17 /// Gets the data stored with the specified key in the storage
18 /// </summary>
19 /// <typeparam name="T">The type of data to get.</typeparam>
20 /// <param name="key">object. The key that uniquely identifies the data to retrieve.</param>
21 /// <returns>A <typeparamref name="T"/> instance or null if not found.</returns>
22 public T Get<T>(object key)
23 {
24 throw new NotImplementedException();
25 }
26
27 /// <summary>
28 /// Adds or updates the data specified with the key in the Storage
29 /// </summary>
30 /// <typeparam name="T">The type of data to add or update.</typeparam>
31 /// <param name="key">object. The key that uniquely identifies the data being stored.</param>
32 /// <param name="value"><typeparamref name="T"/>. The value to add or update.</param>
33 public void Set<T>(object key, T value)
34 {
35 throw new NotImplementedException();
36 }
37
38 /// <summary>
39 /// Checks whether the Storage contains data with the specified key.
40 /// </summary>
41 /// <param name="key">object. The unique key to check for.</param>
42 /// <returns>True if data specified with the key was found, else false.</returns>
43 public bool Contains(object key)
44 {
45 throw new NotImplementedException();
46 }
47
48 /// <summary>
49 /// Removes the data specified with the key in Storage
50 /// </summary>
51 /// <param name="key"></param>
52 /// <returns></returns>
53 public void Remove(object key)
54 {
55 throw new NotImplementedException();
56 }
57
58 /// <summary>
59 /// Removes all data in Storage
60 /// </summary>
61 public void Clear()
62 {
63 throw new NotImplementedException();
64 }
65 #endregion
66 }
So the Storage class is an abstract class with standard methods for adding, removing and clearing items from the storage. The idea is that the client will request the type of storage that it wants to use and then use the above methods to manipulate the storage. The following properties tell the Storage class what type of Storage instance is the client expecting to use:
15 #region properties
16 /// <summary>
17 /// Gets whether the current application is a web application.
18 /// </summary>
19 /// <value>bool. True if the current application is a web application.</value>
20 public static bool IsWebApplication
21 {
22 get { return System.Web.HttpContext.Current != null; }
23 }
24
25 /// <summary>
26 /// Gets a <see cref="Storage"/> implementation that can be used to store application
27 /// sepcific data for the current thread / request.
28 /// </summary>
29 public static Storage Local
30 {
31 get
32 {
33 return null;
34 }
35 }
36
37 /// <summary>
38 /// Gets a <see cref="Storage"/> instance that can be used to store application specific
39 /// data for the applicatuion.
40 /// </summary>
41 public static Storage Application
42 {
43 get
44 {
45 return null;
46 }
47 }
48
49 /// <summary>
50 /// Gets a <see cref="Storage"/> instance that can be used to store application sepcific
51 /// data in the current session.
52 /// </summary>
53 public static Storage Session
54 {
55 get
56 {
57 return null;
58 }
59 }
60 #endregion
For now the properties return null and I have added a IsWebApplication properties that would allow consumers to and implementations of Storage to know the current application is a web site.
Now, the way I have implemented the Add, Remove, Clear methods on Storage is to add a abstract GetInternalHashtable method that will be implemented by specific implementations of the Storage class, in this case Application, Local and Session, and then the Hashtable returned by this method will then be manipulated in the Add, Remove, Clear methods. Below is the implementation of the methods:
109 #region methods
110 /// <summary>
111 /// Gets the data stored with the specified key in <see cref="Storage"/>.
112 /// </summary>
113 /// <typeparam name="T">The type of data to get.</typeparam>
114 /// <param name="key">object. The key that uniquely identifies the data to retrieve.</param>
115 /// <returns>A <typeparamref name="T"/> instance or null if not found.</returns>
116 public T Get<T>(object key)
117 {
118 if (UseLocking)
119 {
120 lock (LockInstance)
121 return (T)GetInternalHashtable()[key];
122 }
123 return (T)GetInternalHashtable()[key];
124 }
125
126 /// <summary>
127 /// Adds or updates the data specified with the key in <see cref="Storage"/>.
128 /// </summary>
129 /// <typeparam name="T">The type of data to add or update.</typeparam>
130 /// <param name="key">object. The key that uniquely identifies the data being stored.</param>
131 /// <param name="value"><typeparamref name="T"/>. The value to add or update.</param>
132 public void Set<T>(object key, T value)
133 {
134 if (UseLocking)
135 {
136 lock (LockInstance)
137 GetInternalHashtable()[key] = value;
138 }
139 else
140 GetInternalHashtable()[key] = value;
141 }
142
143 /// <summary>
144 /// Checks whether the <see cref="AppStorage"/> contains data with the specified key.
145 /// </summary>
146 /// <param name="key">object. The unique key to check for.</param>
147 /// <returns>True if data specified with the key was found, else false.</returns>
148 public bool Contains(object key)
149 {
150 if (UseLocking)
151 {
152 lock (LockInstance)
153 return GetInternalHashtable().ContainsKey(key);
154 }
155 return GetInternalHashtable().ContainsKey(key);
156 }
157
158 /// <summary>
159 /// Removes the data specified with the key in <see cref="Storage"/>.
160 /// </summary>
161 /// <param name="key"></param>
162 /// <returns></returns>
163 public void Remove(object key)
164 {
165 if (UseLocking)
166 {
167 lock (LockInstance)
168 GetInternalHashtable().Remove(key);
169 }
170 else
171 GetInternalHashtable().Remove(key);
172 }
173
174 /// <summary>
175 /// Removes all data in <see cref="Storage"/>.
176 /// </summary>
177 public void Clear()
178 {
179 if (UseLocking)
180 {
181 lock (LockInstance)
182 GetInternalHashtable().Clear();
183 }
184 else
185 GetInternalHashtable().Clear();
186 }
187 #endregion
188
189 #region abstract members
190 /// <summary>
191 /// When overriden, tells whether the <see cref="Storage"/> uses locking when retrieving and setting values.
192 /// </summary>
193 protected abstract bool UseLocking { get; }
194
195 /// <summary>
196 /// Gets the object used by the <see cref="Storage"/> for locking when retrieving and setting values.
197 /// </summary>
198 protected abstract object LockInstance { get; }
199
200 ///<summary>
201 /// When overriden by a sub class, provides the internal Hashtable used to store and retrieve
202 /// data.
203 ///</summary>
204 ///<returns></returns>
205 protected abstract Hashtable GetInternalHashtable();
206 #endregion
So the above methods first ask the implementation whether locking is required for thread synchronization, which would be required if you are manipulating at the application scope, i.e. App Domain or HttpApplication. If locking is required then it asks the implementation to provide a object that Storage can use for locking and then uses the Hashtable returned by the GetInternalHashtable method.
Now to bring Storage full circle the next step is to provide specific implementations of Storage that will be returned in Local, Application and Session properties. Below is the complete code for the Storage class and it’s implementation (Also available in Rhinestone’s source code).
Storage.cs
25 public abstract class Storage
26 {
27 #region fields
28 private static AppStorage _appStorage;
29 protected static readonly object AppStorageLock = new object();
30
31 private static LocalStorage _localStorage;
32 protected static readonly object LocalStorageLock = new object();
33
34 private static SessionStorage _sessionStorage;
35 protected static readonly object SessionStorageLock = new object();
36 #endregion
37
38 #region properties
39 /// <summary>
40 /// Gets whether the current application is a web application.
41 /// </summary>
42 /// <value>bool. True if the current application is a web application.</value>
43 public static bool IsWebApplication
44 {
45 get { return System.Web.HttpContext.Current != null; }
46 }
47
48 /// <summary>
49 /// Gets a <see cref="Storage"/> implementation that can be used to store application
50 /// sepcific data for the current thread / request.
51 /// </summary>
52 public static Storage Local
53 {
54 get
55 {
56 if (_localStorage == null)
57 {
58 lock (LocalStorageLock)
59 {
60 if (_localStorage == null)
61 _localStorage = new LocalStorage();
62 }
63 }
64 return _localStorage;
65 }
66 }
67
68 /// <summary>
69 /// Gets a <see cref="Storage"/> instance that can be used to store application specific
70 /// data for the applicatuion.
71 /// </summary>
72 public static Storage Application
73 {
74 get
75 {
76 if (_appStorage == null)
77 {
78 lock (AppStorageLock)
79 {
80 if (_appStorage == null)
81 _appStorage = new AppStorage();
82 }
83 }
84 return _appStorage;
85 }
86 }
87
88 /// <summary>
89 /// Gets a <see cref="Storage"/> instance that can be used to store application sepcific
90 /// data in the current session.
91 /// </summary>
92 public static Storage Session
93 {
94 get
95 {
96 if (_sessionStorage == null)
97 {
98 lock (SessionStorageLock)
99 {
100 if (_sessionStorage == null)
101 _sessionStorage = new SessionStorage();
102 }
103 }
104 return _sessionStorage;
105 }
106 }
107 #endregion
108
109 #region methods
110 /// <summary>
111 /// Gets the data stored with the specified key in <see cref="Storage"/>.
112 /// </summary>
113 /// <typeparam name="T">The type of data to get.</typeparam>
114 /// <param name="key">object. The key that uniquely identifies the data to retrieve.</param>
115 /// <returns>A <typeparamref name="T"/> instance or null if not found.</returns>
116 public T Get<T>(object key)
117 {
118 if (UseLocking)
119 {
120 lock (LockInstance)
121 return (T)GetInternalHashtable()[key];
122 }
123 return (T)GetInternalHashtable()[key];
124 }
125
126 /// <summary>
127 /// Adds or updates the data specified with the key in <see cref="Storage"/>.
128 /// </summary>
129 /// <typeparam name="T">The type of data to add or update.</typeparam>
130 /// <param name="key">object. The key that uniquely identifies the data being stored.</param>
131 /// <param name="value"><typeparamref name="T"/>. The value to add or update.</param>
132 public void Set<T>(object key, T value)
133 {
134 if (UseLocking)
135 {
136 lock (LockInstance)
137 GetInternalHashtable()[key] = value;
138 }
139 else
140 GetInternalHashtable()[key] = value;
141 }
142
143 /// <summary>
144 /// Checks whether the <see cref="AppStorage"/> contains data with the specified key.
145 /// </summary>
146 /// <param name="key">object. The unique key to check for.</param>
147 /// <returns>True if data specified with the key was found, else false.</returns>
148 public bool Contains(object key)
149 {
150 if (UseLocking)
151 {
152 lock (LockInstance)
153 return GetInternalHashtable().ContainsKey(key);
154 }
155 return GetInternalHashtable().ContainsKey(key);
156 }
157
158 /// <summary>
159 /// Removes the data specified with the key in <see cref="Storage"/>.
160 /// </summary>
161 /// <param name="key"></param>
162 /// <returns></returns>
163 public void Remove(object key)
164 {
165 if (UseLocking)
166 {
167 lock (LockInstance)
168 GetInternalHashtable().Remove(key);
169 }
170 else
171 GetInternalHashtable().Remove(key);
172 }
173
174 /// <summary>
175 /// Removes all data in <see cref="Storage"/>.
176 /// </summary>
177 public void Clear()
178 {
179 if (UseLocking)
180 {
181 lock (LockInstance)
182 GetInternalHashtable().Clear();
183 }
184 else
185 GetInternalHashtable().Clear();
186 }
187 #endregion
188
189 #region abstract members
190 /// <summary>
191 /// When overriden, tells whether the <see cref="Storage"/> uses locking when retrieving and setting values.
192 /// </summary>
193 protected abstract bool UseLocking { get; }
194
195 /// <summary>
196 /// Gets the object used by the <see cref="Storage"/> for locking when retrieving and setting values.
197 /// </summary>
198 protected abstract object LockInstance { get; }
199
200 ///<summary>
201 /// When overriden by a sub class, provides the internal Hashtable used to store and retrieve
202 /// data.
203 ///</summary>
204 ///<returns></returns>
205 protected abstract Hashtable GetInternalHashtable();
206 #endregion
207 }
AppStorage.cs
22 public class AppStorage : Storage
23 {
24 #region fields
25 private static Hashtable _internalStorage;
26 #endregion
27
28 #region properties
29 /// <summary>
30 /// Overriden. Configures the storage to use locking when getting and setting values.
31 /// </summary>
32 protected override bool UseLocking
33 {
34 get { return true; }
35 }
36
37 /// <summary>
38 /// Gets the object used by the <see cref="Storage"/> for locking when retrieving and setting values.
39 /// </summary>
40 protected override object LockInstance
41 {
42 get { return AppStorageLock; }
43 }
44 #endregion
45
46 #region methods
47 ///<summary>
48 /// Overriden. Gets the internal hash table that is used to store and retrieve application
49 /// specific data.
50 ///</summary>
51 ///<returns>A <see cref="Hashtable"/> that is used to store application specific data.</returns>
52 /// <remarks>
53 /// This method implementation uses locking as multiple threads (or requests in the case of a web app) can call
54 /// the GetInternalHashtable at the same time.
55 /// </remarks>
56 protected override Hashtable GetInternalHashtable()
57 {
58 if (IsWebApplication)
59 {
60 //This code is executing under a WebSite. Use the Application context to retrieve the hash table.
61 Hashtable internalHashtable = System.Web.HttpContext.Current.Application[typeof(LocalStorage).FullName] as Hashtable;
62 if (internalHashtable == null)
63 {
64 lock(AppStorageLock)
65 {
66 internalHashtable = System.Web.HttpContext.Current.Application[typeof(LocalStorage).FullName] as Hashtable;
67 if (internalHashtable == null)
68 System.Web.HttpContext.Current.Application[typeof(LocalStorage).FullName] = internalHashtable = new Hashtable();
69 }
70 }
71 return internalHashtable;
72 }
73
74 //The code is running under a normal windows application. Use the static property.
75 if (_internalStorage == null)
76 {
77 lock (SessionStorageLock)
78 {
79 if (_internalStorage == null)
80 _internalStorage = new Hashtable();
81 }
82 }
83 return _internalStorage;
84 }
85 #endregion
86 }
LocalStorage.cs
23 public class LocalStorage : Storage
24 {
25 #region fields
26 [ThreadStatic]
27 private static Hashtable _internalStorage;
28 #endregion
29
30 #region properties
31 /// <summary>
32 /// Overriden. Configures the storage to not use locking when getting and setting values.
33 /// </summary>
34 protected override bool UseLocking
35 {
36 get { return false; }
37 }
38
39 /// <summary>
40 /// Gets the object used by the <see cref="Storage"/> for locking when retrieving and setting values.
41 /// </summary>
42 protected override object LockInstance
43 {
44 get { return null; }
45 }
46 #endregion
47
48 #region methods
49 /// <summary>
50 /// Overriden. Gets the <see cref="Hashtable"/> used to store thread specific data.
51 /// </summary>
52 /// <returns>A <see cref="Hashtable"/> that can be used to store thread / request specific data.</returns>
53 /// <remarks>
54 /// No locking is used when initializing the Hashtables as only one request to GetInternalHashtable can be made at one time.
55 /// This code block is thread-safe
56 /// </remarks>
57 protected override Hashtable GetInternalHashtable()
58 {
59 if (IsWebApplication)
60 {
61 Hashtable internalStorage = System.Web.HttpContext.Current.Items[typeof(LocalStorage).FullName] as Hashtable;
62 if (internalStorage == null)
63 System.Web.HttpContext.Current.Items[typeof(LocalStorage).FullName] = internalStorage = new Hashtable();
64 return internalStorage;
65 }
66 else
67 return _internalStorage ?? (_internalStorage = new Hashtable());
68 }
69 #endregion
70 }
SessionStorage.cs
24 public class SessionStorage : Storage
25 {
26 #region properties
27 /// <summary>
28 /// Overriden. Configures the storage to use locking when getting and setting values.
29 /// </summary>
30 protected override bool UseLocking
31 {
32 get { return true; }
33 }
34
35 /// <summary>
36 /// Gets the object used by the <see cref="Storage"/> for locking when retrieving and setting values.
37 /// </summary>
38 protected override object LockInstance
39 {
40 get { return SessionStorageLock; }
41 }
42 #endregion
43
44 #region methods
45 /// <summary>
46 /// Overriden. Gets the <see cref="Hashtable"/> used to store thread specific data.
47 /// </summary>
48 /// <returns>A <see cref="Hashtable"/> that can be used to store Session specific data.</returns>
49 /// <remarks>
50 /// This code block uses locking to create the Hashtable as multiple requests can execute under the same session in
51 /// the case AJAX calls.
52 /// </remarks>
53 protected override Hashtable GetInternalHashtable()
54 {
55 Guard.Against<InvalidOperationException>(!IsWebApplication || HttpContext.Current.Session == null,
56 "An ASP.Net session must be available when using Session storage. No ASP.Net session was found in the current context.");
57
58 Hashtable internalStorage = HttpContext.Current.Session[typeof(SessionStorage).FullName] as Hashtable;
59 if (internalStorage == null)
60 {
61 lock(SessionStorageLock)
62 {
63 internalStorage = HttpContext.Current.Session[typeof(SessionStorage).FullName] as Hashtable;
64 if (internalStorage == null)
65 HttpContext.Current.Session[typeof(SessionStorage).FullName] = internalStorage = new Hashtable();
66 }
67 }
68 return internalStorage;
69 }
70 #endregion
71 }
Building a IoC wrapper component:
Starting with an abstract class, I start out with first defining abstract methods on an abstract IoC class. These abstract methods are the most common IoC methods I use in my applications and largely comprize of resolving component dependencies:
13 public abstract class IoC
14 {
15 /// <summary>
16 /// When overriden, should initialize and configure the container with the optionally provided
17 /// path to the configuration file path.
18 /// </summary>
19 /// <param name="configFilePath">string. An optional path to the external configuration file
20 /// that should be used by the container to initialize itself.</param>
21 protected abstract void Initialize(string configFilePath);
22
23 /// <summary>
24 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type.
25 /// </summary>
26 /// <typeparam name="T">The type of component to resolve or build.</typeparam>
27 /// <returns>An instance of <typeparamref name="T"/>.</returns>
28 public abstract T Resolve<T>();
29
30 /// <summary>
31 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and service name
32 /// </summary>
33 /// <typeparam name="T">The type of component to resolve or build.</typeparam>
34 /// <param name="serviceName">string. The component key that the component is registered with.</param>
35 /// <returns>An instance of <typeparamref name="T"/>.</returns>
36 public abstract T Resolve<T>(string serviceName);
37
38 /// <summary>
39 /// When overriden, tries to resolve a component of the specified <paramref name="type"/>
40 /// returning a new instance of the type <typeparam name="T"/>
41 /// </summary>
42 /// <param name="type">A <see cref="Type"/> instance representing the type to resolve.</param>
43 /// <returns>object. The resolved component if it exists.</returns>
44 public abstract T Resolve<T>(Type type);
45
46 /// <summary>
47 /// When overriden, tries to resolve a component of the specified <paramref name="type"/> and service name
48 /// returning a new instance of the type <typeparam name="T"/>
49 /// </summary>
50 /// <param name="type">A <see cref="Type"/> instance representing the type to resolve.</param>
51 /// <param name="serviceName">string. The component key that the component is registered with.</param>
52 /// <returns>object. The resolved component if it exists.</returns>
53 public abstract T Resolve<T>(Type type, string serviceName);
54
55 /// <summary>
56 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and
57 /// returns null (or default value for value types) when the component is not found.
58 /// </summary>
59 /// <typeparam name="T">The type of component to resolve.</typeparam>
60 /// <returns>An instance of <typeparamref name="T"/> or null if the compnent is not found.</returns>
61 public abstract T TryResolve<T>();
62
63 /// <summary>
64 /// When overriden, tries to resolve a component of type <typeparam name="T"/> registered for the type
65 /// <paramref name="type"/> and returns null (or default value for value types) when the component is not found.
66 /// </summary>
67 /// <param name="type">The registered component to resolve..</param>
68 /// <returns>An instance of the component if found, else null.</returns>
69 public abstract T TryResolve<T>(Type type);
70
71 /// <summary>
72 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and
73 /// returns the default value provided when the component is not found.
74 /// </summary>
75 /// <typeparam name="T">The type of component to resolve.</typeparam>
76 /// <param name="defaultValue">An optional default instance of <typeparamref name="T"/> that is
77 /// returned in case the component could not be resolved.</param>
78 /// <returns>An instance of <typeparamref name="T"/> type.</returns>
79 public abstract T TryResolve<T>(T defaultValue);
80
81 /// <summary>
82 /// When overriden, tries to resolve a component of the specified <typeparam name="T"/> type registered
83 /// with the <paramref name="type"/> and returns the default value provided when a component registeration
84 /// is not provided.
85 /// </summary>
86 /// <param name="type">The <see cref="Type"/> that the component is registered with.</param>
87 /// <param name="defaultValue">The default value to return in case the component is not registered.</param>
88 /// <returns>An instance of <typeparamref name="T"/> type or the default value if the component is not found.</returns>
89 public abstract T TryResolve<T>(Type type, T defaultValue);
90
91 /// <summary>
92 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> and
93 /// service name type returning null (or default value for value types) when the component is not found.
94 /// </summary>
95 /// <typeparam name="T">The type of component to resolve.</typeparam>
96 /// <param name="serviceName">string. The component key with which the component is registered.</param>
97 /// <returns>An instance of <typeparamref name="T"/> type or null if the component is not found.</returns>
98 public abstract T TryResolve<T>(string serviceName);
99
100 /// <summary>
101 /// When overriden, tries to resolve the component of the specified <typeparamref name="T"/> and
102 /// service name type returning the default value when the component is not found.
103 /// </summary>
104 /// <typeparam name="T">The type of component to resolve.</typeparam>
105 /// <param name="serviceName">string. The component key with which the compinent is registered.</param>
106 /// <param name="defaultValue">The default value to return if not found.</param>
107 /// <returns>An instance of <typeparamref name="T"/> instance.</returns>
108 public abstract T TryResolve<T>(string serviceName, T defaultValue);
109
110 /// <summary>
111 /// When overriden, tries to resolve the component of the specified <typeparamref name="T"/> registered
112 /// with the specified <paramref name="type"/> and service name. Returns the default value provided when
113 /// the registered component is not found.
114 /// </summary>
115 /// <typeparam name="T">The type of component to resolve.</typeparam>
116 /// <param name="type">The <see cref="Type"/> that the component is registered with.</param>
117 /// <param name="serviceName">string. The component key with which the component is registered.</param>
118 /// <param name="defaultValue">The default value to return in case the component is not registered.</param>
119 /// <returns>An instance of <typeparamref name="T"/> type or the default value if the component is not found.</returns>
120 public abstract T TryResolve<T>(Type type, string serviceName, T defaultValue);
121 }
The Initialize method is supposed to allow the IoC implementation to initialize and register all components. Next I add a Container property to the IoC class to allow getting an actual implementation of the abstract IoC class:
33 #region properties
34 /// <summary>
35 /// Gets the current IoC container in the current application instance
36 /// or HttpApplication that used for resolving instances.
37 /// </summary>
38 public static IoC Container
39 {
40 get
41 {
42 if (Storage.Application.Contains(CONTAINER_KEY))
43 {
44 lock (typeof(IoC))
45 {
46 if (!Storage.Application.Contains(CONTAINER_KEY))
47 {
48 LoadContainer();
49 }
50 }
51 }
52 return Storage.Application.Get<IoC>(CONTAINER_KEY);
53 }
54 }
55 #endregion
So the Container property attempts to get the concrete implementation if IoC from the application scope storage, or if it doesn’t exist it then calls a LoadContainer() method to load a default container. I’ll get into LoadContainer() in a moment…
Apart from that, sometimes I would also like to specify the concrete IoC instance to use as the container, mainly for mocking and testing basically. So to allow setting the container I’ve added a SetContainer method:
60 /// <summary>
61 /// Sets the container that is used as the current application's <see cref="Container"/> instance.
62 /// </summary>
63 /// <param name="instance"></param>
64 public static void SetContainer(IoC instance)
65 {
66 lock (typeof(IoC)) //Sync lock to ensure only one thread updates the application container.
67 {
68 Storage.Application.Set(CONTAINER_KEY, instance);
69 }
70 }
So now coming back to LoadContainer. Thinking about how to load up the container that the application use for Dependency Injection, the approach I wanted to take is to allow the application to define the concrete instance using the application’s (or website) configuration file. So what LoadContainer would do is check the configuration to find the Type that should be created as the default IoC container. Of course this Type must inherit from the IoC class. To do this I’ve created a custom configuration section that will be used by the LoadContainer method:
22 /// <summary>
23 /// Provides configuration settings to configure and initialize the <see cref="IoC"/> component.
24 /// </summary>
25 public class IoCConfiguration : ConfigurationSection
26 {
27 /// <summary>
28 /// Gets or sets the fully qualified type name of a class that inherits from
29 /// the <see cref="IoC"/> abstract class.
30 /// </summary>
31 [ConfigurationProperty("type", DefaultValue = "", IsRequired = true)]
32 public string Type
33 {
34 get { return base["type"] as string;}
35 set
36 {
37 Guard.Against<NullReferenceException>(string.IsNullOrEmpty(value), "Please specify a valid type that inherits from Vantage.Shared.IoC");
38 base["type"] = value;
39 }
40 }
41
42 /// <summary>
43 /// Gets or sets the name of the external file that should be used to initialize
44 /// the <see cref="IoC"/> implmentation.
45 /// </summary>
46 [ConfigurationProperty("fileName", DefaultValue = "", IsRequired = false)]
47 public string FileName
48 {
49 get { return base["fileName"] as string; }
50 set { base["fileName"] = value; }
51 }
52 }
Nothing fancy here, just a simple class inheriting from ConfigurationSection and has two properties; Type that identifies the type of class that should be loaded and an optional FileName to point to an external configuration file that is passed as the filePath parameter of the Initialize method on the IoC class.
With the configuration section defined, here’s the LoadContainer method:
74 /// <summary>
75 /// Loads the implementation of IoC container that should be used
76 /// by the application to resolve instances.
77 /// </summary>
78 private static void LoadContainer()
79 {
80 //Load the vantage.shared.ioc configuration section for the application.
81 IoCConfiguration config = ConfigurationManager.GetSection("rhinestone.shared.ioc") as IoCConfiguration;
82 Guard.Against<ConfigurationException>(config == null, "Configuration section rhinestone.shared.ioc not found. Failed to configure a valid IoC container.");
83
84 //Check and load the IoC implementing type to be used as the IoC container.
85 Type type = Type.GetType(config.Type);
86 Guard.InheritsFrom<IoC>(type, "The specified type in rhinestone.shared.ioc does not inherit from Rhinestone.Shared.IoC. Failed to configure valid IoC container.");
87
88 //Creating an instance of the container and setting that instance as the application's container.
89 IoC container = (IoC) Activator.CreateInstance(type);
90 container.Initialize(config.FileName);
91 SetContainer(container);
92 }
Here’s the full IoC class:
22 /// <summary>
23 /// The IoC abstract classes is implemented by IoC container providers
24 /// that provide inversion of control and service lookup functionality.
25 /// </summary>
26 public abstract class IoC
27 {
28 #region fields
29 //Key field used as a key to get the container from the ApplicationData
30 private static readonly object CONTAINER_KEY = typeof (IoC);
31 #endregion
32
33 #region properties
34 /// <summary>
35 /// Gets the current IoC container in the current application instance
36 /// or HttpApplication that used for resolving instances.
37 /// </summary>
38 public static IoC Container
39 {
40 get
41 {
42 if (Storage.Application.Contains(CONTAINER_KEY))
43 {
44 lock (typeof(IoC))
45 {
46 if (!Storage.Application.Contains(CONTAINER_KEY))
47 {
48 LoadContainer();
49 }
50 }
51 }
52 return Storage.Application.Get<IoC>(CONTAINER_KEY);
53 }
54 }
55 #endregion
56
57 #region methods
58
59 #region public
60 /// <summary>
61 /// Sets the container that is used as the current application's <see cref="Container"/> instance.
62 /// </summary>
63 /// <param name="instance"></param>
64 public static void SetContainer(IoC instance)
65 {
66 lock (typeof(IoC)) //Sync lock to ensure only one thread updates the application container.
67 {
68 Storage.Application.Set(CONTAINER_KEY, instance);
69 }
70 }
71 #endregion
72
73 #region private
74 /// <summary>
75 /// Loads the implementation of IoC container that should be used
76 /// by the application to resolve instances.
77 /// </summary>
78 private static void LoadContainer()
79 {
80 //Load the vantage.shared.ioc configuration section for the application.
81 IoCConfiguration config = ConfigurationManager.GetSection("rhinestone.shared.ioc") as IoCConfiguration;
82 Guard.Against<ConfigurationException>(config == null, "Configuration section rhinestone.shared.ioc not found. Failed to configure a valid IoC container.");
83
84 //Check and load the IoC implementing type to be used as the IoC container.
85 Type type = Type.GetType(config.Type);
86 Guard.InheritsFrom<IoC>(type, "The specified type in rhinestone.shared.ioc does not inherit from Rhinestone.Shared.IoC. Failed to configure valid IoC container.");
87
88 //Creating an instance of the container and setting that instance as the application's container.
89 IoC container = (IoC) Activator.CreateInstance(type);
90 container.Initialize(config.FileName);
91 SetContainer(container);
92 }
93 #endregion
94
95 #region abstract
96 /// <summary>
97 /// When overriden, should initialize and configure the container with the optionally provided
98 /// path to the configuration file path.
99 /// </summary>
100 /// <param name="configFilePath">string. An optional path to the external configuration file
101 /// that should be used by the container to initialize itself.</param>
102 protected abstract void Initialize(string configFilePath);
103
104 /// <summary>
105 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type.
106 /// </summary>
107 /// <typeparam name="T">The type of component to resolve or build.</typeparam>
108 /// <returns>An instance of <typeparamref name="T"/>.</returns>
109 public abstract T Resolve<T>();
110
111 /// <summary>
112 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and service name
113 /// </summary>
114 /// <typeparam name="T">The type of component to resolve or build.</typeparam>
115 /// <param name="serviceName">string. The component key that the component is registered with.</param>
116 /// <returns>An instance of <typeparamref name="T"/>.</returns>
117 public abstract T Resolve<T>(string serviceName);
118
119 /// <summary>
120 /// When overriden, tries to resolve a component of the specified <paramref name="type"/>
121 /// returning a new instance of the type <typeparam name="T"/>
122 /// </summary>
123 /// <param name="type">A <see cref="Type"/> instance representing the type to resolve.</param>
124 /// <returns>object. The resolved component if it exists.</returns>
125 public abstract T Resolve<T>(Type type);
126
127 /// <summary>
128 /// When overriden, tries to resolve a component of the specified <paramref name="type"/> and service name
129 /// returning a new instance of the type <typeparam name="T"/>
130 /// </summary>
131 /// <param name="type">A <see cref="Type"/> instance representing the type to resolve.</param>
132 /// <param name="serviceName">string. The component key that the component is registered with.</param>
133 /// <returns>object. The resolved component if it exists.</returns>
134 public abstract T Resolve<T>(Type type, string serviceName);
135
136 /// <summary>
137 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and
138 /// returns null (or default value for value types) when the component is not found.
139 /// </summary>
140 /// <typeparam name="T">The type of component to resolve.</typeparam>
141 /// <returns>An instance of <typeparamref name="T"/> or null if the compnent is not found.</returns>
142 public abstract T TryResolve<T>();
143
144 /// <summary>
145 /// When overriden, tries to resolve a component of type <typeparam name="T"/> registered for the type
146 /// <paramref name="type"/> and returns null (or default value for value types) when the component is not found.
147 /// </summary>
148 /// <param name="type">The registered component to resolve..</param>
149 /// <returns>An instance of the component if found, else null.</returns>
150 public abstract T TryResolve<T>(Type type);
151
152 /// <summary>
153 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> type and
154 /// returns the default value provided when the component is not found.
155 /// </summary>
156 /// <typeparam name="T">The type of component to resolve.</typeparam>
157 /// <param name="defaultValue">An optional default instance of <typeparamref name="T"/> that is
158 /// returned in case the component could not be resolved.</param>
159 /// <returns>An instance of <typeparamref name="T"/> type.</returns>
160 public abstract T TryResolve<T>(T defaultValue);
161
162 /// <summary>
163 /// When overriden, tries to resolve a component of the specified <typeparam name="T"/> type registered
164 /// with the <paramref name="type"/> and returns the default value provided when a component registeration
165 /// is not provided.
166 /// </summary>
167 /// <param name="type">The <see cref="Type"/> that the component is registered with.</param>
168 /// <param name="defaultValue">The default value to return in case the component is not registered.</param>
169 /// <returns>An instance of <typeparamref name="T"/> type or the default value if the component is not found.</returns>
170 public abstract T TryResolve<T>(Type type, T defaultValue);
171
172 /// <summary>
173 /// When overriden, tries to resolve a component of the specified <typeparamref name="T"/> and
174 /// service name type returning null (or default value for value types) when the component is not found.
175 /// </summary>
176 /// <typeparam name="T">The type of component to resolve.</typeparam>
177 /// <param name="serviceName">string. The component key with which the component is registered.</param>
178 /// <returns>An instance of <typeparamref name="T"/> type or null if the component is not found.</returns>
179 public abstract T TryResolve<T>(string serviceName);
180
181 /// <summary>
182 /// When overriden, tries to resolve the component of the specified <typeparamref name="T"/> and
183 /// service name type returning the default value when the component is not found.
184 /// </summary>
185 /// <typeparam name="T">The type of component to resolve.</typeparam>
186 /// <param name="serviceName">string. The component key with which the compinent is registered.</param>
187 /// <param name="defaultValue">The default value to return if not found.</param>
188 /// <returns>An instance of <typeparamref name="T"/> instance.</returns>
189 public abstract T TryResolve<T>(string serviceName,T defaultValue);
190
191 /// <summary>
192 /// When overriden, tries to resolve the component of the specified <typeparamref name="T"/> registered
193 /// with the specified <paramref name="type"/> and service name. Returns the default value provided when
194 /// the registered component is not found.
195 /// </summary>
196 /// <typeparam name="T">The type of component to resolve.</typeparam>
197 /// <param name="type">The <see cref="Type"/> that the component is registered with.</param>
198 /// <param name="serviceName">string. The component key with which the component is registered.</param>
199 /// <param name="defaultValue">The default value to return in case the component is not registered.</param>
200 /// <returns>An instance of <typeparamref name="T"/> type or the default value if the component is not found.</returns>
201 public abstract T TryResolve<T>(Type type, string serviceName, T defaultValue);
202 #endregion
203
204 #endregion
Providing an implementation for StructureMap:
Okay, so now that I have a IoC wrapper how would one use you might ask? So for you application or website or whatever… you could then create a subclass of IoC, set it up in the Initialize methods and then delegate the Resolve calls to the actual container of your choice. Here is one example using StructureMap, the following class can be used as a generic StructureMap container:
15 public class StructureMapContainer : IoC
16 {
17 #region Overrides of IoC
18 protected override void Initialize(string configFilePath)
19 {
20 //Initialize the object factory and setup the
21 if (string.IsNullOrEmpty(configFilePath))
22 StructureMapConfiguration.ScanAssemblies();
23 else
24 StructureMapConfiguration.IncludeConfigurationFromFile(configFilePath);
25 }
26
27 public override T Resolve<T>()
28 {
29 return ObjectFactory.GetInstance<T>();
30 }
31
32 public override T Resolve<T>(string serviceName)
33 {
34 return ObjectFactory.GetNamedInstance<T>(serviceName);
35 }
36
37 public override T Resolve<T>(Type type)
38 {
39 return (T) ObjectFactory.GetInstance(type);
40 }
41
42 public override T Resolve<T>(Type type, string serviceName)
43 {
44 return (T) ObjectFactory.GetNamedInstance(type, serviceName);
45 }
46
47 public override T TryResolve<T>()
48 {
49 return TryResolve(default(T));
50 }
51
52 public override T TryResolve<T>(Type type)
53 {
54 return TryResolve(type, default(T));
55 }
56
57 public override T TryResolve<T>(T defaultValue)
58 {
59 try
60 {
61 object instance = ObjectFactory.GetInstance<T>();
62 return instance == null ? defaultValue : (T)instance;
63 }
64 catch (Exception)
65 {
66 return defaultValue;
67 }
68 }
69
70 public override T TryResolve<T>(Type type, T defaultValue)
71 {
72 try
73 {
74 object instance = ObjectFactory.GetInstance(type);
75 return instance == null ? defaultValue : (T)instance;
76 }
77 catch (Exception)
78 {
79 return defaultValue;
80 }
81 }
82
83 public override T TryResolve<T>(string serviceName)
84 {
85 return TryResolve(serviceName, default(T));
86 }
87
88 public override T TryResolve<T>(string serviceName, T defaultValue)
89 {
90 try
91 {
92 object instance = ObjectFactory.GetNamedInstance<T>(serviceName);
93 return instance == null ? defaultValue : (T)instance;
94 }
95 catch (Exception)
96 {
97 return defaultValue;
98 };
99 }
100
101 public override T TryResolve<T>(Type type, string serviceName, T defaultValue)
102 {
103 try
104 {
105 object instance = ObjectFactory.GetNamedInstance(type, serviceName);
106 return instance == null ? defaultValue : (T)instance;
107 }
108 catch (Exception)
109 {
110 return defaultValue;
111 };
112 }
113 #endregion
114 }
Using the StructureMapContainer now, I can either use a configuration file to define the registered plugin types or use the Registry class and define component registrations using that. But the bottom line is that my application isn’t really tied down to one implementation of an IoC container and I can swap out implementations quite easily since my application uses the IoC wrapper.
The complete source for the IoC wrapper can be found in Rhinestone’s source code: https://rhinestone.googlecode.com/svn/trunk/src/Rhinestone.Shared/IoC