Search code examples
c#asp.net-core.net-coreinversion-of-controlservice-locator

Scoping not respected with service locator in .NET Core


Parts of my code need to use a ServiceLocator because constructor injection isn't supported.

My startup class configures the services. I have some that are transient, others which are singleton and others scoped.

For example:

services.AddScoped<IAppSession, AppSession>();
services.AddScoped<IAuthentication, Authentication>();
services.AddScoped<NotificationActionFilter>();

At the end of my service definitions, I have the following block of code, which sets up the service locator.

var serviceProvider = services.BuildServiceProvider();
DependencyResolver.Current = new DependencyResolver();
DependencyResolver.Current.ResolverFunc = (type) =>
{
    return serviceProvider.GetService(type);
};

I noticed that in a given request, I am not receiving the same instance from the service locator that I am from the constructor injection. Instances returned from the service locator appear to be singletons and do not respect the scoping.

The code for DependencyResolver is as follows:

public class DependencyResolver
{
    public static DependencyResolver Current { get; set; }

    public Func<Type, object> ResolverFunc { get; set; }

    public T GetService<T>()
    {
        return (T)ResolverFunc(typeof(T));
    }
}

How can I fix this?


Solution

  • I would suggest creating a middleware which will set ServiceProvider to the one which is used in other places:

    public class DependencyResolverMiddleware
    {
        private readonly RequestDelegate _next;
    
        public DependencyResolverMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext httpContext)
        {
            DependencyResolver.Current.ResolverFunc = (type) =>
            {
                return httpContext.RequestServices.GetService(type);
            };
    
            await _next(httpContext);
        }
    }
    

    Also, DependencyResolver should be updated to support such behavior:

    public class DependencyResolver
    {
        private static readonly AsyncLocal<Func<Type, object>> _resolverFunc = new AsyncLocal<Func<Type, object>>();
    
        public static DependencyResolver Current { get; set; }
    
        public Func<Type, object> ResolverFunc
        {
            get => _resolverFunc.Value;
            set => _resolverFunc.Value = value;
        }
    
        public T GetService<T>()
        {
            return (T)ResolverFunc(typeof(T));
        }
    }
    

    Don't forget to register it in Configure method in Startup.cs:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
        app.UseMiddleware<DependencyResolverMiddleware>();
    }