Search code examples
asp.netasp.net-mvc-5entity-framework-6ninject

ASP.NET MVC 5 + EF6 + Ninject - Multitenancy Database


I have a business ASP.NET MVC5 application where each customer has his own database. I want to use EF6 and Ninject for DI. For login I'm using ASP.NET Identity.

For each user exists a UserClaim where the name of the database is specified:

  1. UserId = 1 | ClaimType = "db_name" | ClaimValue = "Customer0001"
  2. UserId = 2 | ClaimType = "db_name" | ClaimValue = "Customer0002"

and so on... This means it is one web-application with a "shared" database for user authentication and on the other side each customers has his own database - all databases are located on the same database server (MS SQL Server).

The user need to login in, after login he should receive data from his personal database (specified in the UserClaim-Table).

For Ninject I think I have to something like this

private void AddBindings() {
   kernel.Bind<EFDBContext>().ToMethod(c => new EFDBContext("db_name"));
}

But how would I get the UserClaim into the bindings? (I don't want to use a Session, because sessions can get lost).

And what steps after the bindings are necessary?

For example at the AccountRepository the EFDBContext expects the "db_name" > but how would I get it there?

public class AccountRepository : IAccountRepository {
    private EFDBContext context = new EFDBContext("db_name");
}

And finally I can change the connection string inside of this class??

public class EFDBContext : DbContext {
    public EFDBContext(string db_name) : base("EFDBContext") {

    }
}

UPDATE AFTER @Hooman Bahreini ANSWER

NinjectDependencieResolver.cs

private void AddBindings() {
    kernel.Bind<ICustomerRepository>().To<CustomerRepository>().WithConstructorArgument("http_current_context", HttpContext.Current);
}

CustomerRepository.cs

    public class CustomerRepository : ICustomerRepository {

        private CustomerDBContext context;

        public CustomerRepository(HttpContext httpContext) {

            string db_name = "";
            var claimValue = ((ClaimsPrincipal)HttpContext.Current.User).Claims.FirstOrDefault(c => c.Type == "db_name");

            if(claimValue != null) {
                db_name = claimValue.Value.ToString();
            }

            context = new CustomerDBContext(db_name);
        }

        public IEnumerable<Test> Tests {
            get { return context.Test; }
        }
    }

DB-Context-File

    public class CustomerDBContext : DbContext {

        public CustomerDBContext(string db_name) : base("CustomerDBContext") {

            string temp_connection = Database.Connection.ConnectionString.Replace(";Initial Catalog=;", ";Initial Catalog=" + db_name + ";");

            Database.Connection.ConnectionString = temp_connection;
        }

        public DbSet<Test> Test { get; set; }
    }

Solution

  • You can access user claims from HttpContext:

    var claimValue = ((ClaimsPrincipal)HttpContext.Current.User)
                     .Claims
                     .FirstOrDefault(c => c.Type == "db_name");
    

    For your ninject code, you can create an extension method for HttpContext:

    public static HttpcontextExtensions 
    {
        public static string GetDbName(this HttpContext context)
        {
            return ((ClaimsPrincipal)context.Current.User)
                   .Claims
                   .FirstOrDefault(c => c.Type == "db_name");
        }
    }
    

    And use the following ninject binding:

    kernel.Bind<ICustomerRepository>()
        .To<CustomerRepository>()
        .WithConstructorArgument("db_name", HttpContext.GetDbName());
    

    See this document for more info about accessing HttpContext in ninject.


    In your example, CustomerRepository has a dependency on HttpContext, this is not a good design. CustomerRepository requires a db-name, and that's what should be passed in the constructor. Related to this is Nikola’s 4th law of IoC

    Every constructor of a class being resolved should not have any implementation other than accepting a set of its own dependencies.

    To give you an example, you don't have any HttpContext in your test project, which makes unit testing CustomerRepository complicated.

    P.S. I don't know your design, but maybe getting db-name from HttpContext is not an ideal solution... user may logout or clear their browser history and you will loose your db-name.