MVC — простой инжектор и атрибут, вызывающий исключения контекста (EF)

Если я запускаю свое приложение и позволяю ему установиться, оно отлично работает.

Однако, когда я отлаживаю свое приложение и закрываю вкладку браузера до того, как оно что-либо инициализирует, а затем вызываю другое, например localhost:81/Home/Test, оно выдает исключение при извлечении данных из БД (EF).

Это исключение возникает во время вызова фильтра CultureResolver, который затем вызывает LanguageService. Внутри LanguageService есть вызов БД для получения всех доступных языков.

У меня есть много разных исключений, например:

  • Контекст нельзя использовать во время создания модели. Это исключение может быть вызвано, если контекст используется внутри метода OnModelCreating или если к одному и тому же экземпляру контекста одновременно обращаются несколько потоков. Обратите внимание, что члены экземпляра DbContext и связанных классов не гарантируют потокобезопасность.
  • В экземпляре объекта не задана ссылка на объект.
  • Базовый провайдер дал сбой при открытии.

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

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

У меня есть следующее:

SimpleInjectorInitializer.cs

public static class SimpleInjectorInitializer
{
    /// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
    public static void Initialize()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

        InitializeContainer(container);
        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        container.Verify();
        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters, container);
    }

    private static void InitializeContainer(Container container)
    {
        container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

        /* Bindings... */

        container.RegisterPerWebRequest<IAjaxMessagesFilter, AjaxMessagesFilter>();
        container.RegisterPerWebRequest<ICustomErrorHandlerFilter, CustomErrorHandlerFilter>();
        container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();
    }
}

FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
    {
        filters.Add(container.GetInstance<ICultureInitializerFilter>());
        filters.Add(container.GetInstance<ICustomErrorHandlerFilter>());
        filters.Add(container.GetInstance<IAjaxMessagesFilter>());
    }
}

CultureResolver.cs

public class CultureResolver : ICultureResolver
{
    ILanguageService Service;
    public CultureResolver(ILanguageService Service)
    {
        this.Service = Service;
    }

    public string Resolve(string CultureCode)
    {
        // Get the culture by name or code (pt / pt-pt)
        ILanguageViewModel language = Service.GetByNameOrCode(CultureCode);

        if (language == null)
        {
            // Get the default language
            language = Service.GetDefault();
        }

        return language.Code;
    }
}

LanguageService.cs

public class LanguageService : ILanguageService
{
    IMembership membership;
    ChatContext context;
    ILanguageConverter converter;

    public LanguageService(
            ChatContext context,
            IMembership membership,
            ILanguageConverter converter
        )
    {
        this.membership = membership;
        this.context = context;
        this.converter = converter;
    }

    public virtual ILanguageViewModel GetByNameOrCode(string Text)
    {
        string lowerText = Text.ToLower();
        string lowerSmallCode = "";

        int lowerTextHiphen = lowerText.IndexOf('-');
        if (lowerTextHiphen > 0)
            lowerSmallCode = lowerText.Substring(0, lowerTextHiphen);

        Language item = this.context
                            .Languages
                            .FirstOrDefault(x => x.Code.ToLower() == lowerText
                                                 || x.SmallCode.ToLower() == lowerText
                                                 || x.SmallCode == lowerSmallCode);
        return converter.Convert(item);
    }

    public virtual ILanguageViewModel GetDefault()
    {
        Language item = this.context
                            .Languages
                            .FirstOrDefault(x => x.Default);
        return converter.Convert(item);
    }
}

Это запрос, который дает мне исключения

Language item = this.context
                    .Languages
                    .FirstOrDefault(x => x.Code.ToLower() == lowerText
                                         || x.SmallCode.ToLower() == lowerText
                                         || x.SmallCode == lowerSmallCode);

person Leandro Soares    schedule 15.07.2016    source источник


Ответы (1)


Глобальные фильтры в MVC и веб-API являются синглтонами. Существует только один экземпляр такого фильтра за время существования вашего приложения. Это становится очевидным, если вы посмотрите на следующий код:

filters.Add(container.GetInstance<ICultureInitializerFilter>());

Здесь вы разрешаете фильтр один раз из контейнера и сохраняете его на время существования контейнера.

Однако вы зарегистрировали этот тип как Scoped, используя:

container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();

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

Чтобы ваши фильтры имели зависимости, вы должны либо сделать их скромными объектами, либо обернуть их в скромный объект, который может их вызвать. Например, вы можете создать следующий фильтр действий:

public sealed class GlobalActionFilter<TActionFilter> : IActionFilter 
    where TActionFilter : class, IActionFilter
{
    private readonly Container container;
    public GlobalActionFilter(Container container) { this.container = container; }

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        container.GetInstance<TActionFilter>().OnActionExecuted(filterContext);
    }

    public void OnActionExecuting(ActionExecutingContext filterContext) {
        container.GetInstance<TActionFilter>().OnActionExecuting(filterContext);
    }
}

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

filters.Add(new GlobalActionFilter<ICultureInitializerFilter>(container));
filters.Add(new GlobalActionFilter<ICustomErrorHandlerFilter>(container));
filters.Add(new GlobalActionFilter<IAjaxMessagesFilter>(container));

GlovalActionFilter<T> будет выполнять обратный вызов в контейнер для разрешения предоставленного типа каждый раз, когда он вызывается. Это предотвращает превращение зависимости в пленку, что предотвращает проблемы, которые у вас возникают. .

person Steven    schedule 15.07.2016
comment
Ты жжешь! Хорошо, но использование слова «Одиночка» кажется таким неправильным столько раз, что я его избегаю. Я попробую это и отвечу вам - person Leandro Soares; 15.07.2016
comment
Тем не менее, я пытаюсь использовать код, который вы дали, но я не знаю, как использовать такой предикат, это просто дает мне ошибку компиляции. - person Leandro Soares; 15.07.2016
comment
@LeandroSoares: Это означает, что вы все еще используете C # 5 (бедняжка ;-)). Просто замените =› на { и добавьте закрывающий } к методу. - person Steven; 15.07.2016
comment
@LeandroSoares: для C# 6 вам понадобится Visual Studio 2015. Я обновил свой вопрос. Вероятно, теперь это будет иметь для вас больше смысла. - person Steven; 15.07.2016
comment
хорошо, я очень скоро получу Windows 10 + vs15: D Спасибо за ваш ответ! - person Leandro Soares; 15.07.2016
comment
И на самом деле, мне это больше нравится, чем предикатная версия :p - person Leandro Soares; 15.07.2016
comment
Скажи мне еще одну вещь, я должен зарегистрировать эти фильтры как Singleton? - person Leandro Soares; 15.07.2016
comment
@LeandroSoares попробуйте, запустите приложение и посмотрите, что произойдет. - person Steven; 15.07.2016
comment
Я попытался поместить их как Singleton, а затем получил ошибку, что Singleton может не иметь зависимостей Scope/Transient. Затем я некоторое время гуглил и обнаружил некоторые ответы от вас. Но я действительно не нашел решения, подходящего для этого - person Leandro Soares; 15.07.2016
comment
Simple Injector обнаруживает для вас несоответствия образа жизни, и, если я не ошибаюсь, исключение ссылается на документацию; вы должны прочитать его, чтобы понять, что обнаруживает SI и как решить: simpleinjector.readthedocs.io/ ru/последние/. Важно понимать подводные камни и то, как SI может помочь вам в этом. Исключение говорит вам, что фильтр не должен быть зарегистрирован как синглтон. - person Steven; 15.07.2016
comment
Я вижу, вы правы... Но этот DI + Patterns + Everything дает мне информационный переизбыток, и я не могу запомнить все, что я читал. Для меня это работает лучше, когда я пробую что-то несколько раз, пока не пойму их. Но подсказка тоже работает хорошо :p - person Leandro Soares; 15.07.2016
comment
Я понимаю. Можно многое прожевать. Не торопитесь и читайте, усваивайте и учитесь. - person Steven; 15.07.2016