rebus-org / Rebus.ServiceProvider

:bus: Microsoft Extensions Dependency Injection container adapter for Rebus
https://mookid.dk/category/rebus
Other
67 stars 34 forks source link

OutgoingStepContext can't resolve a SCOPED instance #77

Closed danilobreda closed 1 year ago

danilobreda commented 1 year ago

The project is a API AspNetCore running on .net 7 (with latest nugets) using Microsoft DI. I have a scoped interface name ISessionData that have the informations of the http request received by a asp net core api controller. When i receive the HTTP request, i set the informations to the ISessionData (scoped) by a http middleware/filter

[HttpGet("rebus")]
public IActionResult SendMessage(long id)
{
    var sessionData = _serviceProvider.GetRequiredService<ISessionData>();// <-- data is ok here

    _messenger.Publish(new ReservarEstoque()
    {
        IdPedido = id
    });
    return GetResponse();
}
var sess = context.HttpContext.RequestServices.GetRequiredService<ISessionData>();
sess.SetSessionInfo(sessionInfo);

If this controller call a publish for example, the CustomFlowOutgoingStep receive ISessionData with different info (like a new one was created). The ideia is set informations of the sessiondata on the Event (headers)

public static void ConfigureBus(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
    services.AddRebus((configurer, provider) =>
    {
         ...
        var config = configurer.Transport(...)
            .Options(o => o.Flow(provider))
         ...
        return config;
    });
}
public static void Flow(this OptionsConfigurer configurer, IServiceProvider provider)
{
    configurer.Decorate<IPipeline>(c =>
    {
        var outgoingStep = new CustomFlowOutgoingStep(provider);
        var incomingStep = new CustomFlowIncomingStep(provider);

        var pipeline = c.Get<IPipeline>();

        return new PipelineStepInjector(pipeline)
        .OnReceive(incomingStep, PipelineRelativePosition.After, typeof(DeserializeIncomingMessageStep))
        .OnSend(outgoingStep, PipelineRelativePosition.Before, typeof(SerializeOutgoingMessageStep));
    });
}

var sessionData null above...

public class CustomFlowOutgoingStep : IOutgoingStep
{
  private IServiceProvider _serviceProvider;

  public CustomFlowOutgoingStep(IServiceProvider provider)
  {
      _serviceProvider = provider;
  }

  public async Task Process(OutgoingStepContext context, Func<Task> next)
  {
        var contextMessage = context.Load<Message>();
        var sessionData = _serviceProvider.GetService<ISessionData>(); //<-- wrong info like a new scoped was created  ;(
        contextMessage.AddSessionHeaders(sessionData);
        await next();
  }
}

what im doing wrong? :(

danilobreda commented 1 year ago

What im doing...

@mookid8000 is that possible?

danilobreda commented 1 year ago

I created a example on a fork https://github.com/danilobreda/Rebus.ServiceProvider Project Sample.WebAppPipes

Workflow of the example: Api Call (Controller) -> Publish MessageA -> Handler of MessageA -> Publish MessageB -> Handler of Message B SessionData GUID originated from Controller should be the same to the Handler of Message B... that not occurs

danilobreda commented 1 year ago

I created a temporary solution with a custom IMessenger(Injecting IBus) that publish/send/sendlocal with headers from my scoped ISessionData and this incomingstep

public async Task Process(IncomingStepContext context, Func<Task> next)
{
    var message = context.Load<Message>();

    var serviceProvider = context.Load<IServiceProvider>();
    using (var scope = serviceProvider.CreateScope())
    {
        //setting sessiondata into IsessionData
        var sess = scope.ServiceProvider.GetRequiredService<ISessionData>();
        sess.SetSessionGuid(message.GetSessionGuid());
        sess.SetSessionInfo(message.GetSessionInfo());
        sess.SetApiInfo(message.GetApiInfo());

        context.Save(scope);
        await next();
    }
}
mookid8000 commented 1 year ago

Your issue is quite natural, because it's the root service provider that gets injected here:

public class CustomFlowOutgoingStep : IOutgoingStep
{
  private IServiceProvider _serviceProvider;

  //                                         👇 root
  public CustomFlowOutgoingStep(IServiceProvider provider)
  {
      _serviceProvider = provider;
  }
(...)

The newest Rebus.ServiceProvider (9.0.0-b02) has had some work done to make it easier to get access the current IServiceScope (or AsyncServiceScope if you're on .NET 6 or later) – check out an example here: https://github.com/rebus-org/Rebus.ServiceProvider/blob/master/Rebus.ServiceProvider.Tests/Examples/DigOutServiceProviderScopeInOutgoingStep.cs

I'm taking the liberty of closing this issue for now. Let me know if you still have questions about this 🙂