Ninject,通用参考绑定

时间:2011-03-21 16:13:39

标签: dynamic asp.net-mvc-3 ioc-container ninject

认为这属于上下文绑定的概念,但是Ninject文档虽然非常彻底,但没有任何示例与我目前的情况相近。真的很确定我还是很困惑。

我基本上有代表查询参数结构的类。比如..

class CurrentUser {
  string Email { get; set; }
}

然后是一个代表其数据库检索的接口(在数据层中)

class CurrentUserQuery : IQueryFor<CurrentUser> {
   public CurrentUserQuery(ISession session) {
     this.session = session;
   }

   public Member ExecuteQuery(CurrentUser parameters) {
      var member = session.Query<Member>().Where(n => n.Email == CurrentUser.Email);
      // validation logic
      return member;
   }
}

现在,我想要做的是建立一个可以获取给定对象的简单类,从中获取IQueryFor<T>类,从我的Ninject.IKernel(构造函数参数)构造它,并且对它执行ExecuteQuery方法,通过给定的对象。

我能做到这一点的唯一方法就是基本上做到以下几点......

Bind<IQueryFor<CurrentUser>>().To<CurrentUserQuery>();

这解决了一个查询的问题。但我预计会有大量的查询...所以这种方法不仅会变得单调乏味,而且非常容易出现冗余。

我想知道在Ninject中是否存在继承方式以包含这种行为。 : -

最后,我(理想)使用它的方式是......

class HomeController : Controller {
  public HomeController(ITransit transit) {
    // injection of the transit service
  }

  public ActionResult CurrentMember() {
    var member = transit.Send(new CurrentUser{ Email = User.Identity.Name });
  }
}

显然这不会正常工作,因为Send方法无法知道返回类型。

我一直在广泛地解析Rhino Service Bus并投射Alexandria以尝试制作轻巧,轻便的实施方案。

更新

我已经能够使用.NET 4.0 dynamic对象获得相当理想的结果,例如以下内容......

dynamic Send<T>(object message);

然后宣布我的界面......

public interface IQueryFor<T,K>
{
    K Execute(T message);
}

然后使用......

public class TestCurrentMember
{
    public string Email { get; set; }
}

public class TestCurrentMemberQuery : IConsumerFor<TestCurrentMember, Member>
{
    private readonly ISession session;

    public TestCurrentMemberQuery(ISession session) {
        this.session = session;
    }

    public Member Execute(TestCurrentMember user)
    {
        // query the session for the current member
        var member = session.Query<Member>()
            .Where(n => n.Email == user.Email).SingleOrDefault();

        return member;
    }
}

然后在我的控制器中......

        var member = Transit.Send<TestCurrentMemberQuery>(
            new TestCurrentMember { 
                Email = User.Identity.Name 
            }
        );

有效地使用<T>作为我的'嘿,这就是实现查询参数!'。 确实工作,但我觉得很不舒服。这是否适用于.NET 4.0的dynamic功能?或者这就是为什么它首先存在的原因?

更新(2)

为了保持一致性并保持这篇文章相对于最初的问题,我为动态问题开辟了一个不同的问题。

1 个答案:

答案 0 :(得分:3)

是的,你应该能够使用Ninject Conventions处理这个问题。我刚学习Ninject的公约部分,文档很少;但是,Conventions扩展的源代码非常简单,易于阅读/导航,Remo Gloor在这里和邮件列表上都非常有用。

我要尝试的第一件事是GenericBindingGenerator(根据应用程序的需要更改过滤器和范围):

internal class YourModule : NinjectModule
{
    public override void Load()
    {
        Kernel.Scan(a => {
                    a.From(System.Reflection.Assembly.GetExecutingAssembly());
                    a.InTransientScope();
                    a.BindWith(new GenericBindingGenerator(typeof(IQueryFor<>)));
                });
    }
}

任何BindingGenerator的核心都是这个界面:

public interface IBindingGenerator 
{
    void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel);
}

默认绑定生成器只检查类的名称是否与接口名称匹配:

public void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel)
{
    if (!type.IsInterface && !type.IsAbstract)
    {
        Type service = type.GetInterface("I" + type.Name, false);
        if (service != null)
        {
            kernel.Bind(service).To(type).InScope(scopeCallback);
        }
    }
}

GenericBindingGenerator将类型作为构造函数参数,并检查扫描的类上的接口,以查看这些接口的通用定义是否与传递给构造函数的类型匹配:

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


public void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel)
{
    Type service = this.ResolveClosingInterface(type);
    if (service != null)
    {
        kernel.Bind(service).To(type).InScope(scopeCallback);
    }
}

public Type ResolveClosingInterface(Type targetType)
{
    if (!targetType.IsInterface && !targetType.IsAbstract)
    {
        do
        {
            foreach (Type type in targetType.GetInterfaces())
            {
                if (type.IsGenericType && (type.GetGenericTypeDefinition() == this._contractType))
                {
                    return type;
                }
            }
            targetType = targetType.BaseType;
        }
        while (targetType != TypeOfObject);
    }
    return null;
}

因此,当约定扩展程序扫描类CurrentUserQuery时,它将看到界面IQueryFor<CurrentUser>。该接口的通用定义是IQueryFor<>,因此它将匹配,并且该类型应该为该接口注册。

最后,有一个RegexBindingGenerator。它尝试将扫描的类的接口与作为构造函数参数给出的Regex进行匹配。如果您想查看其运行方式的详细信息,您现在应该可以仔细阅读它的源代码。

此外,您应该能够编写您可能需要的IBindingGenerator的任何实现,因为合同非常简单。