I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository
and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With