'Register dependent services on every request

I am working in Multi-tenant solution primarily there are 2 type of applications

  1. WebAPI
  2. Console app to process message from queue

I have implemented dependency injection to inject all services. I have crated TenantContext class where I am resolving tenant information from HTTP header and it's working fine for API, but console application getting tenant information with every message (tenant info is part of queue message) so I am calling dependency injection register method on every incoming message which is not correct, do you have any suggestion/solution here?

The way I am resolving ITenantContext in API

services.AddScoped<ITenantContext>(serviceProvider =>
{
    //Get Tenant from JWT token
    if (string.IsNullOrWhiteSpace(tenantId))
    {
        //1. Get HttpAccessor and processor settings
        var httpContextAccessor =
            serviceProvider.GetRequiredService<IHttpContextAccessor>();

        //2. Get tenant information (temporary code, we will get token from JWT)
        tenantId = httpContextAccessor?.HttpContext?.Request.Headers["tenant"]
            .FirstOrDefault();
        if (string.IsNullOrWhiteSpace(tenantId))
            //throw bad request for api
            throw new Exception($"Request header tenant is missing");
    }

    var tenantSettings =
        serviceProvider.GetRequiredService<IOptionsMonitor<TenantSettings>>();
    return new TenantContext(tenantId, tenantSettings );
});


Solution 1:[1]

Create two different ITenantContext implementations. One for your Web API, and one for your Console application.

Your Web API implementation than might look as follows:

public class WebApiTenantContext : ITenantContext
{
    private readonly IHttpContextAccessor accessor;
    private readonly IOptionsMonitor<TenantSettings> settings;

    public WebApiTenantContext(
        IHttpContextAccessor accessor,
        IOptionsMonitor<TenantSettings> settings)
    {
        // Notice how the dependencies are not used in this ctor; this is a best
        // practice. For more information about this, see Mark's blog:
        // https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
        this.accessor = accessor;
        this.settings = settings;
    }

    // This property searches for the header each time its called. If needed,
    // it can be optimized by using some caching, e.g. using Lazy<string>.
    public string TenantId =>
        this.accessor.HttpContext?.Request.Headers["tenant"].FirstOrDefault()
        ?? throw new Exception($"Request header tenant is missing");
}

Notice that this implementation might be a bit naive for your purposes, but hopefully you'll get the idea.

This class can be registered in the Composition Root of the Web API project as follows:

services.AddScoped<ITenantContext, WebApiTenantContext>();

Because the WebApiTenantContext has all its dependencies defined in the constructor, you can do a simple mapping between the ITenantContext abstraction and the WebApiTenantContext implementation.

For the Console application, however, you need a very different approach. The WebApiTenantContext, as shown above, is currently stateless. It is able to pull in the required data (i.e. TenantId) from its dependencies. This probably won't work for your Console application. In that case, you will likely need to manually wrap the execution of each message from the queue in a IServiceScope and initialize the ConsoleTenantContext at the beginning of that request. In that case, the ConsoleTenantContext would look merely as follows:

public class ConsoleTenantContext : ITentantContext
{
    public string TenantId { get; set; }
}

Somewhere in the Console application's Composition Root, you will have to pull messages from the queue (logic that you likely already have), and that's the point where you do something as follows:

var envelope = PullInFromQueue();

using (var scope = this.serviceProvider.CreateScope())
{
    // Initialize the tenant context
    var context = scope.ServiceProvider.GetRequiredService<ConsoleTenantContext>();
    content.TenantId = envelope.TenantId;

    // Forward the call to the message handler
    var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
    handler.Handle(envelope.Message);
}

The Console application's Composition Root will how have the following registrations:

services.AddScoped<ConsoleTenantContext>();
services.AddScoped<ITenentContext>(
    c => c.GetRequiredServices<ConsoleTenantContext>());

With the registrations above, you register the ConsoleTenantContext as scoped. This is needed, because the previous message infrastructure needs to pull in ConsoleTenantContext explicitly to configure it. But the rest of the application will depend instead on ITenantContext, which is why it needs to be registered as well. That registration just forwards itself to the registered ConsoleTenantContext to ensure that both registrations lead to the same instance within a single scope. This wouldn't work when there would be two instances.

Note that you could use the same approach for Web API as demonstrated here for the Console application, but in practice it's harder to intervene in the request lifecycle of Web API compared to doing that with your Console application, where you are in full control. That's why using an ITenantContext implementation that is itself responsible of retrieving the right values is in this case an easier solution for a Web API, compared to the ITenantContext that is initialized from the outside.

What you saw here was a demonstration of different composition models that you can use while configuring your application. I wrote extensively about this in my series on DI Composition Models on my blog.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1