The unity container (not to be confused with game engine) is one of the most popular dependency injection tools for C#.
However, by default the unity container will try to Resolve() all classes that it can. If you do not register a class, it will will often just succeed anyways.
I much prefer explicitly registering classes, and resolution just throwing and exception if I try to resolve something I did not register.
There’s a viable solution for that on stackoverflow, but it fails to throw when trying to resolve a class that was only registered via its interface.
Here’s our fixed version:
public class UnityRegistrationTracking : UnityContainerExtension { private readonly ConcurrentDictionary<NamedTypeBuildKey, bool> registrations = new ConcurrentDictionary<NamedTypeBuildKey, bool>(); protected override void Initialize() { base.Context.Registering += Context_Registering; base.Context.Strategies.Add( new ValidateRegistrationStrategy(this.registrations), UnityBuildStage.Setup); } private void Context_Registering(object sender, RegisterEventArgs e) { var buildKey = new NamedTypeBuildKey(e.TypeFrom, e.Name); this.registrations.AddOrUpdate(buildKey, true, (key, oldValue) => true); } public class ValidateRegistrationStrategy : BuilderStrategy { private ConcurrentDictionary<NamedTypeBuildKey, bool> registrations; public ValidateRegistrationStrategy(ConcurrentDictionary<NamedTypeBuildKey, bool> registrations) { this.registrations = registrations; } public override void PreBuildUp(ref BuilderContext context) { var buildKey = new NamedTypeBuildKey(context.RegistrationType, context.Name); if (!this.registrations.ContainsKey(buildKey)) { throw new ResolutionFailedException(buildKey.Type, buildKey.Name, string.Format("Type {0} was not explicitly registered in the container.", buildKey.Type.Name)); } } } }
We hook into two parts of the unity API here:
- The registration, which is called when you call Unity.RegisterType
- The resolution process, which is called when unity tries to resolve a specific instance.
The first part happens in Context_Registering. We just store the registration in dictionary for later. It is important to use TypeFrom as a key, since we want to refer to objects by the interfaces they are registered with, not their concrete implementations.
The second part is the ValidateRegistrationStrategy. All registered BuilderStrategy objects go in a list that is processed when an object is built. The UnityBuildStage.Setup acts as a sorting key, to make sure that this strategy is executed as early as possible.
In the strategy, we check whether the requested type was previously registered, and throw an exception if it was not. It is important to use context.RegistrationType here, since context.Type will again contain the concrete type, and not the interface.