.netasp.net-coredependency-injectionlocalizationsimple-injector

SharedResource Localization and Simple Injector in asp.net core 2.1


I have an ASP.NET web api in ASP.NET core 2.1 and I have implemented a shared resource as explained here. This works fine.

The line :

services.AddLocalization()

adds localization to the inbuilt IOC container. (I think this is where the magic happens at least)

Now I have added Simple Injector into the mix for my own classes and I have a class which I have registered as Async Scoped which gets injected with the shared resource (through IStringLocalizer). However, IStringLocalizer is scoped as transient and this is not compatible with Async Scoped (as this has a longer scope). I can obviously solve the problem by setting the option "SuppressLifestyleMismatchVerification" to true, but this doesn't smell right. (in this case it probably doesn't matter but by using this option I mask any other problems I may have) Is there anyway of fixing this problem? Can I change the scoping of the shared resource for example?


Solution

  • TLDR; override the default registration as follows:

    services.AddLocalization();
    services.AddSingleton(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
    

    What you are seeing is two universes collide; both DI libraries have their own, incompatible, definition of what it means to be transient:

    It is because of this behavior that the Microsoft.Extensions.DependencyInjection (MS.DI) container allows injecting transients into both singleton and scoped consumers.

    SIDE NOTE: I would even argue that Microsoft named their lifestyle incorrectly, because the actual behavior is to have one instance per consumer's dependency. Microsoft seems to have copied this behavior from Autofac. Autofac, however, does name this same lifestyle InstancePerDependency, which is a much more obvious name, if you ask me.

    Weird thing, though, is that the Microsoft's AddLocalization extension method registers StringLocalizer<T> as transient. This is weird, because, apart from the wrapped IStringLocalizer, StringLocalizer<T> doesn't have any state. And not only that, the IStringLocalizer that it wraps is produced by the injected IStringLocalizerFactory and can be expected to be the same instance (which is enforced by the fact that the ResourceManagerStringLocalizerFactory caches returned instances).

    As stated above, within MS.DI, Transient means “I'll live as long as my consumer does. “ This practically means that a StringLocalizer<T> instance can live as long as a singleton, meaning: for the duration of the entire application.

    From this respect it is actually really weird that the localization team chose StringLocalizer<T> to have a transient lifestyle, even in MS.DI. Transient only means more instances created, and the IStringLocalizerFactory invoked more often than required. I find Singleton a much more obvious lifestyle for that registration.

    I would, therefore, suggest overriding the default registration with a singleton, as it is safe to do so anyway:

    services.AddLocalization();
    services.AddSingleton(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));