Включение зависимостей в фильтры действия ASP.NET MVC 3. Что не так с этим подходом?

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

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 инъекции зависимостей в фильтры действия были немного болью в заднице. Наиболее распространенный подход заключался в том, чтобы использовать пользовательский action invoker, как можно увидеть здесь: 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()) //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-нетто-3-MVC

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

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

 public class MyActionFilter : ActionFilterAttribute { [Injected] public IMyService MyService {get;set;} public override void OnActionExecuting(ActionExecutingContext filterContext) { MyService.DoSomething(); base.OnActionExecuting(filterContext); } } 

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

Обновить

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

Обновление 2

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

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

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

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

 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

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

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

В global.asax:

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

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

 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class MyAuthorizeAttribute : AuthorizeAttribute { public static Func AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } } 
  • Вопросы об использовании Ninject
  • ContextLoaderListener или нет?
  • Как использовать контейнер DI / IoC с привязкой модели в ASP.NET MVC 2+?
  • Регистрация фаз (прототип) во время выполнения весной
  • Доступ к инжектору Guice в его модуле?
  • Зависимость впрыска wcf
  • Как я могу вводить в @FacesConverter?
  • Инъекция Мокито издевается над весенним бобаном
  • Инъекция зависимостей в Struts2 Доступ к фазам с включенным сеансом
  • «Взаимозависимость» (DI) «дружественная» библиотека
  • Модульный ввод / вывод файлов
  • Давайте будем гением компьютера.