2010-10-30 13 views
28

Estoy usando Microsoft Unity. Tengo una interfaz ICustomerService y su implementación CustomerService. Puedo registrarlos para el contenedor Unidad utilizando el siguiente código:Microsoft Unity. ¿Cómo especificar un cierto parámetro en el constructor?

container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager()); 

Si CustomerService tiene un determinado parámetro en su constructor (por ejemplo ISomeService1), utilizo el siguiente código (necesito especificar SomeService1):

container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager(), new InjectionConstructor(new SomeService1())); 

Sin problemas aquí.

El problema aparece cuando CustomerService clase tiene dos parámetros (no uno param como en el ejemplo anterior) en su constructor (por ejemplo ISomeService1 y ISomeService2). Funciona bien cuando estoy usando el siguiente código: container.RegisterType<ICustomerService, CustomerService>(new TransientLifetimeManager(), new InjectionConstructor(new SomeService1(), new SomeService2()));

El problema es que no quiero especificar SomeService2() para el segundo parámetro. Quiero especificar solo el primer parámetro - SomeService1(). Pero me sale el error de que necesito especificar ninguno o ambos parámetros.

¿Cómo puedo especificar solo el primer parámetro del constructor?

+0

Hola and.maz, llegaste cualquier solución en la que no es necesario para proporcionar los otros parámetros de tipo así. algo así como valor clave donde podemos especificar el nombre del constructor y el valor – rdhaundiyal

Respuesta

0

Puede predeterminar el segundo parámetro al constructor (por ejemplo, =null) u ofrecer un constructor de parámetro único además del constructor de dos parámetros sobrecargándolo.

57

Su mejor respuesta es usar el contenedor.

Lo que está haciendo es decir "al construir este tipo, use esta instancia específica del objeto". Esto no aprovecha la capacidad del contenedor para crear una instancia para usted. En su lugar, debe registrar IService1 e IService2 en el contenedor. Luego, dígale al contenedor que resuelva esas dependencias por usted.

se ve algo como esto:

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

Lo que esto hace es decirle al contenedor "cada vez que hay una dependencia de tipo IService1, nuevo un nuevo objeto de tipo SomeService1 y de la mano de esa" y lo mismo para IService2 .

Así que, a continuación, debe decirle al contenedor qué hacer con ICustomerService. En la mayor generalidad, que haría esto:

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

Esto le dice al contenedor: cuando se resuelve ICustomerService, nuevo hasta una instancia de Servicio a Cliente utilizando el constructor que toma IService1 y IService2. Para obtener esos parámetros, vuelva a llamar al contenedor para resolver esos tipos.

Esto es un poco detallado, y un caso común, por lo que hay algunos accesos directos. En primer lugar, se puede pasar un objeto de tipo en lugar de hacer nueva ResolvedParameter, así:

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

Como otra forma abreviada, si a Cliente sólo tiene un constructor, o si el que usted desea llamado es el que lleva la mayor lista de parámetros, puede dejar InjectionConstructor completamente, ya que ese es el constructor que elegirá el contenedor en ausencia de otra configuración.

container.RegisterType<ICustomerService, CustomerService>(); 

El formulario que está utilizando normalmente se utiliza cuando se desea un valor específico para un parámetro pasado constructor en lugar de resolver el servicio a través del recipiente.

Para responder a su pregunta original, no puede hacer exactamente lo que ha dicho. El parámetro constructor necesita un valor de A de algún tipo. Sin embargo, podría poner cualquier otra cosa que desee, normalmente nulo funciona.

Tenga en cuenta que también puede mezclar los dos formularios. Por ejemplo, si desea resolver IService1 y pasar null para el parámetro IService2, haga lo siguiente:

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

* EDITAR *

Basado en el comentario anterior, lo que realmente quiere es otra característica - registros nombrados.

Básicamente, tiene dos implementaciones de IService1 y una de IService2. Entonces, lo que puedes hacer es registrarlos a ambos y luego decirle al contenedor cuál usar.

En primer lugar, para registrar la segunda aplicación, es necesario dar un nombre explícito:

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

A continuación, puede decirle al contenedor para resolver IService1 pero el uso del nombre. Esta es la razón principal por la que existe el tipo ResolvedParameter. Como solo quiere el valor predeterminado para IService2, puede usar typeof() como una forma abreviada. Aún necesita especificar ambos tipos para los parámetros, pero no necesita un valor específico. Si eso tiene algún sentido.

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

Eso debería hacer lo que necesita.

+1

Chris, gracias. Pero el problema es que tengo dos implementaciones de IService1. Vamos a llamarlos Service1Impl1 y Service1Impl2. Ambos se usan en la solución. CustomerManager debe usar Service1Impl1 y OrderManager debe usar Service1Impl2. Es por eso que debo especificar Service1Impl1 como el primer parámetro de CustomerService. Pero no quiero especificar el segundo parámetro porque solo hay una implementación utilizada de IService2 y ya está registrada con el siguiente código fuente: container.RegisterType (); –

+0

Editaré mi respuesta, requerirá un poco más de detalle de lo que puedo encajar en un comentario. –

+1

Gran respuesta Chris. –

11

Como una alternativa de respuesta Chris Tavares', puede dejar que el contenedor resolver sólo el segundo parámetro:

container.RegisterType<ICustomerService, CustomerService>(
    new InjectionConstructor(new SomeService1(), new ResolvedParameter<IService2>()); 
2

Puede utilizar recipientes jerarquía. Registre la implementación común en el contenedor primario, esta instancia se resolverá si se resuelve a través del contenedor maestro. A continuación, cree el contenedor secundario y registre la implementación alternativa en el contenedor secundario. Esta implementación se resolverá si se resuelve a través del contenedor secundario, es decir, el registro en el contenedor secundario anula el registro en el contenedor principal.

Aquí está el ejemplo:

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! 
} 

Tenga en cuenta que el uso de contenedores de jerarquía se puede saber nada acerca de número de parámetros en el constructor, y eso es genial, porque mientras usted está limitado a los parámetros de conteo y sus usted tipos no puede usar toda la potencia de IoC. Entre menos sepas, mejor.

6

Chris Tavares dio una buena respuesta con mucha información.

Si tiene muchos parámetros para inyectar, generalmente se trata de interfaces o instancias que Unity puede resolver mediante las técnicas de differnet. Pero, ¿qué ocurre si desea proporcionar solo un parámetro que no pueda resolverse automáticamente, p. una cadena para un nombre de archivo?

Ahora tiene que proporcionar todos los typeof(IMyProvider) y una cadena o instancia.Pero, para ser sincero, el hecho de proporcionar los tipos podría ser hecho por Unity, porque Unity ya tiene una estrategia para elegir el mejor ctor.

Así que codifica un reemplazo para 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; 
      } 
     } 
    } 
} 

Uso:

container.RegisterType(new TransientLifetimeManager(), new InjectionConstructorRelaxed(
    new SomeService1("with special options") 
    //, new SomeService2() //not needed, normal unity resolving used 
    //equivalent to: , typeof(SomeService2) 
    )); 
Cuestiones relacionadas