Ninject, Родовые ссылочные привязки - PullRequest
1 голос
/ 21 марта 2011

I думаю это подпадает под концепцию контекстной привязки , но документация 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> как «Эй, это то, что реализует параметры запроса!».Это работает , но я чувствую себя довольно неловко с этим.Является ли это неправильным использованием функции dynamic .NET 4.0?Или это еще одна причина, по которой он вообще существует?

Обновление (2)

Ради последовательности и сохранения этого поста относительно первоначального вопроса, который я открываюдругой вопрос для динамического вопроса.

1 Ответ

3 голосов
/ 21 марта 2011

Да, вы должны быть в состоянии справиться с этим с помощью Ninject Conventions.Я только изучаю часть конвенций в Ninject, и документация редкая;тем не менее, исходный код расширения Conventions довольно легкий и его легко читать / перемещать, а также Remo Gloor очень полезен как здесь, так и в списке рассылки.

Первое, что я хотел быtry является 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;
}

Итак, когда расширение Conventions сканирует класс CurrentUserQuery, оно увидит интерфейс IQueryFor<CurrentUser>.Общее определение этого интерфейса - IQueryFor<>, поэтому он будет совпадать, и этот тип должен быть зарегистрирован для этого интерфейса.

Наконец, есть RegexBindingGenerator.Он пытается сопоставить интерфейсы сканируемых классов с Regex, заданным в качестве аргумента конструктора.Если вы хотите узнать подробности о том, как это работает, вы сможете просмотреть исходный код для него прямо сейчас.

Кроме того, вы сможете написать любую реализацию IBindingGenerator, которая вам может понадобиться,так как договор довольно прост.

...