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.