2011-05-20 8 views
10

Imagínese que tengo las siguientes clases e interfaces:Usando ninject a asociar una interfaz genérica, con un defecto si una unión de tipo genérico no está configurado

public interface IService<T> { } 

public class DefaultService<T> : IService<T> { } 

public class FooService : IService<Foo> { } 

public class BarService : IService<Bar> { } 

Después me gustaría ser capaz de obtener instancias desde el núcleo de esta manera:

Kernel.Get<IService<Foo>>(); // Should return FooService 
Kernel.Get<IService<Bar>>(); // Should return BarService 
Kernel.Get<IService<Dog>>(); // Should return DefaultService 
Kernel.Get<IService<Cat>>(); // Should return DefaultService 
Kernel.Get<IService<Giraffe>>(); // Should return DefaultService 

¿es posible fijaciones de programación utilizando ninject (posiblemente mediante la extensión de los convenios), de modo que yo no tengo que unir manualmente cada posible implementación de IService?

Respuesta

11

He estado trabajando en algo similar hace poco y se acercó con algo más simple solución de su problema (aunque un poco más débil).

Lo que debería ser suficiente es vincular una implementación genérica (DefaultService) a la interfaz genérica, y las implementaciones concretas (FooService, BarService) a las interfaces concretas. Cuando solicita una instancia concreta de la interfaz, Ninject resuelve si definió la vinculación concreta. Si lo hizo, le da la instancia adecuada, de lo contrario, se cae en la unión genérica. El siguiente código debería hacer el truco.

var kernel = new StandardKernel(); 
kernel.Bind(typeof(IService<>)).To(typeof(DefaultService<>)); 
kernel.Bind<IService<Foo>>().To<FooService>(); 
kernel.Bind<IService<Bar>>().To<BarService>(); 

EDIT:

El concepto funciona durante todo el Ninject, por lo que se puede utilizar junto con Extensions.Conventions también. p. definir los siguientes:

public class Foo{} 
public class Bar{} 
public class Dog{} 

public interface IService<T>{} 
public class DefaultService<T> : IService<T>{} 
public class FooService : IService<Foo>{} 
public class BarService : IService<Bar>{} 

convenciones de uso para unir los servicios:

kernel.Bind(x => x.FromThisAssembly() 
        .SelectAllClasses() 
        .InheritedFrom(typeof(IService<>)) 
        .BindSingleInterface()); 

y crear y verificar los servicios apropiados:

Assert.IsInstanceOf<BarService>(kernel.Get<IService<Bar>>()); 
Assert.IsInstanceOf<FooService>(kernel.Get<IService<Foo>>()); 
Assert.IsInstanceOf<DefaultService<Dog>>(kernel.Get<IService<Dog>>()); 
+0

Se ve bien, pero ¿qué versión de NInject estás usando? Todavía estoy usando 2.2 y me aparece el error "Hay más de un enlace compatible disponible". Esperemos que hayan solucionado esto en la versión 3. – cbp

+0

Estoy usando v3 por lo que es probable que sea una característica adicional; no me he dado cuenta de eso cuando escribí la respuesta. – Jan

+0

También debo señalar que esto no cumple con los requisitos de mi pregunta porque todavía tiene que vincular manualmente cada una de las clases de servicio. – cbp

1

Me di cuenta de cómo hacerlo después de un par de horas jugando con GenericBindingGenerator de NInject Convention.

Si alguien está interesado, puedo publicarlo.

Actualización:

/// <summary> 
/// Creates bindings on open generic types. 
/// This is similar to the out-of-the-box <see cref="GenericBindingGenerator" />, but allows a default class to be 
/// specified if no other bindings can be found. See the test case for usages. 
/// </summary> 
public class GenericBindingGeneratorWithDefault : IBindingGenerator 
{ 
    private static readonly Type TYPE_OF_OBJECT = typeof (object); 
    private readonly Type _contractType; 
    private Dictionary<Type, Type> _cachedBindings = new Dictionary<Type, Type>(); 
    private readonly Type _defaultType; 

    public GenericBindingGeneratorWithDefault(Type contractType, Type defaultType) 
    { 
     if (!(contractType.IsGenericType || contractType.ContainsGenericParameters)) 
     { 
      throw new ArgumentException("The contract must be an open generic type.", "contractType"); 
     } 
     _contractType = contractType; 
     _defaultType = defaultType; 
    } 

    /// <summary> 
    /// Processes the specified type creating kernel bindings. 
    /// </summary> 
    /// <param name="type">The type to process.</param> 
    /// <param name="scopeCallback">the scope callback.</param> 
    /// <param name="kernel">The kernel to configure.</param> 
    public void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel) 
    { 
     if (type == _defaultType) 
     { 
      kernel.Bind(_contractType).ToMethod(
       ctx => 
       { 
        var requestedType = ctx.Request.Service; 
        var resolution = _cachedBindings.ContainsKey(requestedType) 
             ? _cachedBindings[requestedType] 
             : _defaultType.MakeGenericType(ctx.GenericArguments); 
        return ctx.Kernel.Get(resolution); 
       }); 
     } 
     else 
     { 
      Type interfaceType = ResolveClosingInterface(type); 
      if (interfaceType != null) 
      { 
       _cachedBindings[interfaceType] = type; 
      } 
     } 
    } 

    /// <summary> 
    /// Resolves the closing interface. 
    /// </summary> 
    /// <param name="targetType">Type of the target.</param> 
    /// <returns></returns> 
    public Type ResolveClosingInterface(Type targetType) 
    { 
     if (targetType.IsInterface || targetType.IsAbstract) 
     { 
      return null; 
     } 

     do 
     { 
      Type[] interfaces = targetType.GetInterfaces(); 
      foreach (Type @interface in interfaces) 
      { 
       if ([email protected]) 
       { 
        continue; 
       } 

       if (@interface.GetGenericTypeDefinition() == _contractType) 
       { 
        return @interface; 
       } 
      } 
      targetType = targetType.BaseType; 
     } while (targetType != TYPE_OF_OBJECT); 

     return null; 
    } 
} 
+0

¿Lo puede postear? – chobo2

+0

sí, deberías publicar la solución –

+0

por favor postear ya que esto podría haber ayudado a resolver el problema ... – bbqchickenrobot

2

he tomado la libertad de refactorización la respuesta de @cbp, para que funcione para la nueva firma IBindingGenerator en Ninject v3 conventions. Está prácticamente reemplazando la firma del método Process() con la firma del método CreateBindings(), pero no probé esto, por lo que existe la posibilidad de que deba modificarlo un poco si lo usa.

/// <summary> 
/// Creates bindings on open generic types. 
/// This is similar to the out-of-the-box 
/// <see cref="GenericBindingGenerator" />, 
/// but allows a default class to be 
/// specified if no other bindings can be found. 
/// See the test case for usages. 
/// </summary> 
public class GenericBindingGeneratorWithDefault : IBindingGenerator 
{ 
    private static readonly Type TypeOfObject = typeof(object); 
    private readonly Type _contractType; 
    private readonly Dictionary<Type, Type> _cachedBindings; 
    private readonly Type _defaultType; 

    public GenericBindingGeneratorWithDefault(Type contractType, Type defaultType) 
    { 
     if (!(contractType.IsGenericType || contractType.ContainsGenericParameters)) 
      throw new ArgumentException("The contract must be an open generic type.", 
       "contractType"); 

     _cachedBindings = new Dictionary<Type, Type>(); 
     _contractType = contractType; 
     _defaultType = defaultType; 
    } 

    /// <summary> 
    /// Creates the bindings for a type. 
    /// </summary> 
    /// <param name="type">The type for which the bindings are created.</param> 
    /// <param name="bindingRoot">The binding root that is used to create the bindings.</param> 
    /// <returns> 
    /// The syntaxes for the created bindings to configure more options. 
    /// </returns> 
    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot) 
    { 
     if (type == null) throw new ArgumentNullException("type"); 
     if (bindingRoot == null) throw new ArgumentNullException("bindingRoot"); 

     if (type.IsInterface || type.IsAbstract) yield break; 

     if (type == _defaultType) 
     { 
      yield return bindingRoot.Bind(_contractType).ToMethod(
       ctx => 
        { 
         Type requestedType = ctx.Request.Service; 
         Type resolution = _cachedBindings.ContainsKey(requestedType) 
              ? _cachedBindings[requestedType] 
              : _defaultType.MakeGenericType(ctx.GenericArguments); 
         return ctx.Kernel.Get(resolution); 
        }); 
     } 
     else 
     { 
      Type interfaceType = ResolveClosingInterface(type); 
      if (interfaceType != null) 
      { 
       yield return bindingRoot.Bind(type).To(_cachedBindings[interfaceType]); 
      } 
     } 
    } 

    /// <summary> 
    /// Resolves the closing interface. 
    /// </summary> 
    /// <param name="targetType">Type of the target.</param> 
    /// <returns></returns> 
    private Type ResolveClosingInterface(Type targetType) 
    { 
     if (targetType.IsInterface || targetType.IsAbstract) return null; 

     do 
     { 
      Type[] interfaces = targetType.GetInterfaces(); 
      foreach (Type @interface in interfaces) 
      { 
       if ([email protected]) continue; 

       if (@interface.GetGenericTypeDefinition() == _contractType) 
       { 
        return @interface; 
       } 
      } 
      targetType = targetType.BaseType; 
     } while (targetType != TypeOfObject); 

     return null; 
    } 
} 
Cuestiones relacionadas