Pipeline when IMessageSession is created in a Scope

@Stig_Christensen

I tried this and that worked:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NServiceBus.Pipeline;

public sealed class InjectTenantBehavior(ILogger<InjectTenantBehavior> logger) :
    Behavior<IOutgoingLogicalMessageContext>
{
    public override async Task Invoke(IOutgoingLogicalMessageContext context, Func<Task> next)
    {

        var tenantProvider = context.Builder.GetRequiredService<TenantProvider>();
        logger.LogInformation("Injected scoped dependency: {TenantId}",  tenantProvider.TenantId);
        context.Headers["TenantId"] = tenantProvider.TenantId.ToString();
        await next();
    }
}

class TenantProvider
{
    static long counter;
    public long TenantId { get; } = Interlocked.Increment(ref counter);
}

Registrations:

builder.Services.AddScoped<TenantProvider>();
builder.Services.AddSingleton<InjectTenantBehavior>();

// ...

endpointConfiguration.Pipeline.Register<InjectTenantBehavior>(b=> b.GetRequiredService<InjectTanentBehavior>(), nameof(InjectTenantBehavior));

That works.. but I think your problem is related by using IMessageSession.

IMessageSession is a singleton and it isn’t context aware. THere is no way to pass the current scope to any method of that interface.

In that case you likely want to use Transactional Session:

As an alternative workaround you could use something like:

public sealed class TenantContext
{
    static readonly AsyncLocal<string?> _tenantId = new();
    
    public string TenantId
    {
        get => _tenantId.Value ?? throw new InvalidOperationException(
            "Tenant context not set. Did you forget to call SetTenantId?");
        set => _tenantId.Value = value;
    }
    
    public bool IsSet => _tenantId.Value is not null;
}

– Ramon