使用NInject绑定通用接口,如果未设置泛型类型的绑定,则使用默认值

时间:2011-05-20 01:49: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的每个可能的实现?

3 个答案:

答案 0 :(得分: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>>());

答案 1 :(得分: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 (!@interface.IsGenericType) continue;

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

        return null;
    }
}

答案 2 :(得分: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 ( !@interface.IsGenericType )
                {
                    continue;
                }

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

        return null;
    }
}