Unity:隐式ResolvedParameter用于未命名的注册

时间:2014-02-20 10:52:12

标签: dependency-injection inversion-of-control unity-container ioc-container onion-architecture

UserService构造函数有两个参数,IUnitOfWorkIUserRepository

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

我使用named registrations来区分IUnitOfWork的多个实例,因此在使用Unity容器注册UserService时,我需要使用{{1}显式指定参数}:

InjectionConstructor

是否可以省略container.RegisterType<IUserService, UserService>( new InjectionConstructor( new ResolvedParameter<IUnitOfWork>("someContext"), new ResolvedParameter<IUserRepository>() ) ); ?我希望Unity隐式推导出这个参数,因为不需要命名注册。代码看起来像这样:

new ResolvedParameter<IUserRepository>()

当我不需要使用container.RegisterType<IUserService, UserService>( new InjectionConstructor( new ResolvedParameter<IUnitOfWork>("someContext") ) ); 时,可以这样做。

2 个答案:

答案 0 :(得分:7)

基于InjectionConstructor,我提出了这个RequiredInjectionConstructor。它允许您指定任何参数集,并且它将尝试查找需要(至少)传递的注入参数集的构造函数。如果有多个构造函数符合此条件,则会选择具有最少参数的构造函数。假设其余的构造函数参数是未命名的已解析参数。

我还没有对它进行全套的单元测试,所以如果您遇到任何问题,请告诉我。

/// <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;
    }
}

答案 1 :(得分:2)

您是否愿意使用Unity的DependencyAttribute来装饰您的构造函数?此解决方案是直接的,内置的,并允许您选择和选择命名依赖项。但它确实用Unity goo“弄脏”你的构造函数。

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

另一种解决方案是编写自定义的BuilderStrategy和UnityContainerExtension。这可以通过更多的工作来完成。