Не удается выполнить разрешение от корневого поставщика, поскольку для этого требуется служба с ограниченной областью действия

У меня есть следующий Program.cs

 public class Program
{
    public async static Task Main(string[] args)
    {

        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            try
            {
                var context = services.GetRequiredService<ProductDbContext>();

                if (context.Database.IsSqlServer())
                {
                    context.Database.Migrate();
                }
                await ProductDbContextSeed.SeedSampleDataAsync(context);
            }
            catch (Exception ex)
            {
                var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();

                logger.LogError(ex, "An error occurred while migrating or seeding the database.");

                throw;
            }
        }

        await host.RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
         .UseServiceProviderFactory(new AutofacServiceProviderFactory())
            .ConfigureWebHostDefaults(webBuilder =>
                webBuilder.UseStartup<Startup>());

}

Добавив следующую строку: (как я уже сделал в приведенном выше коде):

.UseServiceProviderFactory(new AutofacServiceProviderFactory())

Мой код работает так, как я хочу. Однако, если я удалю .UseServiceProviderFactory(new AutofacServiceProviderFactory()) И просто есть:

  public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
                webBuilder.UseStartup<Startup>());

Я получаю следующую ошибку:

    Cannot resolve 'ProductAPI.IntegrationEvents.EventHandling.OrderCreatedIntegrationEventHandler' 
from root provider because it requires scoped service 
'RetailInMotion.Services.Inventory.ProductAPI.Infrastructure.Persistence.ProductDbContext'.

Вот как я настроил ProductDbContext:

    public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
    {
       
            services.AddDbContext<ProductDbContext>(options =>
                options.UseSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    b => b.MigrationsAssembly(typeof(ProductDbContext).Assembly.FullName)));

        services.AddDbContext<EventLogContext>(options =>
        {
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
                                 sqlServerOptionsAction: sqlOptions =>
                                 {
                                     sqlOptions.MigrationsAssembly(typeof(ProductDbContext).Assembly.FullName);
                                     sqlOptions.EnableRetryOnFailure(10, TimeSpan.FromSeconds(30), null);
                                 });
        });

        services.AddAuthentication();

        services.AddAuthorization();

        return services;
    }
}

Я регистрирую свой EventBus следующим образом:

private void RegisterEventBus(IServiceCollection services, IConfiguration configuration)
{
    services.AddSingleton<IEventBus, EventBusRabbitMq>(sp =>
    {
        var subscriptionClientName = configuration["SubscriptionClientName"];
        var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQConnectionManagementService>();
        var logger = sp.GetRequiredService<ILogger<EventBusRabbitMq>>();
        //var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
        var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();

        var retryCount = 5;
        if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
        {
            retryCount = int.Parse(configuration["EventBusRetryCount"]);
        }
        //return new EventBusRabbitMq(eventBusSubcriptionsManager, rabbitMQPersistentConnection, iLifetimeScope, logger, retryCount, subscriptionClientName);
        return new EventBusRabbitMq(eventBusSubcriptionsManager, rabbitMQPersistentConnection, sp, logger, retryCount, subscriptionClientName);
    });

    services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
    services.AddTransient<OrderCreatedIntegrationEventHandler>();
    services.AddTransient<RemoveProductStockIntegrationEventHandler>();
}

Вот мой класс EventBusRabbitMq:

`

public class EventBusRabbitMq : IEventBus, IDisposable
{
    const string BROKER_NAME = "retailInMotion_event_bus";
    const string AUTOFAC_SCOPE_NAME = "retailInMotion_event_bus";

private readonly IEventBusSubscriptionsManager _subsManager;
private readonly IRabbitMQConnectionManagementService _rabbitMQConnectionManagementService;
private readonly IServiceProvider _serviceProvider;
//private readonly ILifetimeScope _autofac;
private readonly ILogger<EventBusRabbitMq> _logger;
private readonly int _retryCount;

private IModel _consumerChannel;
private string _queueName;

public EventBusRabbitMq(
     IEventBusSubscriptionsManager subsManager,
     IRabbitMQConnectionManagementService rabbitMQConnectionManagementService,
     IServiceProvider serviceProvider, 
     //ILifetimeScope autofac,
     ILogger<EventBusRabbitMq> logger, 
     int retryCount = 5, 
     string queueName = null)
{
    _subsManager = subsManager;
    _rabbitMQConnectionManagementService = rabbitMQConnectionManagementService;
    _serviceProvider = serviceProvider;
    //_autofac = autofac;
    _logger = logger;
    _retryCount = retryCount;
    _queueName = queueName;
    _consumerChannel = CreateConsumerChannel();
    _subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
}

private void SubsManager_OnEventRemoved(object? sender, string eventName)
{
    if (!_rabbitMQConnectionManagementService.IsConnected)
    {
        _rabbitMQConnectionManagementService.TryConnect();
    }

    using (var channel = _rabbitMQConnectionManagementService.CreateModel())
    {
        channel.QueueUnbind(queue: _queueName,
            exchange: BROKER_NAME,
            routingKey: eventName);

        if (_subsManager.IsEmpty)
        {
            _queueName = string.Empty;
            _consumerChannel.Close();
        }
    }
}

private IModel? CreateConsumerChannel()
{
    if (!_rabbitMQConnectionManagementService.IsConnected)
    {
        _rabbitMQConnectionManagementService.TryConnect();
    }

    _logger.LogTrace("Creating RabbitMQ consumer channel");

    var channel = _rabbitMQConnectionManagementService.CreateModel();

    channel.ExchangeDeclare(exchange: BROKER_NAME,
                            type: "direct");

    channel.QueueDeclare(queue: _queueName,
                            durable: true,
                            exclusive: false,
                            autoDelete: false,
                            arguments: null);

    channel.CallbackException += (sender, ea) =>
    {
        _logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel");

        _consumerChannel.Dispose();
        _consumerChannel = CreateConsumerChannel();
        StartBasicConsume();
    };

    return channel;
}

public void Publish(IntegrationEvent @event)
{
    if (!_rabbitMQConnectionManagementService.IsConnected)
    {
        _rabbitMQConnectionManagementService.TryConnect();
    }

    var policy = RetryPolicy.Handle<BrokerUnreachableException>()
        .Or<SocketException>()
        .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
        {
            _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message);
        });

    var eventName = @event.GetType().Name;
    //var jsonMessage = JsonConvert.SerializeObject(@event);

    _logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName);

    using (var channel = _rabbitMQConnectionManagementService.CreateModel())
    {
        _logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);

        channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");

        var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), new JsonSerializerOptions
        {
            WriteIndented = true
        });

        //var body = Encoding.UTF8.GetBytes(jsonMessage);

        policy.Execute(() =>
        {
            var properties = channel.CreateBasicProperties();
            properties.DeliveryMode = 2; // persistent

            _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);

            channel.BasicPublish(
                exchange: BROKER_NAME,
                routingKey: eventName,
                mandatory: true,
                basicProperties: properties,
                body: body);
        });
    }
}

//public void Setup()
//{
//    throw new NotImplementedException();
//}

public void Subscribe<T, TH>()
    where T : IntegrationEvent
    where TH : IIntegrationEventHandler<T>
{
    var eventName = _subsManager.GetEventKey<T>();
    DoInternalSubscription(eventName);

    _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).Name);

    _subsManager.AddSubscription<T, TH>();
    StartBasicConsume();
}
private void DoInternalSubscription(string eventName)
{
    var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
    if (!containsKey)
    {
        if (!_rabbitMQConnectionManagementService.IsConnected)
        {
            _rabbitMQConnectionManagementService.TryConnect();
        }

        _consumerChannel.QueueBind(queue: _queueName,
                            exchange: BROKER_NAME,
                            routingKey: eventName);
    }
}
public void Unsubscribe<T, TH>()
    where T : IntegrationEvent
    where TH : IIntegrationEventHandler<T>
{
    var eventName = _subsManager.GetEventKey<T>();

    _logger.LogInformation("Unsubscribing from event {EventName}", eventName);

    _subsManager.RemoveSubscription<T, TH>();
}

private void StartBasicConsume()
{
    _logger.LogTrace("Starting RabbitMQ basic consume");

    if (_consumerChannel != null)
    {
        var consumer = new AsyncEventingBasicConsumer(_consumerChannel);

        consumer.Received += Consumer_Received;

        _consumerChannel.BasicConsume(
            queue: _queueName,
            autoAck: false,
            consumer: consumer);
    }
    else
    {
        _logger.LogError("StartBasicConsume can't call on _consumerChannel == null");
    }
}

private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs)
{
    var eventName = eventArgs.RoutingKey;
    var message = Encoding.UTF8.GetString(eventArgs.Body.Span);

    try
    {
        if (message.ToLowerInvariant().Contains("throw-fake-exception"))
        {
            throw new InvalidOperationException($"Fake exception requested: \"{message}\"");
        }

        await ProcessEvent(eventName, message);
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message);
    }

    // Even on exception we take the message off the queue.
    // in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX). 
    // For more information see: https://www.rabbitmq.com/dlx.html
    _consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false);
}

private async Task ProcessEvent(string eventName, string message)
{
    _logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName);

    if (_subsManager.HasSubscriptionsForEvent(eventName))
    {
        //using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) // can do it this way with autofac or like directly below with built in .net core DI 
        using (var scope = _serviceProvider.CreateScope())
        {
            var subscriptions = _subsManager.GetHandlersForEvent(eventName);
            foreach (var subscription in subscriptions)
            {
                var handler = _serviceProvider.GetRequiredService(subscription.HandlerType);  //CHRIS INVESTIGATE!!
                //var handler = scope.ResolveOptional(subscription.HandlerType);
                if (handler == null) continue;
                var eventType = _subsManager.GetEventTypeByName(eventName);
                //var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
                //dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(message);
                //using dynamic eventData = JsonDocument.Parse(message);

                var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true});   //JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
                var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
                await (Task)concreteType.GetMethod("HandleAsync").Invoke(handler, new object[] { integrationEvent });
            }
        }
    }
    else
    {
        _logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName);
    }
}

public void Dispose()
{
    if (_consumerChannel != null)
    {
        _consumerChannel.Dispose();
    }
    _subsManager.Clear();
}

} `

Вот трассировка стека для ошибки, которую я получаю:

at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope) at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at EventBus.EventBusRabbitMq.<ProcessEvent>d__18.MoveNext() in C:\Users\porterc\Documents\My Version of DDD\RetailInMotion-master\EventBus\EventBusRabbitMq.cs:line 260 at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at EventBus.EventBusRabbitMq.<Consumer_Received>d__17.MoveNext() in C:\Users\porterc\Documents\My Version of DDD\RetailInMotion-master\EventBus\EventBusRabbitMq.cs:line 235

Мой вопрос: почему? Если кто-нибудь может указать мне правильное направление или сказать мне, что на самом деле делает эта строка, я был бы очень признателен.

🤔 А знаете ли вы, что...
C# предоставляет средства для сериализации и десериализации данных.


3
440
2

Ответы:

Попробуйте это, я думаю, это может быть связано с порядком, в котором он построен.

Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder => {
                webBuilder.UseStartup<Startup>();
            })
            .UseServiceProviderFactory(new AutofacServiceProviderFactory());

Решено

Как говорится в сообщении об исключении, ваш ProductAPI.IntegrationEvents.EventHandling.OrderCreatedIntegrationEventHandler не разрешен в области действия или не зарегистрирован в качестве службы с областью действия.

Какой бы диспетчер событий вы ни использовали, он должен начинать область до разрешения обработчика событий и обработки событий. И OrderCreatedIntegrationEventHandler также должен быть зарегистрирован как служба.

Я предполагаю, что Autofac не выполняет никаких проверок несоответствия времени жизни при запуске, и поэтому он не выдает ошибку при подключении Autofac. Это не значит, что это действительно работает, просто вы скрываете потенциальную ошибку. Это может произойти как исключение при первой обработке события, или может быть еще более серьезным, поскольку он просто использует один и тот же ProductDbContext в течение всего времени существования обработчика события, который может быть одноэлементным.

Но обратите внимание, что я не использовал Autofac, только другие контейнеры, поэтому я не знаю, как именно эта библиотека справляется с такими проблемами.

РЕДАКТИРОВАТЬ 1

Эта строка (260): var handler = _serviceProvider.GetRequiredService(subscription.HandlerType); в предоставленном источнике для EventBusRabbitMq должна быть изменена на: var handler = scope.ServiceProvicer.GetRequiredService(subscription.HandlerType);.

Внутри области вы должны разрешать свои службы с использованием области (в отличие от корневого поставщика услуг _serviceProvider, из которого вы создаете свою область).