Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass the UserId/TenantId to a Repository Constructor using Dependency Injection?

I am writting a multi-tenants web service using ASP.NET 5 and EF7. The Repository Data is specific to a tenant/user. Because I'll be using the TenantId in all the calls, I would like to initialize the Repository with the current TenantId.

public class MyRepository {
    private int tenantId;
    public MyRepository(MyDbContext context, int tenantId) { 
        this.tenantId = tenantId;
        // ...
    }

    public Task<List<Data>> GetAllAsync() {
        return this.context.Data.Where(d => d.TenantId == this.tenantId).ToListAsync();
    }
}

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        // How do I pass the TenantId to the constructor?
        services.AddScoped<MyRepository>();
    }
}

Is it possible to initialize my Repository once after a user has been authenticated? How would I pass the TenantId with the constructor?

like image 849
Martin Avatar asked Dec 03 '22 16:12

Martin


1 Answers

Both your tenant and user id values are runtime data and you should not inject runtime data into the components of your system (your repository in this case), because that complicates your Composition Root tremendously (as you are already experiencing) and makes it almost impossible to verify your object graph. Runtime data should flow through an already constructed object graph. There are basically two ways of achieving this. Either you pass through the data through the method calls of your component's public API, or you inject a component that is responsible for retrieving the runtime value when it is requested.

In your case, the latter option is best. My advice is therefore to inject a component that allows retrieving this contextual information:

public interface ITenantContext
{
    int CurrentTenantId { get; }
}

public interface IUserContext
{
    int CurrentUserId { get; }
}

public class MyRepository {
    private readonly Func<MyDbContext> contextProvider;
    private readonly ITenantContext tentantContext;

    public MyRepository(Func<MyDbContext> contextProvider, ITenantContext tentantContext){ 
        this.contextProvider = contextProvider;
        this.tentantContex = tentantContex;
    }

    public Task<List<Data>> GetAllAsync() {
        return this.contextProvider().Data
            .Where(d => d.TenantId == this.tenantContext.CurrentTenantId)
            .ToListAsync();
}

This solves the problem elegantly, because now you can define a ITenantContext implementation that knows how to retrieve the right tenant ID for the current request. For instance:

public sealed class AspNetSessionTenantContext : ITenantContext {
    public int CurrentTenantId {
        get { return (int)HttpContext.Current.Session["tenantId"]; }
    }
}

This even allows you to register all your components as singleton, which can improve performance, and reduce the change of common DI pitfalls, such as Captive Dependencies.

like image 127
Steven Avatar answered Dec 09 '22 15:12

Steven