Ninject в фильтре действий

Я создал собственный фильтр действий и привязываю его к методу BindFilter Ninject:

public class ExtendModelAttribute : FilterAttribute {}

public class ExtendModelFilter : IActionFilter
{
    private IKernel kernel;
    public ExtendModelFilter(Func<IKernel> kernel)
    {
        this.kernel = kernel;
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // TODO:
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
    }
}

Я привязываю свой фильтр так:

 kernel.BindFilter<ExtendModelFilter>(FilterScope.Action, 0).WhenActionMethodHas<ExtendModelAttribute>();

Пока все работает отлично. Я хочу добиться следующего:

[ExtendModel]
public ActionResult Index()
{
    return View(new IndexModel());
}

Я хочу, чтобы фильтр ExtendModel определял, какой тип модели используется, а затем находил правильную зависимость:

public interface IModelExtender<TModel> {
    void Extend(TModel model);
}

public class IndexModelExtender : IModelExtender<IndexModel> {
    public void Extend(IndexModel model)
    {
        // extend the model here with anything extra that is required
    }
}

Я не уверен, как написать код в фильтре действий, чтобы получить экземпляр IModelExtender:

public void OnActionExecuted(ActionExecutedContext filterContext)
{
    // TODO:
    // I Need to look at the filterContext Model to determine which 
    // type of IModelExtender<T> to create:
    // For Example, when [ExtendModel] is applied on the Index() method above
    // then I need it to resolve to IndexModelExtender
}

Может быть, то, что я хочу сделать, даже невозможно? Поскольку во время компиляции я не знаю, что такое T, есть ли способ сделать то, что я хочу?

EDIT
Вот пример того, что может сделать ModelExtender:

public class IndexModelExtender : IModelExtender<IndexModel>
{
    public IndexModelExtender(IFooRepository fooRepository, IBarRepository barRepository)
     {
        // ...
     }

     public void Extend(IndexModel model)
     {
         model.SelectList1 = new SelectList(fooRepository.GetFoos(), "Description", "Id");
         model.SelectList2 = new SelectList(barRepository.GetBars(), "Description", "Id");
     }
}

Я хочу избежать такого кода в своем контроллере:

 public ActionResult Index()
 {
     var model = new IndexModel();
     // populate select lists here
     return View(model);
 }

 [HttpPost]
 public ActionResult Index(IndexModel model)
 {
     if(!ModelState.IsValid ) {
        // populate the same stuff here
     }
  }

person Dismissile    schedule 21.02.2012    source источник
comment
Можете ли вы объяснить, что вы пытаетесь сделать со всем этим? Звучит так, как будто вы хотите получить помощь в работе над очень сложным решением, которое можно легко решить с помощью Html.RenderAction().   -  person Remo Gloor    schedule 21.02.2012
comment
@RemoGloor Я хочу создать фильтр действий, который будет заполнять списки поиска в моих моделях. В приведенном выше примере мой IndexModelExtender будет зависеть от некоторого репозитория/службы, который будет заполнять все элементы для SelectList, поэтому я держу его вне контроллера.   -  person Dismissile    schedule 21.02.2012
comment
@RemoGloor Я обновил свой вопрос, добавив немного больше деталей.   -  person Dismissile    schedule 21.02.2012
comment
@Dismissile - я поддерживаю remo, это кажется ужасно сложным решением простой проблемы.   -  person Erik Funkenbusch    schedule 21.02.2012
comment
@MystereMan Когда я вижу, что шаблонный код одного и того же типа пишется снова и снова, я начинаю искать способы его улучшения. Каждое действие контроллера должно заполнять некоторые списки поиска в действии GET и снова в действии POST, если состояние модели недопустимо. Я просто хотел найти способ избежать дублирования всего этого кода и начал исследовать этот путь.   -  person Dismissile    schedule 21.02.2012
comment
@Dismissile - Хотя это может быть правдой, теперь вы разрушаете целостность действия контроллера и разделяете единую ответственность на несколько частей. Что еще более важно, вы должны использовать Service Location, чтобы заставить эту работу работать, что является хорошо известным анти-шаблоном. Зависимости, которые вам нужны, внедряются в контроллер, а не в атрибут, поэтому вам нужно сломать DI, чтобы попытаться сохранить некоторый код.   -  person Erik Funkenbusch    schedule 22.02.2012


Ответы (1)


Вам придется использовать отражение, потому что тип модели известен только во время выполнения, а ваш расширитель является универсальным:

public class ExtendModelFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        object model = null;
        if (filterContext.Result is ViewResultBase)
        {
            model = ((ViewResultBase)filterContext.Result).Model;
        }
        else if (filterContext.Result is JsonResult)
        {
            model = ((JsonResult)filterContext.Result).Data;
        }
        // TODO: you could continue with the else if here to take
        // into account some other action results that have the notion of model
        // like for example some custom action results that you might have written

        if (model == null)
        {
            // we have no model => nothing to extend
            return;
        }

        var extenderType = typeof(IModelExtender<>).MakeGenericType(model.GetType());
        var extender = DependencyResolver.Current.GetService(extenderType);
        var extend = extenderType.GetMethod("Extend");
        extend.Invoke(extender, new[] { model });
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
    }
}

Вы также заметите, что я реорганизовал фильтр пользовательских действий, чтобы использовать текущий преобразователь зависимостей и сделать его независимым от NInject. Конечно, вы можете сохранить зависимость IKernel, если хотите.

person Darin Dimitrov    schedule 21.02.2012
comment
Спасибо Дарин. У тебя ВСЕГДА есть ответы. - person Dismissile; 21.02.2012
comment
Однако это решение требует использования Service Location. Я понимаю, что выбора не так много, но именно поэтому вы обычно выполняете эту работу в контроллере, потому что зависимости внедряются туда, а не в код атрибута. - person Erik Funkenbusch; 22.02.2012