2011-05-20 65 views
10

想象我有以下的类和接口:使用NInject绑定一个通用接口,使用默认的,如果为通用类型绑定未设置

public interface IService<T> { } 

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

public class FooService : IService<Foo> { } 

public class BarService : IService<Bar> { } 

然后,我会希望能够获得实例从这样的内核:

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 

是否有可能使用NInject(可能使用公约扩展)设置绑定,这样我就不必每一个可能的实现IService的手动绑定?

回答

11

我一直对类似的东西最近并与您的问题有些简单的解决方案提出了(虽然有点弱)。

应该满足的是将泛型实现(DefaultService)绑定到通用接口,并将具体实现(FooService,BarService)绑定到具体接口。当您询问接口的具体实例时,Ninject会解析您是否定义了具体绑定。如果你这样做了,它会给你适当的实例,否则就会进入通用绑定。下面的代码应该可以做到。

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

编辑:

概念作品在整个Ninject,所以你可以随着Extensions.Conventions使用它。 例如定义如下:

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

使用约定来绑定服务:

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

创建并检查相应的服务:

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

看起来不错,但是您使用的是哪种版本的NInject?我仍在使用2.2,并且出现错误“有多个匹配的绑定可用。”希望他们已经在第3版中解决了这个问题。 – cbp 2012-07-27 01:53:48

+0

我使用的是v3,所以它可能是一个附加功能 - 当我写答案时还没有意识到。 – Jan 2012-07-27 10:18:41

+0

我还应该指出,这并不完全符合我的问题的要求,因为您仍然需要手动绑定每个服务类。 – cbp 2012-07-29 10:37:07

1

我想了一下在使用NInject Convention的GenericBindingGenerator几个小时后如何做到这一点。

如果有人有兴趣,我可以发布它。

更新:

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

你可以发布吗? – chobo2 2011-06-05 20:57:04

+0

是的,你应该发布解决方案 – 2011-06-24 22:34:12

+0

请发布,因为这可能有助于解决概率.... – bbqchickenrobot 2011-09-13 10:35:24

2

我把重构的答案的自由@cbp,以便它适用于Ninject v3 conventions中的新IBindingGenerator签名。这几乎取代了方法签名Process()方法签名与CreateBindings()方法签名,但我没有测试这个,所以有可能你必须稍微调整它,如果你使用它。

/// <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; 
    } 
} 
相关问题