/src/LinFu.IoC/Configuration/Loaders/FactoryAttributeLoader.cs
C# | 216 lines | 126 code | 29 blank | 61 comment | 15 complexity | 8aaeb8cb8e343a9705c55b09b96120d0 MD5 | raw file
1using System; 2using System.Collections.Generic; 3using System.IO; 4using System.Linq; 5using LinFu.IoC.Configuration.Injectors; 6using LinFu.IoC.Factories; 7using LinFu.IoC.Interfaces; 8 9namespace LinFu.IoC.Configuration 10{ 11 /// <summary> 12 /// A class that injects custom <see cref="IFactory" /> and <see cref="IFactory{T}" /> 13 /// instances into an <see cref="IServiceContainer" /> instance. 14 /// </summary> 15 public class FactoryAttributeLoader : ITypeLoader 16 { 17 /// <summary> 18 /// Loads an <see cref="IFactory" /> and <see cref="IFactory{T}" /> instance 19 /// into a <see cref="IServiceContainer" /> instance using the given 20 /// <paramref name="sourceType" />. 21 /// </summary> 22 /// <param name="sourceType">The input type from which one or more factories will be created.</param> 23 /// <returns>A set of <see cref="Action{T}" /> instances. This cannot be null.</returns> 24 public IEnumerable<Action<IServiceContainer>> Load(Type sourceType) 25 { 26 if (sourceType == null) 27 throw new ArgumentNullException("sourceType"); 28 29 // Extract the factory attributes from the current type 30 var attributes = sourceType.GetCustomAttributes(typeof(FactoryAttribute), false); 31 var attributeList = attributes.Cast<FactoryAttribute>() 32 .Where(f => f != null).ToList(); 33 34 35 // The target type must have at least one 36 // factory attribute 37 if (attributeList.Count == 0) 38 return new Action<IServiceContainer>[0]; 39 40 41 // Make sure the factory is created only once 42 Func<IFactoryRequest, object> getFactoryInstance = request => 43 { 44 var container = request.Container; 45 return container.AutoCreateInternal(sourceType); 46 }; 47 48 49 return GetResults(sourceType, attributeList, getFactoryInstance); 50 } 51 52 /// <summary> 53 /// Determines whether or not the current <paramref name="sourceType" /> 54 /// can be loaded. 55 /// </summary> 56 /// <param name="sourceType">The source type currently being loaded.</param> 57 /// <returns>Returns <c>true</c> if the type is a class type; otherwise, it returns <c>false</c>.</returns> 58 public bool CanLoad(Type sourceType) 59 { 60 try 61 { 62 return sourceType.IsClass; 63 } 64 catch (TypeInitializationException) 65 { 66 // Ignore the error 67 return false; 68 } 69 catch (FileNotFoundException) 70 { 71 // Ignore the error 72 return false; 73 } 74 } 75 76 77 /// <summary> 78 /// Instantiates the <see cref="IFactory" /> instances associated with the <paramref name="sourceType" /> and 79 /// adds those factories to the target container upon initialization. 80 /// </summary> 81 /// <param name="sourceType">The <see cref="System.Type" /> currently being inspected.</param> 82 /// <param name="attributeList"> 83 /// The list of <see cref="FactoryAttribute" /> instances currently declared on on the source 84 /// type. 85 /// </param> 86 /// <param name="getFactoryInstance">The functor that will be responsible for generating the factory instance.</param> 87 /// <returns>A list of actions that will add the factories to the target container.</returns> 88 private static IEnumerable<Action<IServiceContainer>> GetResults(Type sourceType, 89 IEnumerable<FactoryAttribute> attributeList, 90 Func<IFactoryRequest, object> 91 getFactoryInstance) 92 { 93 var results = new List<Action<IServiceContainer>>(); 94 // The factory instance must implement either 95 // IFactory or IFactory<T> 96 var factoryInterfaces = from t in sourceType.GetInterfaces() 97 where t.IsGenericType && 98 t.GetGenericTypeDefinition() == typeof(IFactory<>) 99 select t; 100 101 if (!typeof(IFactory).IsAssignableFrom(sourceType) && factoryInterfaces.Count() == 0) 102 { 103 var message = 104 string.Format( 105 "The factory type '{0}' must implement either the IFactory interface or the IFactory<T> interface.", 106 sourceType.AssemblyQualifiedName); 107 throw new ArgumentException(message, "sourceType"); 108 } 109 110 var implementedInterfaces = new HashSet<Type>(factoryInterfaces); 111 Func<Type, Func<IFactoryRequest, object>, IFactory> createFactory = 112 (currentServiceType, getFactory) => 113 { 114 // Determine if the factory implements 115 // the generic IFactory<T> instance 116 // and use that instance if possible 117 118 Func<IFactoryRequest, IFactory> getStronglyTypedFactory = 119 request => 120 { 121 var result = getFactory(request); 122 // If the object is IFactory then we can just return it. 123 if (result is IFactory) 124 return (IFactory) result; 125 126 // Check to see if the object is IFactory<T>, if so we need to adapt it to 127 // IFactory. 128 var genericType = typeof(IFactory<>).MakeGenericType(currentServiceType); 129 if (!genericType.IsInstanceOfType(result)) return null; 130 131 // Adapt IFactory<T> to IFactory. 132 var adapterType = typeof(FactoryAdapter<>).MakeGenericType(currentServiceType); 133 var adapter = (IFactory) Activator.CreateInstance(adapterType, result); 134 return adapter; 135 }; 136 137 return GetFactory(currentServiceType, getStronglyTypedFactory, implementedInterfaces); 138 }; 139 140 // Build the list of services that this factory can implement 141 var servicesToImplement = from f in attributeList 142 let serviceName = f.ServiceName 143 let serviceType = f.ServiceType 144 let argumentTypes = f.ArgumentTypes ?? new Type[0] 145 let factory = createFactory(serviceType, getFactoryInstance) 146 where factory != null 147 select new 148 { 149 ServiceName = serviceName, 150 ServiceType = serviceType, 151 ArgumentTypes = argumentTypes, 152 FactoryInstance = factory 153 }; 154 155 156 foreach (var currentService in servicesToImplement) 157 { 158 var serviceName = currentService.ServiceName; 159 var serviceType = currentService.ServiceType; 160 var argumentTypes = currentService.ArgumentTypes; 161 var factory = currentService.FactoryInstance; 162 163 // HACK: Unnamed custom factories should be able to 164 // intercept every request for the given service type 165 if (serviceName == null) 166 { 167 var injector = new CustomFactoryInjector(serviceType, factory); 168 results.Add(container => container.PreProcessors.Add(injector)); 169 } 170 171 // Add each service to the container on initialization 172 results.Add(container => container.AddFactory(serviceName, serviceType, argumentTypes, factory)); 173 } 174 175 return results; 176 } 177 178 /// <summary> 179 /// Instantiates the given factory using the <paramref name="getStronglyTypedFactory">factory functor.</paramref> 180 /// </summary> 181 /// <param name="currentServiceType">The service type that will be created by the factory.</param> 182 /// <param name="getStronglyTypedFactory">The functor that will be responsible for creating the factory itself.</param> 183 /// <param name="implementedInterfaces"> 184 /// The list of <see cref="IFactory{T}" /> interfaces that are implemented by the 185 /// source type. 186 /// </param> 187 /// <returns>A valid factory instance.</returns> 188 private static IFactory GetFactory(Type currentServiceType, 189 Func<IFactoryRequest, IFactory> getStronglyTypedFactory, 190 ICollection<Type> implementedInterfaces) 191 { 192 var genericType = typeof(IFactory<>).MakeGenericType(currentServiceType); 193 194 // Lazy-instantiate the factories so that they can be injected by the container 195 IFactory lazyFactory = new LazyFactory(getStronglyTypedFactory); 196 197 IFactory result; 198 if (implementedInterfaces.Contains(genericType)) 199 { 200 // Convert the IFactory<T> instance down to an IFactory 201 // instance so that it can be used by the target container 202 var lazyFactoryType = typeof(LazyFactory<>).MakeGenericType(currentServiceType); 203 var adapterType = typeof(FactoryAdapter<>).MakeGenericType(currentServiceType); 204 205 lazyFactory = (IFactory) Activator.CreateInstance(lazyFactoryType, getStronglyTypedFactory); 206 result = (IFactory) Activator.CreateInstance(adapterType, lazyFactory); 207 208 return result; 209 } 210 211 // Otherwise, use the untyped IFactory instance instead 212 result = lazyFactory; 213 return result; 214 } 215 } 216}