Fun with constructor arguments Part 3: Named registrations
August 6, 2012 Leave a comment
A while ago there was a question on StackOverflow: If I have multiple implementations of an interface registered as named mappings and a constructor that should consume such a named mapping how can I tell Unity to automatically match the argument name to the mapping name?
E.g. if you have the following class definitions:
public interface ICommand { } public class LoadCommand : ICommand { } public class SaveCommand : ICommand { } public class Consumer { public Consumer(ICommand loadCommand, ICommand saveCommand) { // ... } }
Now you setup your container and register both command implementations:
container.RegisterType<ICommand, LoadCommand>("loadCommand"); container.RegisterType<ICommand, SaveCommand>("saveCommand");
And when you resolve the Consumer the LoadCommand should be used for the parameter named loadCommand and the SaveCommand should be used for the parameter named saveCommand.
This is what you would have to do using Unity as is:
container.RegisterType<Consumer>( new InjectionConstructor( new ResolvedParameter(typeof(ICommand), "loadCommand"), new ResolvedParameter(typeof(ICommand), "saveCommand")));
And this is a “slightly” enhanced version:
container.RegisterType<Consumer>(new MapParameterNamesToRegistrationNames());
The class MapParameterNamesToRegistrationNames is derived from InjectionMember. It places a marker policy in Unity’s build pipeline. When an object is resolved by the container a custom BuilderStrategy looks for that marker. If the marker is found the strategy will replace the dependency resolvers (implementations of IDependencyResolverPolicy) that Unity puts into the pipeline by default with NamedTypeDependencyResolvers using the name of the constructor argument as name of the mapping.
public class MapParameterNamesToRegistrationNamesStrategy : BuilderStrategy { public override void PreBuildUp(IBuilderContext context) { if (context.Policies.Get( context.BuildKey) == null) { return; } IPolicyList resolverPolicyDestination; IConstructorSelectorPolicy selector = context.Policies.Get( context.BuildKey, out resolverPolicyDestination); if (selector == null) { return; } var selectedConstructor = selector.SelectConstructor(context, resolverPolicyDestination); if (selectedConstructor == null) { return; } ParameterInfo[] parameters = selectedConstructor.Constructor.GetParameters(); string[] parameterKeys = selectedConstructor.GetParameterKeys(); for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; if (parameterType.IsAbstract || parameterType.IsInterface || (parameterType.IsClass && parameterType != typeof(string))) { IDependencyResolverPolicy resolverPolicy = new NamedTypeDependencyResolverPolicy(parameterType, parameters[i].Name); context.Policies.Set(resolverPolicy, parameterKeys[i]); } } resolverPolicyDestination.Set( new SelectedConstructorCache(selectedConstructor), context.BuildKey); } }
The registration code tells you exactly what you expect from the container. No confusing setup of InjectionConstructors and ResolvedParameters. Just another simple convention that can make your life a lot easier.
Get the source code for MapParameterNamesToRegistrationNames here (project TecX.Unity folder Injection and the test suite that shows how to use it in TecX.Unity.Test).