PageRenderTime 39ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/Rebus/Injection/Injectionist.cs

https://github.com/rebus-org/Rebus
C# | 258 lines | 184 code | 49 blank | 25 comment | 14 complexity | 224f608a3e83e27b9e1b05775c1a01ed MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. namespace Rebus.Injection;
  6. /// <summary>
  7. /// Dependency injectionist that can be used for configuring a system of injected service implementations, possibly with decorators,
  8. /// with caching of instances so that the same instance of each class is used throughout the tree. Should probably not be used for
  9. /// anything at runtime, is only meant to be used in configuration scenarios.
  10. /// </summary>
  11. public class Injectionist
  12. {
  13. class Handler
  14. {
  15. public Handler()
  16. {
  17. Decorators = new List<Resolver>();
  18. }
  19. public Resolver PrimaryResolver { get; private set; }
  20. public List<Resolver> Decorators { get; private set; }
  21. void AddDecorator(Resolver resolver)
  22. {
  23. Decorators.Insert(0, resolver);
  24. }
  25. public void AddResolver(Resolver resolver)
  26. {
  27. if (!resolver.IsDecorator)
  28. {
  29. AddPrimary(resolver);
  30. }
  31. else
  32. {
  33. AddDecorator(resolver);
  34. }
  35. }
  36. void AddPrimary(Resolver resolver)
  37. {
  38. PrimaryResolver = resolver;
  39. }
  40. }
  41. readonly Dictionary<Type, Handler> _resolvers = new Dictionary<Type, Handler>();
  42. /// <summary>
  43. /// Starts a new resolution context, resolving an instance of the given <typeparamref name="TService"/>
  44. /// </summary>
  45. public ResolutionResult<TService> Get<TService>()
  46. {
  47. var resolutionContext = new ResolutionContext(_resolvers, ResolveRequested);
  48. var instance = resolutionContext.Get<TService>();
  49. return new ResolutionResult<TService>(instance, resolutionContext.TrackedInstances);
  50. }
  51. /// <summary>
  52. /// Events that is raised when the resolution of a top-level instance is requested
  53. /// </summary>
  54. public event Action<Type> ResolveRequested = delegate { };
  55. /// <summary>
  56. /// Registers a factory method that can provide an instance of <typeparamref name="TService"/>. Optionally,
  57. /// the supplied <paramref name="description"/> will be used to report more comprehensible errors in case of
  58. /// conflicting registrations.
  59. /// </summary>
  60. public void Register<TService>(Func<IResolutionContext, TService> resolverMethod, string description = null)
  61. {
  62. Register(resolverMethod, description: description, isDecorator: false);
  63. }
  64. /// <summary>
  65. /// Registers a decorator factory method that can provide an instance of <typeparamref name="TService"/>
  66. /// (i.e. the resolver is expected to call <see cref="IResolutionContext.Get{TService}"/> where TService
  67. /// is <typeparamref name="TService"/>. Optionally, the supplied <paramref name="description"/> will be used
  68. /// to report more comprehensible errors in case of conflicting registrations.
  69. /// </summary>
  70. public void Decorate<TService>(Func<IResolutionContext, TService> resolverMethod, string description = null)
  71. {
  72. Register(resolverMethod, description: description, isDecorator: true);
  73. }
  74. /// <summary>
  75. /// Returns whether there exists a registration for the specified <typeparamref name="TService"/>.
  76. /// </summary>
  77. public bool Has<TService>(bool primary = true)
  78. {
  79. return ResolverHaveRegistrationFor<TService>(primary, _resolvers);
  80. }
  81. static bool ResolverHaveRegistrationFor<TService>(bool primary, Dictionary<Type, Handler> resolvers)
  82. {
  83. var key = typeof(TService);
  84. if (!resolvers.ContainsKey(key)) return false;
  85. var handler = resolvers[key];
  86. if (handler.PrimaryResolver != null) return true;
  87. if (!primary && handler.Decorators.Any()) return true;
  88. return false;
  89. }
  90. void Register<TService>(Func<IResolutionContext, TService> resolverMethod, bool isDecorator, string description)
  91. {
  92. var handler = GetOrCreateHandler<TService>();
  93. var resolver = new Resolver<TService>(resolverMethod, description: description, isDecorator: isDecorator);
  94. if (!isDecorator)
  95. {
  96. if (handler.PrimaryResolver != null)
  97. {
  98. var message = $"Attempted to register {resolver}, but a primary registration already exists: {handler.PrimaryResolver}";
  99. throw new InvalidOperationException(message);
  100. }
  101. }
  102. handler.AddResolver(resolver);
  103. }
  104. Handler GetOrCreateHandler<TService>()
  105. {
  106. Handler handler;
  107. if (_resolvers.TryGetValue(typeof(TService), out handler)) return handler;
  108. handler = new Handler();
  109. _resolvers[typeof(TService)] = handler;
  110. return handler;
  111. }
  112. abstract class Resolver
  113. {
  114. protected Resolver(bool isDecorator)
  115. {
  116. IsDecorator = isDecorator;
  117. }
  118. public bool IsDecorator { get; private set; }
  119. }
  120. class Resolver<TService> : Resolver
  121. {
  122. readonly Func<IResolutionContext, TService> _resolver;
  123. readonly string _description;
  124. public Resolver(Func<IResolutionContext, TService> resolver, bool isDecorator, string description)
  125. : base(isDecorator)
  126. {
  127. _resolver = resolver;
  128. _description = description;
  129. }
  130. public TService InvokeResolver(IResolutionContext context)
  131. {
  132. return _resolver(context);
  133. }
  134. public override string ToString()
  135. {
  136. var role = IsDecorator ? "decorator ->" : "primary ->";
  137. var type = typeof(TService);
  138. return !string.IsNullOrWhiteSpace(_description)
  139. ? $"{role} {type} ({_description})"
  140. : $"{role} {type}";
  141. }
  142. }
  143. class ResolutionContext : IResolutionContext
  144. {
  145. readonly Dictionary<Type, int> _decoratorDepth = new Dictionary<Type, int>();
  146. readonly Dictionary<Type, Handler> _resolvers;
  147. readonly Action<Type> _serviceTypeRequested;
  148. readonly Dictionary<Type, object> _instances = new Dictionary<Type, object>();
  149. readonly List<object> _resolvedInstances = new List<object>();
  150. public ResolutionContext(Dictionary<Type, Handler> resolvers, Action<Type> serviceTypeRequested)
  151. {
  152. _resolvers = resolvers;
  153. _serviceTypeRequested = serviceTypeRequested;
  154. }
  155. public bool Has<TService>(bool primary = true)
  156. {
  157. return ResolverHaveRegistrationFor<TService>(primary, _resolvers);
  158. }
  159. public TService Get<TService>()
  160. {
  161. var serviceType = typeof(TService);
  162. object existingInstance;
  163. if (_instances.TryGetValue(serviceType, out existingInstance))
  164. {
  165. return (TService)existingInstance;
  166. }
  167. if (!_resolvers.ContainsKey(serviceType))
  168. {
  169. throw new ResolutionException($"Could not find resolver for {serviceType}");
  170. }
  171. if (!_decoratorDepth.ContainsKey(serviceType))
  172. {
  173. _decoratorDepth[serviceType] = 0;
  174. _serviceTypeRequested(serviceType);
  175. }
  176. var handlerForThisType = _resolvers[serviceType];
  177. var depth = _decoratorDepth[serviceType]++;
  178. try
  179. {
  180. var resolver = handlerForThisType
  181. .Decorators
  182. .Cast<Resolver<TService>>()
  183. .Skip(depth)
  184. .FirstOrDefault()
  185. ?? (Resolver<TService>) handlerForThisType.PrimaryResolver;
  186. var instance = resolver.InvokeResolver(this);
  187. _instances[serviceType] = instance;
  188. if (!_resolvedInstances.Contains(instance))
  189. {
  190. _resolvedInstances.Add(instance);
  191. }
  192. return instance;
  193. }
  194. catch (ResolutionException)
  195. {
  196. throw; //< let this one through
  197. }
  198. catch (Exception exception)
  199. {
  200. throw new ResolutionException(exception, $"Could not resolve {serviceType} with decorator depth {depth} - registrations: {string.Join("; ", handlerForThisType)}");
  201. }
  202. finally
  203. {
  204. _decoratorDepth[serviceType]--;
  205. }
  206. }
  207. public IEnumerable TrackedInstances => _resolvedInstances.ToList();
  208. }
  209. }