【问题标题】:Cannot resolve scoped service无法解析范围服务
【发布时间】:2019-03-10 16:18:08
【问题描述】:

我无法理解代码中的错误来源。我尝试在 .net 核心中学习有关微服务的课程。运行构建解决方案后,我得到:

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

【问题讨论】:

  • 您需要在尝试解析服务之前创建一个范围。先尝试解析IServiceScopeFactory,然后调用CreateScope() 获取非root 服务提供者。您需要将工厂存储在某个地方以便能够处理它。
  • 感谢您的回复。你明白了 IServiceScopeFactory 的意义:)
  • 您好,我遇到了同样的问题。我什至有相同的文件命名约定,我想我们观看了相同的视频教程,请问您删除或删除了什么以及究竟是什么。我真的需要了解这个 IServiceScopeFactory

标签: c# asp.net asp.net-core .net-core


【解决方案1】:

无法从根提供商解析范围服务ICommandHandler&lt;CreateUser&gt;

正如错误所说,您无法从根提供程序创建范围服务实例。根提供者是存在于服务范围之外的根服务提供者。因此,它无法解析只应在服务范围内使用的服务。

如果你想从根提供者解析一个作用域服务,例如当你从一个单例服务中使用它时,你应该首先使用IServiceScopeFactory创建一个服务作用域:

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

请注意,服务范围应该是短暂的,您需要在事后处理它们以进行清理。


查看您的实现,似乎您将范围内的服务传递给其他服务以订阅事件。这通常看起来是个坏主意,因为这意味着对范围服务的引用将由(可能的)单例服务在应用程序的整个生命周期内保留。这通常是一个坏主意(正如我所说,范围服务应该只存在很短的时间)。

您应该问自己为什么需要将服务限定在此处,以及是否不能将它们设为单例。或者,如果您确实需要基于实例的订阅机制(而不是例如类型,使用工厂模式或其他东西)。

【讨论】:

  • 请你把这个和他的代码联系起来我不能这样理解
  • 使用服务定位器模式是不好的做法,我的意思是特别是GetService&lt;IServiceScopeFactory&gt;()
  • @DamirBeylkhanov 这通常是正确的,但在这里我们明确地创建了一个新的服务范围,因此您必须至少使用一次服务定位器才能返回到DI 会为你提供依赖。
  • 服务定位器模式可以。示例:您希望将 DbContect 范围限定为 HTTP 请求。但是,您希望您的应用程序服务是单例的。在不使用服务定位器模式的情况下执行此操作。我赌你。前进的方法是注入一个locator,它也是一个单例并且解析 DbContext(它是请求的范围)。这种方法从来没有让我失望过,除了IServiceProvider,它基本上不允许你这样做。因此,您必须将所有内容都设为单例,或将所有内容设为范围。真是愚蠢的设计。
  • @onefootswill 由于可以同时存在多少个服务范围没有限制,根IServiceProvider 将如何判断在您的任意单例中使用哪个服务范围(即设计上与范围无关)?因此,如果您想检索当前请求范围的服务,则必须访问实际的 scoped 服务提供者。您可以通过HttpContext.RequestServices 获得该服务提供商。
猜你喜欢
  • 2019-06-04
  • 2018-09-27
  • 2019-07-02
  • 2018-10-09
  • 2020-08-05
  • 2018-04-04
  • 1970-01-01
  • 2020-09-06
  • 2020-08-01
相关资源
最近更新 更多