namespace Caliburn.Micro.Core { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; /// /// A simple IoC container. /// public class SimpleContainer { static readonly Type delegateType = typeof(Delegate); static readonly Type enumerableType = typeof(IEnumerable); readonly List entries; /// /// Initializes a new instance of the class. /// public SimpleContainer() { entries = new List(); } SimpleContainer(IEnumerable entries) { this.entries = new List(entries); } /// /// Registers the instance. /// /// The service. /// The key. /// The implementation. public void RegisterInstance(Type service, string key, object implementation) { RegisterHandler(service, key, container => implementation); } /// /// Registers the class so that a new instance is created on every request. /// /// The service. /// The key. /// The implementation. public void RegisterPerRequest(Type service, string key, Type implementation) { RegisterHandler(service, key, container => container.BuildInstance(implementation)); } /// /// Registers the class so that it is created once, on first request, and the same instance is returned to all requestors thereafter. /// /// The service. /// The key. /// The implementation. public void RegisterSingleton(Type service, string key, Type implementation) { object singleton = null; RegisterHandler(service, key, container => singleton ?? (singleton = container.BuildInstance(implementation))); } /// /// Registers a custom handler for serving requests from the container. /// /// The service. /// The key. /// The handler. public void RegisterHandler(Type service, string key, Func handler) { GetOrCreateEntry(service, key).Add(handler); } /// /// Unregisters any handlers for the service/key that have previously been registered. /// /// The service. /// The key. public void UnregisterHandler(Type service, string key) { var entry = GetEntry(service, key); if (entry != null) { entries.Remove(entry); } } /// /// Requests an instance. /// /// The service. /// The key. /// The instance, or null if a handler is not found. public object GetInstance(Type service, string key) { var entry = GetEntry(service, key); if (entry != null) { return entry.Single()(this); } if (service == null) { return null; } if (delegateType.IsAssignableFrom(service)) { var typeToCreate = service.GetGenericArguments()[0]; var factoryFactoryType = typeof(FactoryFactory<>).MakeGenericType(typeToCreate); var factoryFactoryHost = Activator.CreateInstance(factoryFactoryType); var factoryFactoryMethod = factoryFactoryType.GetMethod("Create", new Type[] { typeof(SimpleContainer) }); return factoryFactoryMethod.Invoke(factoryFactoryHost, new object[] { this }); } if (enumerableType.IsAssignableFrom(service) && service.IsGenericType()) { var listType = service.GetGenericArguments()[0]; var instances = GetAllInstances(listType).ToList(); var array = Array.CreateInstance(listType, instances.Count); for (var i = 0; i < array.Length; i++) { array.SetValue(instances[i], i); } return array; } return null; } /// /// Determines if a handler for the service/key has previously been registered. /// /// The service. /// The key. /// True if a handler is registere; false otherwise. public bool HasHandler(Type service, string key) { return GetEntry(service, key) != null; } /// /// Requests all instances of a given type. /// /// The service. /// All the instances or an empty enumerable if none are found. public IEnumerable GetAllInstances(Type service) { var entry = GetEntry(service, null); return entry != null ? entry.Select(x => x(this)) : new object[0]; } /// /// Pushes dependencies into an existing instance based on interface properties with setters. /// /// The instance. public void BuildUp(object instance) { var injectables = from property in instance.GetType().GetProperties() where property.CanRead && property.CanWrite && property.PropertyType.IsInterface() select property; foreach (var propertyInfo in injectables) { var injection = GetAllInstances(propertyInfo.PropertyType).ToArray(); if (injection.Any()) { propertyInfo.SetValue(instance, injection.First(), null); } } } /// /// Creates a child container. /// /// A new container. public SimpleContainer CreateChildContainer() { return new SimpleContainer(entries); } 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; } 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 && x.Key == null) ?? entries.FirstOrDefault(x => x.Service == service); } return entries.FirstOrDefault(x => x.Service == service && x.Key == key); } /// /// Actually does the work of creating the instance and satisfying it's constructor dependencies. /// /// The type. /// protected object BuildInstance(Type type) { var args = DetermineConstructorArgs(type); return ActivateInstance(type, args); } /// /// Creates an instance of the type with the specified constructor arguments. /// /// The type. /// The constructor args. /// The created instance. 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; } /// /// Occurs when a new instance is created. /// public event Action Activated = delegate { }; object[] DetermineConstructorArgs(Type implementation) { var args = new List(); var constructor = SelectEligibleConstructor(implementation); if (constructor != null) args.AddRange(constructor.GetParameters().Select(info => GetInstance(info.ParameterType, null))); return args.ToArray(); } static ConstructorInfo SelectEligibleConstructor(Type type) { return (from c in type.GetConstructors().Where(c => c.IsPublic) orderby c.GetParameters().Length descending select c).FirstOrDefault(); } class ContainerEntry : List> { public string Key; public Type Service; } class FactoryFactory { public Func Create(SimpleContainer container) { return () => (T)container.GetInstance(typeof(T), null); } } } }