无法解析范围服务

14

我在理解代码错误来源方面遇到了问题,我试图通过参加.NET Core微服务课程来解决问题。在运行构建解决方案后,我收到以下信息:

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

但是当我运行它时,我得到了:

/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

当我添加到webHostBuilder .UseDefaultServiceProvider(options => options.ValidateScopes = false)时,我的问题得到了解决。但据我所知,关闭验证并不是一个好主意。另外,将AddScope更改为AddTransient可以解决问题(或者至少运行)。

问题在于我不知道要在哪里查找此错误的源代码。我猜我缺乏对错误的理解,因此如果有人能帮助我或者给出提示,我会非常感激。

这是我的

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

程序.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);
            }
        }
    }

1
在尝试解析服务之前,您需要创建一个范围。首先尝试解析 IServiceScopeFactory,然后调用 CreateScope() 获取非根服务提供程序。您需要将工厂存储在某个地方以便能够处理它。 - Kalten
感谢您的回复。您正确理解了 IServiceScopeFactory :) - Thou
嗨,我遇到了同样的问题。我甚至使用了相同的文件命名约定,我猜我们看了同样的视频教程。 请问你具体添加或删除了什么? 我真的需要理解这个IServiceScopeFactory。 - Ahmed Adewale
1个回答

31
无法从根提供程序解析作用域服务 ICommandHandler<CreateUser>。正如错误所述,您不能从根提供程序创建作用域服务实例。根提供程序是存在于服务范围之外的根服务提供程序。因此,它无法解析应仅在服务范围内使用的服务。如果您想从根提供程序解析作用域服务,例如当您从单例服务中使用它时,您应该首先使用IServiceScopeFactory创建服务范围。
var serviceScopeFactory = _webHost.Services.GetService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
    var handler = (IEventHandler<TEvent>)scope.ServiceProvider.GetService(typeof(IEventHandler<TEvent>))
    // …
}

注意,服务作用域应该是短暂的,并且需要在使用后进行处理以进行清理。


看起来你将作用域服务传递给其他服务以订阅事件。这通常不是一个好主意,因为这意味着(很可能)单例服务将在整个应用程序的生命周期中保留对作用域服务的引用。这通常是一个坏主意(就像我说的,作用域服务只应该存在短时间)。
你应该问自己为什么需要在那里使用作用域服务,是否可以将它们改为单例服务。或者,如果你实际上需要基于实例的订阅机制(而不仅仅是使用工厂模式之类的类型),那么你是否真的需要它。

3
请问您能否将这段话与他的代码联系起来,因为我无法理解它。 - Ahmed Adewale
1
@DamirBeylkhanov 通常情况下是这样的,但在这里我们明确地创建了一个新的服务范围,因此您必须至少使用一次服务定位器才能回到 DI 为您提供依赖项的状态。 - poke
@onefootswill 由于服务范围可以同时存在无限制,根 IServiceProvider 如何能够知道在您的任意单例中使用哪个服务范围(这是按设计独立于范围的)?因此,如果您想要检索当前请求范围的服务,则必须访问实际的 作用域 服务提供程序。您可以通过 HttpContext.RequestServices 精确获取该服务提供程序。 - poke
@onefootswill 如果您的单例服务可以保证作为请求的一部分执行,那么您可以通过 IHttpContextAccessor 访问上下文。否则,在单例服务内假设有一个可用的请求是非常危险的事情。因此,您应该真正地创建自己的范围。 - poke
@poke 我已经到了要用符合我的需求的 DI 容器替换 IServiceProvider 的地步了。你只能在 IServiceProvider 上走得这么远。 - onefootswill
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接