EF DbContext за веб-запрос + Custom RoleProvider = RoleProvider за веб-запрос или синглтон? - PullRequest
3 голосов
/ 22 марта 2012

Используя EF DbContext, завернутый в интерфейс (ы), зависимость вводится для каждого веб-запроса, чтобы убедиться, что весь запрос работает с одним и тем же контекстом.Также есть пользовательский RoleProvider, который использует интерфейс DbContext для настройки служб авторизации.

До сих пор я использовал шаблон локатора служб для разрешения экземпляра DbContext в пользовательских RoleProvider.безошибочный конструктор.Это вызвало некоторые незначительные проблемы, потому что RoleProvider является однотонным, поэтому он может удерживать DbContext бесконечно, тогда как другие запросы могут захотеть утилизировать его во время Application_EndRequest.

Теперь у меня есть решение , основанное на этом , хотя с использованием контейнера ioc, отличного от windsor.Я могу использовать DI для создания нового экземпляра RoleProvider для каждого http-запроса.

У меня такой вопрос, должен ли я?

Наличие открытого DbContext зависанияот RoleProvider кажется расточительным.С другой стороны, я знаю, что каждый MVC AuthorizeAttribute достигает RoleProvider (если у него есть ненулевое свойство Roles, что есть у большинства из нас), поэтому я полагаю, что было бы полезно иметь уже DbContextв ожидании.

В качестве альтернативы можно ввести другое DbContext для RoleProvider, которое отсутствует для веб-запроса.Таким образом, DbContext, которые существуют только для веб-запроса, могут быть расположены в конце, не влияя на одиночный RoleProvider.

Является ли какой-либо подход лучше и почему?

Обновление после комментариев

Стивен, это по сути то, что я и сделал.Разница лишь в том, что я не беру зависимость от System.Web.Mvc.DependencyResolver.Вместо этого у меня в основном есть та же самая вещь в моем собственном проекте, только названная по-другому:

public interface IInjectDependencies
{
    object GetService(Type serviceType);
    IEnumerable<object> GetServices(Type serviceType);
}

public class DependencyInjector
{
    public static void SetInjector(IInjectDependencies injector)
    {
        // ...
    }

    public static IInjectDependencies Current
    {
        get
        {
           // ...
        }
    }
}

Эти классы являются частью основного API проекта и находятся в другом проекте, чем MVC.Таким образом, этот другой проект (наряду с доменным проектом) не должен принимать зависимость от System.Web.Mvc, чтобы компилировать его DependencyResolver.

Учитывая эту структуру, заменяя Unity с SimpleInjectorдо сих пор безболезненноВот как выглядит многоцелевая одиночная установка RoleProvider:

public class InjectedRoleProvider : RoleProvider
{
    private static IInjectDependencies Injector 
        { get { return DependencyInjector.Current; } }

    private static RoleProvider Provider 
        { get { return Injector.GetService<RoleProvider>(); } }

    private static T WithProvider<T>(Func<RoleProvider, T> f)
    {
        return f(Provider);
    }

    private static void WithProvider(Action<RoleProvider> f)
    {
        f(Provider);
    }

    public override string[] GetRolesForUser(string username)
    {
        return WithProvider(p => p.GetRolesForUser(username));
    }

    // rest of RoleProvider overrides invoke WithProvider(lambda)
}

Web.config:

<roleManager enabled="true" defaultProvider="InjectedRoleProvider">
    <providers>
        <clear />
        <add name="InjectedRoleProvider" type="MyApp.InjectedRoleProvider" />
    </providers>
</roleManager>

Контейнер IoC:

Container.RegisterPerWebRequest<RoleProvider, CustomRoleProvider>();

Что касается CUD, то здесьЭто только 1 метод, реализованный в моем CustomRoleProvider:

public override string[] GetRolesForUser(string userName)

Это единственный метод, используемый MVC AuthorizeAttributeIPrincipal.IsInRole), и из всех других методов я просто

throw new NotSupportedException("Only GetRolesForUser is implemented.");

Поскольку у провайдера нет роли CUD, я не беспокоюсь о транзакциях.

1 Ответ

3 голосов
/ 23 марта 2012

Взгляните на проект Griffin.MvcContrib .Он содержит реализацию MembershipProvider и RoleProvider, в которой используется MVC DependencyResolver.

. Вы можете настроить RoleProvider следующим образом:

<roleManager enabled="true" defaultProvider="MvcRoleManager">
  <providers>
    <clear />
    <add name="MvcRoleManager" 
      type="Griffin.MvcContrib.Providers.Roles.RoleProvider, Griffin.MvcContrib"
    />
  </providers>
</roleManager>

Используетсякласс System.Web.MVC DependencyResolver, поэтому вам необходимо настроить реализацию IDependencyResolver для используемого вами DI-контейнера.Для простого инжектора (и интеграционного пакета NuGet SimpleInjector.MVC3 ) вам потребуется следующая конфигурация в вашем событии Application_Start:

container.RegisterAsMvcDependencyResolver();

Griffin.MvcContrib.Providers.Roles.RoleProvider зависит отIRoleRepository, который определен в той же сборке.Вместо того, чтобы реализовывать полный поставщик ролей, вы можете просто реализовать IRoleRepository и зарегистрировать его в своем контейнере:

container.Register<IRoleRepository, MyOwnRoleRepository>();

Этот проект можно найти здесь в NuGet.

ОБНОВЛЕНИЕ

А теперь давайте ответим на вопрос:

Роль Griffin.MvcContrib будет одноэлементной, а вопрос теперь переместится на IRoleRepositoryи его зависимости, но вопрос действительно остается.

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

Однако, когда вы используете поставщика ролей для обновления базы данных, вещиполучить разные.В этом случае я бы дал ему собственный контекст и позволил бы ему явно фиксировать его после каждой операции.Потому что, если вы этого не сделаете, кто собирается совершить эти изменения?При запуске в контексте обработчика команд (и особенно TransactionCommandHandlerDecorator ) операция будет зафиксирована после успешного выполнения команды и откатана после сбоя команды.Возможно, это хорошо, чтобы откатить это изменение, когда команда не удалась.Но когда поставщик роли запускается вне контекста обработчика команды, кто собирается его зафиксировать?Я уверен, что вы сможете решить эту проблему, но я полагаю, что в итоге вы получите систему, которую трудно понять, и она поразит других разработчиков, которые пытаются выяснить, почему эти изменения не зафиксированы.

...