0

I have two services communicating through RabbitMQ, I want to apply a scoped lifetime per received message.

My consumer:

 public class Consumer
 {
    private IModel channel;
    public Consumer(IModel channel)
    {
        this.channel = channel;
    }
    public void Consume()
    {
        var queue = channel.QueueDeclare(queue: MessagingConfigs.QueueName,
            durable: false,
            exclusive: false,
            autoDelete: false,
            arguments: null);
        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, args) => HandleMessageReceived(model, args);
        channel.BasicConsume(queue: MessagingConfigs.QueueName, autoAck: false, consumer: consumer);
    }

    private async Task HandleMessageReceived(object model, BasicDeliverEventArgs args)
    {
        //do logic
    }
}

HandleMessageReceived will be called each time an event Received is fired, how can I open a new scope each time HandleMessageReceived is called?

Found a solution online:

public void ConfigureServices(IServiceCollection services)

{
services.AddScoped<IMyScopedService, MyScopedService>();

var serviceProvider = services.BuildServiceProvider();

var serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();

IMyScopedService scopedOne;

using (var scope = serviceScopeFactory.CreateScope())
{
    scopedOne = scope.ServiceProvider.GetService<Consumer>();
    Consumer.Consume();
}
}

I am thinking that if I use the above in startup.cs Consumer will be called once and scope will be opened once in application lifetime, the following might work:

private async Task HandleMessageReceived(object model, BasicDeliverEventArgs args)
    { using (var scope = serviceScopeFactory.CreateScope())
     {
        //do logic
     }
    }

but I don't want to include lifetime registration in my consumer code, is there a way in .Net core to open a scope automatically each time a method is called, something that I can use in startup.cs or program.cs?

My application is .net core console application not ASP.NET

Yahya Hussein
  • 8,767
  • 15
  • 58
  • 114
  • 1
    I don't think there is such thing. You have to open scope yourself when method starts, like in your last example. You can extract processing logic to separate class, create new scope in HandleMessageReceived, and resolve that class with logic. That way at least class with logic is not bloated with scoping stuff, and also doesn't need to manually resolve dependencies from container. – Evk May 10 '18 at 08:25
  • 1
    ASP.NET Controllers get scopes because a handler in the ASP.NET pipeline creates and uses it for each request. In other cases you'll have to create the scope explicitly. This is shown in the [ASP.NET documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#resolve-a-scoped-service-within-the-application-scope) sample that creates a context in the Main method itself – Panagiotis Kanavos May 10 '18 at 09:33
  • You can simplify your code if you *don't* put any logic in the event handler itself, just the code that sets everything up and passess the message to the actual processing methods. After all, you may decide that async methods are better than events after a while, or you may want to start using the new [System.Threading.Channels](https://github.com/dotnet/corefx/tree/master/src/System.Threading.Channels) classes to push new messages to the processing method. This will be a *lot* easier if you decouple event handling from the actual logic – Panagiotis Kanavos May 10 '18 at 09:35

1 Answers1

1

Why not wrap the method during attachment?

First, you create wrapper for a method :

public static class ScopeFactoryExtension
{
   public static EventHandler<BasicDeliverEventArgs> WrapInScope(this IServiceScopeFactory scopeFactory, Func<IMyScopedService, object, BasicDeliverEventArgs, Task> func)
   {
      async void InScope(object arg1, BasicDeliverEventArgs arg2)
      {
         using (var scope = scopeFactory.CreateScope())
         {
            await func(scope, arg1, arg2);
         }
      }

      return InScope;
   }
}

Then wrap whole method :

consumer.Received += scopeFactory.WrapInScope(HandleMessageReceived);

Also need to extend the method :

private async Task HandleMessageReceived(IMyScopedService service, object model, BasicDeliverEventArgs args)
{
   //do logic with service
}
Euphoric
  • 12,645
  • 1
  • 30
  • 44