9

I am building the multi tenant app and trying to get the data security working now. Unit tests of Context are working nice because I create the context with options and UserContext, but I am struggling to get it working in assembly as it needs userContext to be injected.

I cannot use standard controller injection as with everything else, as if I have it, the context creation fails:

services.AddEntityFrameworkNpgsql()
                .AddDbContext<MultiTenantContext>(o =>
                    o.UseNpgsql(Configuration.GetConnectionString("DbContext")

I cannot or I do not know how to inject my UserContext this way...

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Marek Urbanowicz
  • 12,659
  • 16
  • 62
  • 87
  • What error message are you getting? – Isaac Kleinman Feb 08 '18 at 23:59
  • it is just null there and effectively it is causing stackoverflowexception... – Marek Urbanowicz Feb 09 '18 at 00:00
  • Is the web.config containing the DbContext connection string accessible to the code? – Isaac Kleinman Feb 09 '18 at 00:00
  • Yes. It is creating DbContext but without the UserContext which is scoped – Marek Urbanowicz Feb 09 '18 at 00:01
  • The default lifetime for database contexts with EF Core is scoped though. – Can you show what you are actually trying to do and what error you get? – poke Feb 09 '18 at 00:16
  • Is `UserContext` a different `DbContext`? Your code is only showing adding `MultiTenantContext`. – Brad Feb 09 '18 at 01:09
  • @MU Do you use UserContext in the DbContext constructor? Could you share us how you achieved "create the context with options and UserContext"? Did the constructor of UserContext have parameter? If it likes DbContext, you may try inject like`services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));` – Edward Feb 09 '18 at 05:30

3 Answers3

12

Simple use constructor injection. It is working the same way like in a controller.

public class MultiTenantContext : DbContext
{
    private UserContext _userContext;

    public MultiTenantContext(DbContextOptions options, UserContext userContext) : base(options)
    {
        _userContext = userContext;
    }
}

You need to make sure, that you register the UserContext service before you are registering the entity framework. e. g.

services.AddScoped<UserContext, UserContext>();
services.AddEntityFrameworkNpgsql()
                .AddDbContext<MultiTenantContext>(o =>
                    o.UseNpgsql(Configuration.GetConnectionString("DbContext")
Christian Gollhardt
  • 16,510
  • 17
  • 74
  • 111
5

I am assuming for multi-tenant application you are trying to find out Tenant based on logged in user from HttpRequest. You can do something like this

public class UserContext
{
    private readonly IHttpContextAccessor _accessor;
    public RequestContextAdapter(IHttpContextAccessor accessor)
    {
        this._accessor = accessor;
    }

    public string UserID
    {
        get
        {
            // you have access to HttpRequest object here
            //this._accessor.HttpContext.Request
        }
    }
}

ASP.NET Core will automatically inject IHttpContextAccessor into UserContext

You also have to register UserContext

services.AddScoped<UserContext, UserContext>();

then use constructor injection to inject UserContext wherever you need

LP13
  • 30,567
  • 53
  • 217
  • 400
2

The accepted answer didn't work for me as well, I ended up doing a very similar approach to the original answer, thanks @Christian for your guidance.

I wanted to inject a service into the DbContext which is responsible to load connection string dynamically based on some HTTP request header below is the code used

In Startup:

  services.AddScoped<IConnectionService, ConnectionService>();
  services.AddDbContext<MyDbContext>(); //Connection string will be set on event OnConfiguring

In MyDbContext:

public class MyDbContext : DbContext
    {
        private readonly IConnectionService _connectionService;

        public MyDbContext(DbContextOptions<MyDbContext> options, IConnectionService connectionService) : base(options)
        {
            _connectionService = connectionService;
        }

        public DbSet<Customer> Customers { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var connectionString = _companyConnectionService.ConnectionString; //Custom logic to read from http request header certain value and switch connection string
            optionsBuilder.UseSqlServer(connectionString);
        }
    }

Hope this will help.

Taiseer Joudeh
  • 8,953
  • 1
  • 41
  • 45