Фильтр действий MVC3 с использованием базы данных (EF 4.1 DBContext, Ninject) - PullRequest
2 голосов
/ 22 апреля 2011

Я пытаюсь настроить фильтр «Авторизация» для действия, создавая собственный ActionFilterAttribute, где я выполняю поиск в базе данных, чтобы определить, имеет ли пользователь доступ к определенному ресурсу.

В моем классе, унаследованном от ActionFilterAttribute, я создал свойство Injected (Ninject) для хранения службы, которую я использую для доступа к базе данных. У меня есть конструктор без параметров, поэтому я могу использовать его в качестве атрибута своих действий. В методе «OnActionExecuting» я могу получить доступ к свойству Injected (оно не равно null), но используемый базовый DBCotext закрыт.

Это нормально работало, вплоть до окончательной первоначальной версии MVC3, где в примечаниях к выпуску указано:

Срочные изменения : В предыдущих версиях ASP.NET MVC фильтры действий создавались в соответствии с просьба за исключением нескольких случаев. это поведение никогда не было гарантировано поведение, а просто реализация деталь и договор на фильтры должен был считать их лицами без гражданства. В ASP.NET MVC 3, фильтры кешируются больше агрессивно. Поэтому любой кастом фильтры действий, которые неправильно хранят состояние экземпляра может быть нарушено.

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

Операция не может быть завершена потому что DbContext был расположенный.

это то, что, я думаю, я должен ожидать, учитывая заметки о критических изменениях.

Мой вопрос таков: какой предпочтительный / рекомендуемый способ выполнить то, что мне нужно сделать? Должно ли это быть в ActionFilterAttribute или эта «авторизация» должна быть сделана где-то еще?

Ответы [ 2 ]

3 голосов
/ 22 апреля 2011

Я бы сделал аутентификацию в Application_AuthenticateRequest и авторизацию в вашем атрибуте, используя Thread.CurrentPrincipal, но ваш метод тоже должен работать. Вам просто нужно учитывать тот факт, что DbContext будет отличаться для каждого запроса, но ваш атрибут не будет. Нечто подобное должно сработать (я полагаю, вы используете DependencyResolver):

public class MyMightyAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var context = (DbContext)DependencyResolver.Current.GetService(typeof(DbContext))
        // authenticate, authorize, whatever
        base.OnActionExecuting(filterContext);
    }
}
0 голосов
/ 10 апреля 2014

Я боролся с этим некоторое время и наконец решил свою проблему.Итак, вот мое решение в надежде, что оно может помочь кому-то еще.

Настройка: 1. У меня есть проект MVC3, фильтр настраиваемых действий, который обращается к БД с помощью EF5 через бизнес-сервис.2. Я использую Unity и unity.MVC, чтобы разрешить свои зависимости для каждого запроса.3. Я использую внедрение свойств в свой пользовательский фильтр действий, так как он имеет конструктор без параметров.

Результат.Внедрение зависимостей работает корректно для всех сервисов, используемых действиями, мой EF DbContext корректно удаляется в конце каждого запроса.

Проблема Несмотря на то, что моя зависимость свойства решена в моем фильтре настраиваемых действий, он содержит устаревшиеэкземпляр моего DbContext (например, он, кажется, был кэширован из предыдущего запроса)

Как упоминалось в предыдущих статьях, MVC3 более агрессивен в отношении кэширования фильтра, и на состояние фильтра нельзя полагаться.Поэтому было предложено разрешить зависимость в методе OnActionExecuting.Таким образом, я удалил свое введенное свойство и сделал то, что назвало решимость, в моем контейнере единства.Однако я все еще получил устаревшую версию DbContext.Любые изменения в БД были правильно запрошены в моих основных действиях, но фильтр пользовательских действий не принимал их.

Решение.Unity.MVC Управляет временем жизни каждого запроса, используя дочерние контейнеры и удаляя их в конце каждого запроса.Разрешая мои зависимости в фильтре действий из моего контейнера Unity, я разрешал из родительского контейнера, который не удаляется при каждом запросе.

Поэтому вместо

IoC.Instance.CurrentContainer.Resolve<IService>();

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

var childContainer = HttpContext.Current.Items["perRequestContainer"] as IUnityContainer;
var service = childContainer.Resolve<IServcie>();

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

Хорошо, небольшое уточнениечтобы мой модульный тест позволил внедрить макет сервиса.1. удалите разрешение зависимостей из OnActionexecuting и добавьте два конструктора.

public MyCustomActionfilter() : this(((IUnityContainer)HttpContext.Current.Items["perRequestContainer"].Resolve<IService>())

и

public MyCustomActionfilter(IService service)
{
    this.service = service;
}

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

...