Search code examples
c#.netasp.net-core.net-coresimple-injector

ASP.NET Core 2.1 Service Locator with Simple Injector returning null


I have an .NET MVC 5 .NET Framework Application which I am converting to .NET Core 2.1

I have a custom action filter which in .NET Framework version was registered as a Global Filter in a Filterconfig class like below:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new MyCustomActionFilter());
    }
}

Within the custom action filter in the .NET version I was using Service Locator pattern (I know it can be considered an anti pattern) as below:

var myService = DependencyResolver.Current.GetService<IMyService>();

I am using Simple Injector for DI and everything works fine in the .NET Version. With the .NET Core version I am trying to get the same functionality working but myService is always null

I am still using Simple Injector (as all the other projects in the solution use it and they are not getting move to .NET Core projects (only the web one is).

My Startup.cs class has this code:

services.Configure<MvcOptions>(options =>
{
    options.Filters.Add(new MyCustomActionFilter());
});

SimpleInjectorConfig.IntegrateSimpleInjector(services, container);

At my service layer I have a SimpleInjector Registartion class that gets called from Web Layer - it then calls down to DAL Layer to do Registration

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container)
    {
        container.Register<IMyService, MyService>();
        //further code removed for brevity

When I run the application with a breakpoint in the Custom Filter and a breakpoint in this RegisterServices method I can see the breakpoint in the RegisterServices method gets hit first and then the breakpoint in the Custom Filter - this made me think everything was wired up in the container correctly.

However I am trying to do the below again in the custom filter with .NET Core Service Locator pattern

var myService = filterContext.HttpContext.RequestServices.GetService<IMyService>();

but the result is always null?

Is there something I have missed in this setup?

------------ UPDATE -------------------

Based on Stevens comment I added a constructor to my action filter and passed in the Simple Injector container.

So My Startup class now is:

public class Startup
{
    //Simple Injector container
    private Container container = new Container(); 

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();


        services.Configure<MvcOptions>(options =>
        {
           options.Filters.Add(new MyCustomActionFilter(container)); 

My Custom filter now is like below with constructor added:

public class MyCustomActionFilter : ActionFilterAttribute
{
    private readonly IMyService _myService;

    public MyCustomActionFilter(Container container)
    {
        _myService = container.GetService<IMyService>();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
       //actual code of custom filter removed - use of MyService 

I set a breakpoint on the Constructor of MyCustomActionFilter and I can see it getting hit but I get an Error thrown:

SimpleInjector.ActivationException: 'The IDbContext is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.'

MyService has a Dependency on the DbContext which is injected into it (it is doing work saving and retrieving data from DB.

For the DB Context I registered it as below:

public class SimpleInjectorRegistration
{
    public static void RegisterServices(Container container, string connectionString)
    {
        container.Register<IDbContext>(() => new MyDbContext(connectionString),
            Lifestyle.Scoped);
    }

}

Solution

  • There are some significant changes between how to integrate Simple Injector in the old ASP.NET MVC and the new ASP.NET Core. In the old system, you would be able to replace the IDependencyResolver. ASP.NET Core, however, contains a completely different model, with its own internal DI Container. As it is impossible to replace that built-in container with Simple Injector, you will have the two containers run side-by-side. In that case the built-in container will resolve framework and third-party components, where Simple Injector will compose application components for you.

    When you call HttpContext.RequestServices.GetService, you will be requesting the built-in container for a service, not Simple Injector. Adding the IMyService registration to the built-in container, as TanvirArjel's answer suggests, might seem to work at first, but that completely skips Simple Injector from the equation, which is obviously not an option, as you wish to use Simple Injector as your application container.

    To mimic the Service Locator-like behavior you had before, you will have to inject the SimpleInjector.Container into your filter, as follows:

    options.Filters.Add(new MyCustomActionFilter(container));
    

    It would be an error, however, to call the container from within the constructor, as you are showing in your question:

    public class MyCustomActionFilter : ActionFilterAttribute
    {
        private readonly IMyService _myService;
    
        public MyCustomActionFilter(Container container)
        {
            _myService = container.GetService<IMyService>(); // NEVER DO THIS!!!
        }
    
        ...
    }
    

    WARNING: You should never resolve from the container from the constructor. Or in more general: you should never use any injected dependency from inside the constructor. The constructor should only store the dependency.

    As Mark Seemann explained, injection constructors should be simple. In this case, it even gets worse because:

    • During the time that the constructor of MyCustomActionFilter is invoked, there is no active scope, and IMyService can't be resolved
    • Even if IMyService could be resolved, MyCustomActionFilter is a Singleton and storing IMyService in a private field will cause a hidden Captive Dependency. This could lead to all sorts of trouble.

    Instead of storing the resolved, IMyService dependency, you should store the Container dependency:

    public class MyCustomActionFilter : ActionFilterAttribute
    {
        private readonly Container _container;
    
        public MyCustomActionFilter(Container container)
        {
            _container = container;
        }
    
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            myService = container.GetService<IMyService>();
            //actual code of custom filter removed - use of MyService 
        }
    }
    

    During the time that OnActionExecuting is called, there will be an active Simple Injector Scope, which will allows IMyService to be resolved. On top of that, as IMyService is not stored in a private field, it will not be cached and will not cause a Captive Dependency.

    In your question you referred to the Service Locator anti-pattern. Whether or not the injection of the Container into your filter is in fact an implementation of the Service Locator anti-pattern depends on where the filter is located. As Mark Seemann puts it:

    A DI container encapsulated in a Composition Root is not a Service Locator - it's an infrastructure component.

    In other words, as long as the filter class is located inside your Composition Root, you are not applying the Service Locator anti-pattern. This does mean, however, that you must make sure that the filter itself contains as little interesting behavior as possible. That behavior should all be moved to the service that the filter resolves.