1

If I start my application and let it settle, it works great.

However, when I debug my application and if I close the browser tab before it initializes anything and then call another like localhost:81/Home/Test, it throws an exception on retrieving data from DB (EF).

This exception occurs during a call to a Filter CultureResolver which then calls LanguageService. Inside LanguageService there is a call to the DB to retrieve all the available languages.

I got many different exceptions, like:

  • The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.
  • Object reference not set to an instance of an object.
  • The underlying provider failed on Open.

Those exceptions occur all in the same query, it depends on how much time I left the first tab running.

So it seems it's something like Thread-Unsafe code or this query trying to get items before the Context is initialized.

I've the following:

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);
    }
}

This is the query that is giving me the exceptions

Language item = this.context
                    .Languages
                    .FirstOrDefault(x => x.Code.ToLower() == lowerText
                                         || x.SmallCode.ToLower() == lowerText
                                         || x.SmallCode == lowerSmallCode);
Leandro Soares
  • 2,902
  • 2
  • 27
  • 39

1 Answers1

1

Global filters in MVC and Web API are singletons. There is only one instance of such filter during the lifetime of your application. This becomes obvious when you look at the following code:

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

Here you resolve the filter once from the container and store it for the lifetime of the container.

You however, have registered this type as Scoped using:

container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();

You are effectively saying that there should be one instance per web request, most likely because that class depends on a DbContext, which isn't thread-safe.

To allow your filters to have dependencies, you should either make them humble objects, or wrap them in a humble object that can call them. For instance, you can create the following action filter:

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);
    }
}

This class allows you to add your global filters as follows:

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

The GlovalActionFilter<T> will callback into the container to resolve the supplied type every time it is called. This prevents the dependency from becoming captive which prevents the problems you are having.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • You rock! Ok, but it seems so wrong using the word "Singleton " so many times that I avoid it. I'll try that one and feedback you – Leandro Soares Jul 15 '16 at 14:12
  • However, i'm trying to use the code you gave but I don't know how to use a predicate like that, it just gives me a compiling error – Leandro Soares Jul 15 '16 at 14:14
  • @LeandroSoares: That means that you are still using C# 5 (you poor soul ;-)). Just replace the "=>" with "{" and add a closing "}" to the method. – Steven Jul 15 '16 at 14:16
  • @LeandroSoares: For C# 6 you'll need Visual Studio 2015. I updated my question. It will probably now make more sense to you. – Steven Jul 15 '16 at 14:17
  • ok i'm getting windows 10 + vs15 very soon :D Thank you for your answer! – Leandro Soares Jul 15 '16 at 14:20
  • And actually, i like it more like this instead of the predicate version :p – Leandro Soares Jul 15 '16 at 14:21
  • Tell me one more thing, should i Register those filters as Singleton? – Leandro Soares Jul 15 '16 at 15:05
  • @LeandroSoares try it, run your application and see what happens. – Steven Jul 15 '16 at 15:08
  • I've tried to put them as Singleton and then I got the error that Singleton may not have Scope/Transient dependencies. Then i googled some time and discovered some answers by you. But i didn't really found a solution suitable for this – Leandro Soares Jul 15 '16 at 15:12
  • 1
    Simple Injector detects lifestyle mismatches for you and if I'm not mistsken, the exception references the documentation; you should read it to understand what SI detects and how to resolve: https://simpleinjector.readthedocs.io/en/latest/LifestyleMismatches.html. Important is to understand the pitfalls and how SI can help you here. The exception is telling you that the filter should not be registered as singleton. – Steven Jul 15 '16 at 15:18
  • I see, you are right... But this DI + Patterns + Everything gives me an information overflow and I can't memorize everything I read. For me it works better when i try things multiple times until i understand them. But a clue works well too :p – Leandro Soares Jul 15 '16 at 15:24
  • 1
    I understand. It can be a lot to chew off. Do take your time and read, digest and learn. – Steven Jul 15 '16 at 15:26