14

I have problem with understanding source of errors in my code. I try to get throw course about microservices in .net core. After running build solution I get:

------- Project finished: CrossX.Services.Identity. Succeeded: True. Errors: 0. Warnings: 0

But when I run it I get:

/opt/dotnet/dotnet /RiderProjects/crossx/src/CrossX.Services.Identity/bin/Debug/netcoreapp2.2/CrossX.Services.Identity.dll

Unhandled Exception: System.InvalidOperationException: Cannot resolve scoped service 'CrossX.NETCore.Commands.ICommandHandler`1[CrossX.NETCore.Commands.CreateUser]' from root provider.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at CrossX.NETCore.Services.ServiceHost.BusBuilder.SubscribeToCommand[TCommand]() in /RiderProjects/crossx/src/CrossX.NETCore/Services/ServiceHost.cs:line 78
   at CrossX.Services.Identity.Program.Main(String[] args) in /RiderProjects/crossx/src/CrossX.Services.Identity/Program.cs:line 11

When I added to webHostBuilder .UseDefaultServiceProvider(options => options.ValidateScopes = false) my problem was solved. But turning off validations isn't good idea from what I know. Also When I changed AddScope to AddTransient problem was solved (or at least it run).

Problem is that I have no idea where to look for source of this error. I guess I lack of understanding what is wrong, so I would appreciate if someone would help me, or at least give a hint.

Here is my

Startup.cs:

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddRabbitMq(Configuration);
            services.AddScoped<ICommandHandler<CreateUser>, CreateUserHandler>();       
            services.AddScoped<IEncrypter, Encrypter>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }

Program.cs

public class Program
    {
        public static void Main(string[] args)
        {
            ServiceHost.Create<Startup>(args)
                .UseRabbitMq()
                .SubscribeToCommand<CreateUser>()
                .Build()
                .Run();
        }
    }

ServiceHost.cs

public class ServiceHost : IServiceHost
    {
        private readonly IWebHost _webHost;

        public ServiceHost(IWebHost webHost)
        {
            _webHost = webHost;
        }

        public void Run() => _webHost.Run();

        public static HostBuilder Create<TStartup>(string[] args) where TStartup : class
        {
            Console.Title = typeof(TStartup).Namespace;
            var config = new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddCommandLine(args)
                .Build();
            var webHostBuilder = WebHost.CreateDefaultBuilder(args)
                .UseConfiguration(config)
//                .UseDefaultServiceProvider(options => options.ValidateScopes = false)
                .UseStartup<TStartup>();

            return new HostBuilder(webHostBuilder.Build());
        }

        public abstract class BuilderBase
        {
            public abstract ServiceHost Build();
        }

        public class HostBuilder : BuilderBase
        {
            private readonly IWebHost _webHost;
            private IBusClient _bus;

            public HostBuilder(IWebHost webHost)
            {
                _webHost = webHost;
            }

            public BusBuilder UseRabbitMq()
            {
                _bus = (IBusClient) _webHost.Services.GetService(typeof(IBusClient));
                return new BusBuilder(_webHost, _bus);
            }

            public override ServiceHost Build()
            {
                return new ServiceHost(_webHost);
            }
        }

        public class BusBuilder : BuilderBase
        {
            private readonly IWebHost _webHost;
            private IBusClient _bus;

            public BusBuilder(IWebHost webHost, IBusClient bus)
            {
                _webHost = webHost;
                _bus = bus;
            }

            public BusBuilder SubscribeToCommand<TCommand>() where TCommand : ICommand
            {
                var handler = (ICommandHandler<TCommand>) _webHost.Services.GetService(typeof(ICommandHandler<TCommand>));
                _bus.WithCommandHandlerAsync(handler);

                return this;
            }

            public BusBuilder SubscribeToEvent<TEvent>() where TEvent : IEvent
            {
                var handler = (IEventHandler<TEvent>) _webHost.Services.GetService(typeof(IEventHandler<TEvent>));
                _bus.WithEventHandlerAsync(handler);

                return this;
            }

            public override ServiceHost Build()
            {
                return new ServiceHost(_webHost);
            }
        }
    }
Thou
  • 1,055
  • 3
  • 11
  • 16
  • 1
    You need to create a scope before trying to resolve the service. Try resolve `IServiceScopeFactory` first and then call `CreateScope()` to get the non root service provider. You will need to store the factory somewhere to be able to dispose it. – Kalten Mar 10 '19 at 16:41
  • Thank you for response. You got the point with IServiceScopeFactory :) – Thou Mar 10 '19 at 20:47
  • Hi I'm having the same issue. I even have the same file naming convention, I guess we watched the same video tutorial Please what and what exactly did you had d or remove. I really need to understand this IServiceScopeFactory – Ahmed Adewale May 06 '20 at 20:51

1 Answers1

31

Cannot resolve scoped service ICommandHandler<CreateUser> from root provider

As the error says, you cannot create a scoped service instance from the root provider. The root provider is the root service provider that exists outside of service scopes. As such, it cannot resolve services that should only be consumed within service scopes.

If you want to resolve a scoped service from the root provider, for example when you are consuming it from a singleton service, you should create a service scope first using the IServiceScopeFactory:

var serviceScopeFactory = _webHost.Services.GetService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
    var handler = (IEventHandler<TEvent>)scope.ServiceProvider.GetService(typeof(IEventHandler<TEvent>))
    // …
}

Note that service scopes are supposed to be short lived, and that you need to dispose them afterwards to clean up.


Looking at your implementation, it seems as if you pass your scoped services to some other service in order to subscribe to events. This generally seems like a bad idea since that means that a reference to a scoped service will be kept by a (likely) singleton service for the whole lifetime of the application. This is generally a bad idea (as I said, scoped services are supposed to live only a short time).

You should ask yourself why you need the services to be scoped there, and whether you cannot make them singleton instead. Or if you actually need the subscribe mechanism to be based on the instance (instead of for example just the type, using a factory pattern or something).

poke
  • 369,085
  • 72
  • 557
  • 602
  • 3
    Please can you relate this to his code i can't get it like this – Ahmed Adewale May 06 '20 at 23:28
  • it's bad practice to use the service locator pattern, I mean about in particular `GetService()` – Damir Beylkhanov Jun 09 '21 at 19:34
  • 1
    @DamirBeylkhanov That is usually true but here we are explicitly creating a new service scope, so you _have_ to use the service locator at least once to get back into a state where DI will provide the dependencies for you. – poke Jun 10 '21 at 16:07
  • Service Locator pattern can be fine. Example: you want to scope a DbContect to an HTTP request. However, you want your application services to be singletons. Do THAT without using the Service Locator pattern. I dare you. The way forward is to inject a `locator` which is also a singleton and that resolves the DbContext (which is scope to the request). This approach has never failed me, except with `IServiceProvider`, which basically does not let you do this. So, you have to make everything singletons, or everything scoped. Really dumb design. – onefootswill Sep 07 '21 at 22:14
  • @onefootswill Since there is no limit to how many services scopes can exist at the same time, how would the root `IServiceProvider` be able to tell which service scope to use in your arbitrary singleton (which is scope-independent by design)? So instead, you will have to get access to the actual *scoped* service provider if you want to retrieve services for the current request scope. You can get exactly that service provider via `HttpContext.RequestServices`. – poke Sep 08 '21 at 09:15
  • @onefootswill If it is guaranteed that your singleton service executes *as part of a request*, then you can access the context via `IHttpContextAccessor`. Otherwise, assuming that there is a request available from within a singleton service is a very dangerous thing to do. So you should really be creating your own scope instead. – poke Sep 08 '21 at 09:16
  • @poke That may be the case for an ASP.NET app, but not for a Service, as `IHttpContextAccessor` does not exist. I stand by my criticism. – onefootswill Sep 08 '21 at 22:14
  • @poke I'm actually reaching the point of swapping out `IServiceProvider` for a DI container which meets my needs. You can only go so far with `IServiceProvider`. – onefootswill Sep 08 '21 at 22:15
  • @onefootswill If you are not in an ASP.NET Core app, there is no request scope. So I’m not sure what you are actually arguing about. – Feel free to choose a different DI container. Nobody is forcing you to stick to M.E.DI. – poke Sep 09 '21 at 15:48
  • @poke You can create scopes using `IServiceProvider`. Request scope is not the only scope in town. I realize I am not being compelled to use `IServiceProvider`. But I also have the freedom to denounce it as "limited". And it is. Very. Limited. – onefootswill Sep 10 '21 at 02:07