Внедрение зависимостей в фильтры действий ASP.NET MVC 3.Что не так с этим подходом? - PullRequest
76 голосов
/ 25 августа 2011

Вот настройки. Скажем, у меня есть фильтр действий, которому нужен экземпляр службы:

public interface IMyService
{
   void DoSomething();
}

public class MyService : IMyService
{
   public void DoSomething(){}
}

Затем у меня есть ActionFilter, которому нужен экземпляр этой службы:

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService; // <--- How do we get this injected

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

В MVC 1/2 введение зависимостей в фильтры действий было чем-то вроде боли в заднице. Наиболее распространенным подходом было использование инициатора настраиваемого действия, как можно увидеть здесь: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ Основная мотивация этого обходного пути заключалась в том, что этот следующий подход рассматривался как неаккуратная и жесткая связь с контейнером:

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(MyStaticKernel.Get<IMyService>()) //using Ninject, but would apply to any container
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

Здесь мы используем инжектор конструктора и перегружаем конструктор, чтобы использовать контейнер и внедрить сервис. Я согласен, что это тесно связывает контейнер с ActionFilter.

Мой вопрос, однако, заключается в следующем: теперь в ASP.NET MVC 3, где у нас есть абстракция используемого контейнера (через DependencyResolver), все ли эти обручи все еще необходимы? Позвольте мне продемонстрировать:

public class MyActionFilter : ActionFilterAttribute
{
   private IMyService _myService;

   public MyActionFilter()
      :this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService)
   {

   }

   public MyActionFilter(IMyService myService)
   {
      _myService = myService;
   }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       _myService.DoSomething();
       base.OnActionExecuting(filterContext);
   }
}

Теперь я знаю, что некоторые пуристы могут насмехаться над этим, но если серьезно, что будет недостатком? Он все еще тестируемый, так как вы можете использовать конструктор, который берет IMyService во время тестирования и таким образом внедрить фиктивный сервис. Вы не привязаны к какой-либо реализации контейнера DI, поскольку используете DependencyResolver, поэтому есть ли у этого подхода недостатки?

Кстати, вот еще один приятный подход для выполнения этого в MVC3 с использованием нового интерфейса IFilterProvider: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3

Ответы [ 3 ]

91 голосов
/ 25 августа 2011

Да, есть и недостатки, так как есть множество проблем с самим IDependencyResolver , и к ним можно добавить использование Singleton Service Locator, а также Bastard Injection .

Лучшим вариантом является внедрение фильтра как обычного класса, в который вы можете добавлять любые службы, которые вам нужны:

public class MyActionFilter : IActionFilter
{
    private readonly IMyService myService;

    public MyActionFilter(IMyService myService)
    {
        this.myService = myService;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if(this.ApplyBehavior(filterContext))
            this.myService.DoSomething();
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(this.ApplyBehavior(filterContext))
            this.myService.DoSomething();
    }

    private bool ApplyBehavior(ActionExecutingContext filterContext)
    {
        // Look for a marker attribute in the filterContext or use some other rule
        // to determine whether or not to apply the behavior.
    }

    private bool ApplyBehavior(ActionExecutedContext filterContext)
    {
        // Same as above
    }
}

Обратите внимание, как фильтр проверяет filterContext, чтобы определить, следует ли применять это поведение.

Это означает, что вы все еще можете использовать атрибуты, чтобы контролировать, должен ли фильтр применяться:

public class MyActionFilterAttribute : Attribute { }

Однако теперь этот атрибут полностью инертен.

Фильтр может быть составлен с необходимой зависимостью и добавлен к глобальным фильтрам в global.asax:

GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));

Более подробный пример этого метода, хотя и применяется к веб-API ASP.NET вместо MVC, см. В этой статье: http://blog.ploeh.dk/2014/06/13/passive-attributes

30 голосов
/ 25 августа 2011

Я не уверен, но я считаю, что вы можете просто использовать пустой конструктор (для части атрибут ), а затем иметь конструктор, который фактически вводит значение (для ) фильтр часть). *

Редактировать : После небольшого чтения кажется, что принятым способом сделать это является внедрение свойства:

public class MyActionFilter : ActionFilterAttribute
{
    [Injected]
    public IMyService MyService {get;set;}

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        MyService.DoSomething();
        base.OnActionExecuting(filterContext);
    }
}

Что касается , почему бы не использовать Service Locator вопрос: он в основном просто снижает гибкость внедрения зависимости. Например, что, если вы внедрили службу ведения журнала и хотели бы автоматически дать службе ведения журнала имя класса, в который она внедряется? Если вы используете конструктор инъекций, это будет прекрасно работать. Если вы используете средство поиска зависимостей / сервис-локатор, вам не повезет.

Обновление

Поскольку это было принято в качестве ответа, я бы хотел заняться протоколом и сказать, что я предпочитаю подход Марка Симана , потому что он отделяет ответственность фильтра действий от атрибута. Кроме того, расширение Ninject MVC3 имеет несколько очень мощных способов настройки фильтров действий через привязки. Для получения более подробной информации смотрите следующие ссылки:

Обновление 2

Как указал @usr в комментариях ниже, ActionFilterAttribute создаются при загрузке класса и действуют в течение всего времени жизни приложения. Если интерфейс IMyService не должен быть синглтоном, то он становится зависимой зависимостью . Если его реализация не является поточно-ориентированной, вам может быть очень больно.

Всякий раз, когда у вас есть зависимость с более коротким сроком службы, чем ожидаемая продолжительность жизни вашего класса, разумно вводить фабрику для создания этой зависимости по требованию, а не вводить ее напрямую.

6 голосов
/ 25 марта 2015

Решение, предложенное Марком Симэнном, кажется элегантным.Однако довольно сложный для простой задачи.Использование инфраструктуры путем реализации AuthorizeAttribute кажется более естественным.

Мое решение состояло в том, чтобы создать AuthorizeAttribute со статической фабрикой делегатов для службы, зарегистрированной в global.asax.Он работает для любого контейнера DI и чувствует себя немного лучше, чем локатор служб.

В global.asax:

MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve<IAuthorizeService>();

Мой класс пользовательских атрибутов:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } 

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return AuthorizeServiceFactory().AuthorizeCore(httpContext);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...