How to retrieve current IServiceProvider scope (MS DI) in MassTransit on SendContext filter

Viewed 1543

I'm trying to find information about a way to get DI scope, that were created for message Consumer in SendContext/PublishContext filter.

I was able to find some hints how to get this done for ConsumeContext middleware in MassTransit by using MassTransit.ExtensionsDependencyInjectionIntegration.ScopeProviders.DependencyInjectionConsumerScopeProvider

public static void UseUserServiceMiddleware(this IPipeConfigurator<ConsumeContext> configurator, IServiceProvider serviceProvider)
{
    // create PayLoad to retrieve current scope in Middleware
    var scopeProvider = new DependencyInjectionConsumerScopeProvider(serviceProvider);
    configurator.AddPipeSpecification(new FilterPipeSpecification<ConsumeContext>(new ScopeFilter(scopeProvider)));
    // add specific filter/middleware to set context of IUserService
    configurator.AddPipeSpecification(new UserServiceMiddlewareSpecification<ConsumeContext>());
}

And in IFliter implementation

public class UserServiceMiddlewareFilter<T> :
    IFilter<T>
    where T : class, ConsumeContext
{
    public void Probe(ProbeContext context)
    {
    }

    public async Task Send(T context, IPipe<T> next)
    {
        try
        {
            // current Consumer scope
            if (context.TryGetPayload(out IServiceScope scope))
            {
                if (context.Headers.TryGetHeader("clientIp", out var clientIp) &&
                    context.Headers.TryGetHeader("subId", out var subId))
                {
                    UserIdentityRetrievalSetter(scope, clientIp?.ToString(), subId?.ToString());
                }
            }

            await next.Send(context).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            // propagate the exception up the call stack
            throw;
        }
    }

    private void UserIdentityRetrievalSetter(IServiceScope scope, string clientIp, string subId)
    {
        if (!string.IsNullOrWhiteSpace(clientIp) && !string.IsNullOrWhiteSpace(subId))
        {
            var userIdentityRetrieval =
                scope.ServiceProvider.GetRequiredService<UserIdentityProvider.RequestHandlers.UserIdentityRetrieval>();
            userIdentityRetrieval.SetIpAddress(clientIp);
            userIdentityRetrieval.SetUserSubId(subId);
        }
    }
}

By using context.TryGetPayload(out IServiceScope scope) I'm able to get into scope created for message consumer.

The problem that I'm facing is how to get current DI scope on SendContext/PublishContext before message is sent into RabbitMq, to be able to resolve scoped implementations, that I'm able to set/reconfigure in ConsumeContext middleware. The perfect solution for me is, that sender of message and Consumer are not aware about some part of logic, and there is a need of adding some additional information into messages (I found headers to be perfect for this), that are not part of them.

I was able to write something like this

cfg.ConfigureSend(exec => exec.UseExecute(context =>
                    {
                        if (context.GetType().IsGenericType &&
                            context.GetType().GetGenericTypeDefinition() ==
                            typeof(MassTransit.RabbitMqTransport.Contexts.BasicPublishRabbitMqSendContext<>))
                        {
                            if (context.GetType().GetProperty("Message")?.GetValue(context, null) is
                                Core.MessageBus.Models.IBusDrivenCommunication message)
                            {
                                SendContext sendContext;

                                if (context.TryGetPayload(out ConsumeContext _))
                                {
                                    var userIdentityScoped = provider.GetService<UserIdentityProvider.RequestHandlers.UserIdentityRetrieval>();
                                    var clientIpAddress = userIdentityScoped.GetIpAddress();
                                    var subId = userIdentityScoped.GetUserSubGuidClaim();
                                    if (!context.Headers.TryGetHeader("clientIp", out object cip) &&
                                        !context.Headers.TryGetHeader("subId", out object sid))
                                    {
                                        context.Headers.Set("clientIp", clientIpAddress);
                                        context.Headers.Set("subId", subId);
                                    }
                                }

                                if (context.TryGetPayload(out sendContext))
                                {
                                    var userIdentityScoped = provider.GetService<UserIdentityProvider.RequestHandlers.UserIdentityRetrieval>();
                                    var clientIpAddress = userIdentityScoped.GetIpAddress();
                                    var subId = userIdentityScoped.GetUserSubGuidClaim();
                                    if (!context.Headers.TryGetHeader("clientIp", out object cip) &&
                                        !context.Headers.TryGetHeader("subId", out object sid))
                                    {
                                        context.Headers.Set("clientIp", clientIpAddress);
                                        context.Headers.Set("subId", subId);
                                    }
                                }
                            }
                        }
                    }));

Provider is delivered from IServiceCollectionConfigurator.AddBus, but there is no way to resolve scoped services. I've tried to get Consumer scope

var consumerScope = context.GetPayload<IConsumerScopeContext>();

var consumeContext = context.GetPayload<ConsumeContext>();
var consumerScopeProvider = provider.GetRequiredService<IConsumerScopeProvider>();
using (var scopeMain = consumerScopeProvider.GetScope(consumeContext))
{
    var serviceScope = scopeMain.Context.GetPayload<IServiceScope>();
    var userIdentityScoped =
        serviceScope.ServiceProvider.GetRequiredService<UserIdentityProvider.RequestHandlers.UserIdentityRetrieval>();
}
return;

But it's a blind spot, I've tried to find any hint in SendContext about PayLoad or, something, to be able to get current scope of IServiceProvider.

In the last resort, I can move logic into Command/Event model of message from scoped services and then change ConsumeContext to get this data from it, not from headers, but I prefer to seperate logic from models.

In documentation of MassTrasnit there is Audit functionality http://masstransit-project.com/MassTransit/advanced/audit/, but as far as I understand, Observers are not used to modify data.

0 Answers
Related