2011-05-20 68 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; 
    } 
} 
相關問題