Unity: Implicit ResolvedParameter for unnamed registrations
L

2

6

The UserService constructor has two parameters, a IUnitOfWork and a IUserRepository:

public UserService(IUnitOfWork unitofWork, IUserRepository userRepository) 
{ ... }

I am using named registrations to differentiate between multiple instances of IUnitOfWork, so when registering the UserService with the Unity container, I need to explicitly specify the parameters using an InjectionConstructor:

container.RegisterType<IUserService, UserService>(
    new InjectionConstructor(
        new ResolvedParameter<IUnitOfWork>("someContext"),
        new ResolvedParameter<IUserRepository>()
    )
);

Is it possible for new ResolvedParameter<IUserRepository>() to be omitted? I would like Unity to implicitly deduce this parameter since there is no need for a named registration. The code would look like this:

container.RegisterType<IUserService, UserService>(
    new InjectionConstructor(
        new ResolvedParameter<IUnitOfWork>("someContext")
    )
);

This would be done is any case when I don't need to use the InjectionConstructor.

Leanora answered 20/2, 2014 at 10:52 Comment(0)
P
7

Based on InjectionConstructor, I came up with this RequiredInjectionConstructor. It allows you to specify any set of arguments and it will attempt to find a constructor which is required to have (at a minimum) the passed set of injection parameters. If there are multiple constructors that meet this criteria, it chooses the constructor with the least number of parameters. The remaining constructor parameters are assumed to be unnamed resolved parameters.

I haven't performed a full suite of unit tests on it yet, so let me know if you encounter any issues.

/// <summary>
/// A class that holds the collection of minimum required
/// parameters for a constructor, so that the container can
/// be configured to call this constructor.
/// </summary>
public class RequiredInjectionConstructor : InjectionMember
{
    private readonly List<InjectionParameterValue> _requiredParameterValues;

    /// <summary>
    /// Create a new instance of <see cref="RequiredInjectionConstructor"/> that looks
    /// for a constructor with a minimum of the given required set of parameters.
    /// </summary>
    /// <param name="requiredParameterValues">The values for the parameters, that will
    /// be converted to <see cref="InjectionParameterValue"/> objects.</param>
    public RequiredInjectionConstructor(params object[] requiredParameterValues)
    {
        _requiredParameterValues = InjectionParameterValue.ToParameters(requiredParameterValues).ToList();
    }

    /// <summary>
    /// Add policies to the <paramref name="policies"/> to configure the
    /// container to call this constructor with the required parameter values.
    /// </summary>
    /// <param name="serviceType">Interface registered, ignored in this implementation.</param>
    /// <param name="implementationType">Type to register.</param>
    /// <param name="name">Name used to resolve the type object.</param>
    /// <param name="policies">Policy list to add policies to.</param>
    public override void AddPolicies(Type serviceType, Type implementationType, string name, IPolicyList policies)
    {
        ConstructorInfo ctor = FindConstructor(implementationType, _requiredParameterValues);
        IEnumerable<InjectionParameterValue> selectedConstructorParameterValues = GetSelectedConstructorParameterValues(ctor, _requiredParameterValues);

        policies.Set<IConstructorSelectorPolicy>(
            new SpecifiedConstructorSelectorPolicy(ctor, selectedConstructorParameterValues.ToArray()),
            new NamedTypeBuildKey(implementationType, name));
    }

    private static ConstructorInfo FindConstructor(Type typeToCreate, IEnumerable<InjectionParameterValue> requiredInjectionParameters)
    {
        var typeToCreateReflector = new ReflectionHelper(typeToCreate);

        var matchedConstructors = typeToCreateReflector.InstanceConstructors.
            Where(ctor =>
            {
                var constructorParameterTypes = ctor.GetParameters().Select(info => info.ParameterType);
                return requiredInjectionParameters.All(required => constructorParameterTypes.Any(required.MatchesType));
            });

        if (matchedConstructors.Any())
        {
            // Prefer the constructor that has the least number of arguments.
            // Other preference models could be implemented here. 
            return matchedConstructors.OrderBy(ctor => 
                ctor.GetParameters().Count()).
                FirstOrDefault();
        }

        string signature = string.Join(", ", requiredInjectionParameters.Select(required => required.ParameterTypeName).ToArray());

        throw new InvalidOperationException(
            string.Format("Unable to find a constructor with the minimum required parameters.  Type: {0}, RequiredParameters: {1}",
                typeToCreate.FullName,
                signature));
    }

    private static IEnumerable<InjectionParameterValue> GetSelectedConstructorParameterValues(ConstructorInfo ctor, IEnumerable<InjectionParameterValue> requiredInjectionParameters)
    {
        var injectionParameterValues = new List<InjectionParameterValue>();

        foreach (var parameter in ctor.GetParameters())
        {
            var existingInjectionParameter = requiredInjectionParameters.FirstOrDefault(required => required.MatchesType(parameter.ParameterType));
            injectionParameterValues.Add(existingInjectionParameter ?? new ResolvedParameter(parameter.ParameterType));
        }

        return injectionParameterValues;
    }
}
Parrakeet answered 26/2, 2014 at 2:53 Comment(2)
This works great. Could there be any drawbacks using reflection like this (e.g. performance)? Thanks a lot for your help.Leanora
This uses reflection in the same manor as the standard InjectionConstructor, so it should have about the same performance. Glad I could help! =)Parrakeet
P
2

Would you be willing to decorate your constructor with the DependencyAttribute from Unity? This solution is straight forward, built-in, and lets you pick and chose named dependency. But it does 'dirty' your constructor with Unity goo.

public UserService(
    [Dependency("someContext")]IUnitOfWork unitofWork, 
    IUserRepository userRepository) 
{ ... }

Another solution would be to write a custom BuilderStrategy and UnityContainerExtension. This could be done with a bit more work.

Parrakeet answered 25/2, 2014 at 7:10 Comment(1)
I would prefer not, as these services form part of our domain layer. I wouldn't want them to have any dependency on Unity (or any infrastructural concern). I'm surprised this isn't possible. I believe it is possible with Ninject, from what I've read and understood.Leanora

© 2022 - 2024 — McMap. All rights reserved.