-
Notifications
You must be signed in to change notification settings - Fork 775
/
SimpleContainer.cs
324 lines (281 loc) · 11.4 KB
/
SimpleContainer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Caliburn.Micro
{
/// <summary>
/// A simple IoC container.
/// </summary>
public class SimpleContainer
{
private static readonly Type delegateType = typeof(Delegate);
private static readonly Type enumerableType = typeof(IEnumerable);
private static readonly TypeInfo enumerableTypeInfo = enumerableType.GetTypeInfo();
private static readonly TypeInfo delegateTypeInfo = delegateType.GetTypeInfo();
private readonly Type simpleContainerType = typeof(SimpleContainer);
private readonly List<ContainerEntry> entries;
/// <summary>
/// Initializes a new instance of the <see cref = "SimpleContainer" /> class.
/// </summary>
public SimpleContainer()
{
entries = new List<ContainerEntry>();
}
private SimpleContainer(IEnumerable<ContainerEntry> entries)
{
this.entries = new List<ContainerEntry>(entries);
}
/// <summary>
/// Whether to enable recursive property injection for all resolutions.
/// </summary>
public bool EnablePropertyInjection { get; set; }
/// <summary>
/// Registers the instance.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
/// <param name = "implementation">The implementation.</param>
public void RegisterInstance(Type service, string key, object implementation)
{
RegisterHandler(service, key, container => implementation);
}
/// <summary>
/// Registers the class so that a new instance is created on every request.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
/// <param name = "implementation">The implementation.</param>
public void RegisterPerRequest(Type service, string key, Type implementation)
{
RegisterHandler(service, key, container => container.BuildInstance(implementation));
}
/// <summary>
/// Registers the class so that it is created once, on first request, and the same instance is returned to all requestors thereafter.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
/// <param name = "implementation">The implementation.</param>
public void RegisterSingleton(Type service, string key, Type implementation)
{
object singleton = null;
RegisterHandler(service, key, container => singleton ?? (singleton = container.BuildInstance(implementation)));
}
/// <summary>
/// Registers a custom handler for serving requests from the container.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
/// <param name = "handler">The handler.</param>
public void RegisterHandler(Type service, string key, Func<SimpleContainer, object> handler)
{
GetOrCreateEntry(service, key).Add(handler);
}
/// <summary>
/// Unregisters any handlers for the service/key that have previously been registered.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
public void UnregisterHandler(Type service, string key)
{
var entry = GetEntry(service, key);
if (entry != null)
{
entries.Remove(entry);
}
}
/// <summary>
/// Requests an instance.
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key.</param>
/// <returns>The instance, or null if a handler is not found.</returns>
public object GetInstance(Type service, string key)
{
var entry = GetEntry(service, key);
if (entry != null)
{
var instance = entry.Single()(this);
if (EnablePropertyInjection && instance != null)
{
BuildUp(instance);
}
return instance;
}
if (service == null)
{
return null;
}
TypeInfo serviceTypeInfo = service.GetTypeInfo();
if (delegateTypeInfo.IsAssignableFrom(serviceTypeInfo))
{
var typeToCreate = serviceTypeInfo.GenericTypeArguments[0];
var factoryFactoryType = typeof(FactoryFactory<>).MakeGenericType(typeToCreate);
var factoryFactoryHost = Activator.CreateInstance(factoryFactoryType);
var factoryFactoryMethod = factoryFactoryType.GetRuntimeMethod("Create", new Type[] { simpleContainerType });
return factoryFactoryMethod.Invoke(factoryFactoryHost, new object[] { this });
}
if (enumerableTypeInfo.IsAssignableFrom(serviceTypeInfo) && serviceTypeInfo.IsGenericType)
{
var listType = serviceTypeInfo.GenericTypeArguments[0];
var instances = GetAllInstances(listType).ToList();
var array = Array.CreateInstance(listType, instances.Count);
for (var i = 0; i < array.Length; i++)
{
if (EnablePropertyInjection)
{
BuildUp(instances[i]);
}
array.SetValue(instances[i], i);
}
return array;
}
return null;
}
/// <summary>
/// Determines if a handler for the service/key has previously been registered.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="key">The key.</param>
/// <returns>True if a handler is registere; false otherwise.</returns>
public bool HasHandler(Type service, string key)
{
return GetEntry(service, key) != null;
}
/// <summary>
/// Requests all instances of a given type and the given key (default null).
/// </summary>
/// <param name = "service">The service.</param>
/// <param name = "key">The key shared by those instances</param>
/// <returns>All the instances or an empty enumerable if none are found.</returns>
public IEnumerable<object> GetAllInstances(Type service, string key = null)
{
var entries = GetEntry(service, key);
if (entries == null)
{
return new object[0];
}
var instances = entries.Select(e => e(this));
foreach (var instance in instances)
{
if (EnablePropertyInjection && instance != null)
{
BuildUp(instance);
}
}
return instances;
}
/// <summary>
/// Pushes dependencies into an existing instance based on interface properties with setters.
/// </summary>
/// <param name = "instance">The instance.</param>
public void BuildUp(object instance)
{
var properties = instance
.GetType()
.GetRuntimeProperties()
.Where(p => p.CanRead && p.CanWrite && p.PropertyType.GetTypeInfo().IsInterface);
foreach (var property in properties)
{
var value = GetInstance(property.PropertyType, null);
if (value != null)
{
property.SetValue(instance, value, null);
}
}
}
/// <summary>
/// Creates a child container.
/// </summary>
/// <returns>A new container.</returns>
public SimpleContainer CreateChildContainer()
{
return new SimpleContainer(entries);
}
private ContainerEntry GetOrCreateEntry(Type service, string key)
{
var entry = GetEntry(service, key);
if (entry == null)
{
entry = new ContainerEntry { Service = service, Key = key };
entries.Add(entry);
}
return entry;
}
private ContainerEntry GetEntry(Type service, string key)
{
if (service == null)
{
return entries.FirstOrDefault(x => x.Key == key);
}
if (key == null)
{
return entries.FirstOrDefault(x => x.Service == service && string.IsNullOrEmpty(x.Key))
?? entries.FirstOrDefault(x => x.Service == service);
}
return entries.FirstOrDefault(x => x.Service == service && x.Key == key);
}
/// <summary>
/// Actually does the work of creating the instance and satisfying it's constructor dependencies.
/// </summary>
/// <param name = "type">The type.</param>
/// <returns></returns>
protected object BuildInstance(Type type)
{
var args = DetermineConstructorArgs(type);
return ActivateInstance(type, args);
}
/// <summary>
/// Creates an instance of the type with the specified constructor arguments.
/// </summary>
/// <param name = "type">The type.</param>
/// <param name = "args">The constructor args.</param>
/// <returns>The created instance.</returns>
protected virtual object ActivateInstance(Type type, object[] args)
{
var instance = args.Length > 0 ? System.Activator.CreateInstance(type, args) : System.Activator.CreateInstance(type);
Activated(instance);
return instance;
}
/// <summary>
/// Occurs when a new instance is created.
/// </summary>
public event Action<object> Activated = delegate { };
private object[] DetermineConstructorArgs(Type implementation)
{
var args = new List<object>();
var constructor = SelectEligibleConstructor(implementation);
if (constructor != null)
{
args.AddRange(constructor.GetParameters().Select(info => GetInstance(info.ParameterType, null)));
}
return args.ToArray();
}
private ConstructorInfo SelectEligibleConstructor(Type type)
{
return type.GetTypeInfo().DeclaredConstructors
.Where(c => c.IsPublic)
.Select(c => new
{
Constructor = c,
HandledParamters = c.GetParameters().Count(p => HasHandler(p.ParameterType, null))
})
.OrderByDescending(c => c.HandledParamters)
.Select(c => c.Constructor)
.FirstOrDefault();
}
private class ContainerEntry : List<Func<SimpleContainer, object>>
{
public string Key;
public Type Service;
}
private class FactoryFactory<T>
{
public Func<T> Create(SimpleContainer container)
{
return () => (T)container.GetInstance(typeof(T), null);
}
}
}
}