Microsoft Unity. How to specify a certain parameter in constructor?

后端 未结 5 1326
悲&欢浪女
悲&欢浪女 2020-12-23 09:36

I\'m using Microsoft Unity. I have an interface ICustomerService and its implementation CustomerService. I can register them for the Unity containe

相关标签:
5条回答
  • 2020-12-23 10:19

    Chris Tavares gave a good answer with a lot of information.

    If you have many Parameters to inject usually these are interfaces or instances which can be resolved by Unity (using the differnet techniques). But what if you want to provide only one Parameter which cannot be resolved automatically, e.g. a string for a filename?

    Now you have to provide all the typeof(IMyProvider) and one string or instance. But to be honest just providing the types could be done by Unity, because Unity has already a strategy to choose the best ctor.

    So I coded a replacement for InjectionConstructor:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using Microsoft.Practices.ObjectBuilder2;
    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Unity.ObjectBuilder;
    using Microsoft.Practices.Unity.Utility;
    
    namespace Microsoft.Practices.Unity
    {
        /// <summary>
        /// A class that holds the collection of information for a constructor, 
        /// so that the container can be configured to call this constructor.
        /// This Class is similar to InjectionConstructor, but you need not provide
        /// all Parameters, just the ones you want to override or which cannot be resolved automatically
        /// The given params are used in given order if type matches
        /// </summary>
        public class InjectionConstructorRelaxed : InjectionMember
        {
            private List<InjectionParameterValue> _parameterValues;
    
            /// <summary>
            /// Create a new instance of <see cref="InjectionConstructor"/> that looks
            /// for a constructor with the given set of parameters.
            /// </summary>
            /// <param name="parameterValues">The values for the parameters, that will
            /// be converted to <see cref="InjectionParameterValue"/> objects.</param>
            public InjectionConstructorRelaxed(params object[] parameterValues)
            {
                _parameterValues = InjectionParameterValue.ToParameters(parameterValues).ToList();
            }
    
            /// <summary>
            /// Add policies to the <paramref name="policies"/> to configure the
            /// container to call this constructor with the appropriate 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 = FindExactMatchingConstructor(implementationType);
                if (ctor == null)
                {
                    //if exact matching ctor not found, use the longest one and try to adjust the parameters.
                    //use given Params if type matches otherwise use the type to advise Unity to resolve later
                    ctor = FindLongestConstructor(implementationType);
                    if (ctor != null)
                    {
                        //adjust parameters
                        var newParams = new List<InjectionParameterValue>();
                        foreach (var parameter in ctor.GetParameters())
                        {
                            var injectionParameterValue =
                                _parameterValues.FirstOrDefault(value => value.MatchesType(parameter.ParameterType));
                            if (injectionParameterValue != null)
                            {
                                newParams.Add(injectionParameterValue);
                                _parameterValues.Remove(injectionParameterValue);
                            }
                            else
                                newParams.Add(InjectionParameterValue.ToParameter(parameter.ParameterType));
                        }
                        _parameterValues = newParams;
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.CurrentCulture,
                                "No constructor found for type {0}.",
                                implementationType.GetTypeInfo().Name));
                    }
                }
                policies.Set<IConstructorSelectorPolicy>(
                    new SpecifiedConstructorSelectorPolicy(ctor, _parameterValues.ToArray()),
                    new NamedTypeBuildKey(implementationType, name));
            }
    
    
    
            private ConstructorInfo FindExactMatchingConstructor(Type typeToCreate)
            {
                var matcher = new ParameterMatcher(_parameterValues);
                var typeToCreateReflector = new ReflectionHelper(typeToCreate);
    
                foreach (ConstructorInfo ctor in typeToCreateReflector.InstanceConstructors)
                {
                    if (matcher.Matches(ctor.GetParameters()))
                    {
                        return ctor;
                    }
                }
    
                return null;
            }
    
           private static ConstructorInfo FindLongestConstructor(Type typeToConstruct)
            {
                ReflectionHelper typeToConstructReflector = new ReflectionHelper(typeToConstruct);
    
                ConstructorInfo[] constructors = typeToConstructReflector.InstanceConstructors.ToArray();
                Array.Sort(constructors, new ConstructorLengthComparer());
    
                switch (constructors.Length)
                {
                    case 0:
                        return null;
    
                    case 1:
                        return constructors[0];
    
                    default:
                        int paramLength = constructors[0].GetParameters().Length;
                        if (constructors[1].GetParameters().Length == paramLength)
                        {
                            throw new InvalidOperationException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    "The type {0} has multiple constructors of length {1}. Unable to disambiguate.",
                                    typeToConstruct.GetTypeInfo().Name,
                                    paramLength));
                        }
                        return constructors[0];
                }
            }
            private class ConstructorLengthComparer : IComparer<ConstructorInfo>
            {
                /// <summary>
                /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
                /// </summary>
                /// <param name="y">The second object to compare.</param>
                /// <param name="x">The first object to compare.</param>
                /// <returns>
                /// Value Condition Less than zero is less than y. Zero equals y. Greater than zero is greater than y.
                /// </returns>
                [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Validation done by Guard class")]
                public int Compare(ConstructorInfo x, ConstructorInfo y)
                {
                    Guard.ArgumentNotNull(x, "x");
                    Guard.ArgumentNotNull(y, "y");
    
                    return y.GetParameters().Length - x.GetParameters().Length;
                }
            }
        }
    }
    

    Usage:

    container.RegisterType(new TransientLifetimeManager(), new InjectionConstructorRelaxed(
        new SomeService1("with special options")
        //, new SomeService2() //not needed, normal unity resolving used
        //equivalent to: , typeof(SomeService2)
        ));
    
    0 讨论(0)
  • 2020-12-23 10:28

    As an alternative of Chris Tavares' answer, you can let the container resolve only the second parameter:

    container.RegisterType<ICustomerService, CustomerService>(
        new InjectionConstructor(new SomeService1(), new ResolvedParameter<IService2>());
    
    0 讨论(0)
  • 2020-12-23 10:36

    You can default the second parameter to the constructor (e.g., =null) or offer a single parameter constructor in addition to the two parameter constructor by overloading it.

    0 讨论(0)
  • 2020-12-23 10:40

    Your best answer is to actually use the container.

    What you're doing is saying "when building this type, use this specific instance of the object." This doesn't take advantage of the ability of the container to build up instance for you. Instead, you should register IService1 and IService2 in the container. Then, tell the container to resolve those dependencies for you.

    It looks something like this:

    container.RegisterType<IService1, SomeService1>();
    container.RegisterType<IService2, SomeService2>();
    

    What this does is tell the container "whenever there's a dependency of type IService1, new up a new object of type SomeService1 and hand it that" and similarly for IService2.

    So next, you need to tell the container what to do about ICustomerService. In most generality, you'd do this:

    container.RegisterType<ICustomerService, CustomerService>(
        // Note, don't need to explicitly say transient, that's the default
        new InjectionConstructor(new ResolvedParameter<IService1>(),
            new ResolvedParameter<IService2>()));
    

    This tells the container: when resolving ICustomerService, new up an instance of CustomerService using the constructor that takes IService1 and IService2. To get those parameters, call back into the container to resolve those types.

    This is a bit verbose, and a common case, so there are some shortcuts. First off, you can pass a Type object instead of doing new ResolvedParameter, like so:

    container.RegisterType<ICustomerService, CustomerService>(
        new InjectionConstructor(typeof(IService1), typeof (IService2)));
    

    As another shorthand, if CustomerService has only one constructor, or if the one you want called is the one that takes the largest parameter list, you can leave the InjectionConstructor out completely, as that's the constructor that the container will pick in the absence of other configuration.

    container.RegisterType<ICustomerService, CustomerService>();
    

    The form you're using is typically used when you want a specific value passed for a constructor parameter rather than resolving the service back through the container.

    To answer your original question - well, you can't do exactly what you said. The constructor parameter needs A value of some sort. You could put anything else in there you want, though - null typically works.

    Note you can also mix the two forms. For example, if you want to resolve IService1 and pass null for the IService2 parameter, do this:

    container.RegisterType<ICustomerService, CustomerService>(
        new InjectionConstructor(typeof(IService1), null));
    

    * EDIT *

    Based on the comment below, what you actually want is another feature - named registrations.

    Basically, you have two implementations of IService1 and one of IService2. So, what you can do is register both of them, and then tell the container which one to use.

    First off, to register the second implementation, you need to give an explicit name:

    container.RegisterType<IService1, OtherService1Impl>("other");
    

    Then you can tell the container to resolve IService1 but use the name. This is the main reason that the ResolvedParameter type exists. Since you just want the default for IService2, you can use typeof() as a shorthand. You still need to specify both types for the parameters, but you don't need a specific value. If that makes any sense.

    container.RegisterType<ICustomerService, CustomerService>(
        new InjectionConstructor(new ResolvedParameter<IService1>("other"), typeof(IService2));
    

    That should do what you need.

    0 讨论(0)
  • 2020-12-23 10:40

    You can use containers hierarchy. Register common implementation in parent container, this instance will resolve if resolved through master container. Then create child container, and register alternative implementation in child container. This implementation will resolve If resolved through child container, i.e. registration in child container overrides registration in parent container.

    Here's the example:

    public interface IService {}
    
    public interface IOtherService {}
    
    // Standard implementation of IService
    public class StandardService : IService {}
    
    // Alternative implementaion of IService
    public class SpecialService : IService {}
    
    public class OtherService : IOtherService {}
    
    public class Consumer
    {
        public Consumer(IService service, IOtherService otherService)
        {}
    }
    
    private void Test()
    {
        IUnityContainer parent = new UnityContainer()
            .RegisterType<IService, StandardService>()
            .RegisterType<IOtherService, OtherService>();
    
        // Here standardWay is initialized with StandardService as IService and OtherService as IOtherService
        Consumer standardWay = parent.Resolve<Consumer>();
    
        // We construct child container and override IService registration
        IUnityContainer child = parent.CreateChildContainer()
            .RegisterType<IService, SpecialService>();
    
        // And here specialWay is initialized with SpecialService as IService and still OtherService as IOtherService
        Consumer specialWay = child.Resolve<Consumer>();
    
        // Profit!
    }
    

    Please note that using containers hierarchy you can know nothing about number of parameters in constructor, and that's great, because as long as you are bound to parameters count and their types you can't use full power of IoC. The less you know the better.

    0 讨论(0)
提交回复
热议问题