Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting/Managing at runtime changing connectionstrings using Entity Framework, Dependency Injection, Unit of Work and Repository Patterns

The situation

I'm building a web application using the in the title mentioned techniques. This application will something like a CMS system for multiple clients. The client has to login to this system using his company name and login credentials.

With the provided company name, I connect to a database (static DbContext, same connection string every time) where all clients database information is stored and search for this clients specific database(every client has his own with exact same design) login information. That all works fine.

Now here is the tricky part. To continue the login procedure I need to somehow inject or lazy load the repository using the other DbContext with a connection string that is build up from the result of the other database.

What I have

2 DbContexts generated from an existing database, one static and one if possible dynamic.

Then the generic repository classes/interfaces:

public interface IRepository
{
    void Submit();
}

public interface IRepository<TEntity, TContext> : IRepository
    where TEntity : class
    where TContext : DbContext
{
     //crud stuff
}

public abstract class GenericRepository<TEntity, TContext> : IRepository<TEntity, TContext>
    where TEntity : class
    where TContext : DbContext
{
    private TContext _dataContext;
    private IUnitOfWork _unitOfWork;
    private readonly IDbSet<TEntity> dbset;

    protected GenericRepository(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        _unitOfWork.Register(this);
    }
}

Unit of work class/interface

public interface IUnitOfWork
{
    void Register(IRepository repository);
    void Commit();
}

public class UnitOfWork : IUnitOfWork 
{
    private readonly Dictionary<string, IRepository> _repositories;
    private HttpContextBase _httpContext;
    
    public UnitOfWork(HttpContextBase httpContext)        
    {            
        _httpContext = httpContext;
    }

    public void Register(IRepository repository)
    {
        _repositories.Add(repository.GetType().Name, repository);
    }        

    public void Commit()
    {
        _repositories.ToList().ForEach(x => x.Value.Submit());
    }
}

Then a context/entity specific repository

public class EmployeeRepository : GenericRepository<tbl_Medewerker, CustomerDbEntities>, IEmployeeRepository
{
    public EmployeeRepository(IUnitOfWork unitOfWork)
        : base(unitOfWork)
    {
    }
}

public interface IEmployeeRepository : IRepository<tbl_Medewerker, CustomerDbEntities>
{
}

Then the service that implements the repository

public interface IEmployeeLoginService
{
    tbl_Medewerker GetEmployeeByLogin(string username, string password);
    tbl_Medewerker GetEmployeeByID(Guid id);
}

public class EmployeeLoginService : IEmployeeLoginService
{
    private readonly IEmployeeRepository _employeeRepository;

    public EmployeeLoginService(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    public tbl_Medewerker GetEmployeeByLogin(string username, string password)
    {
        return _employeeRepository.Get(e => e.MedewerkerNaam.ToLower() == username.ToLower() && e.Password == password);
    }

    public tbl_Medewerker GetEmployeeByID(Guid id)
    {
        return _employeeRepository.GetById(id);
    }
}

Finally the controller that implements that service and uses it in the login action

public class AccountController : BaseController
{
    IConnectionService _connectionService;
    IEmployeeLoginService _employeeService;

    public AccountController(IConnectionService connectionService, IEmployeeLoginService employeeService)
    {
        _connectionService = connectionService;
        _employeeService = employeeService;
    }

    [AllowAnonymous, HttpPost]
    public ActionResult Login(LoginModel login)
    {
        if ((Settings)Session["Settings"] == null)
        {
            Settings settings = new Settings();

            settings.company = _connectionService.GetCompanyName(login.CompanyName);
            if (settings.company != null)
            {
                settings.licence = _connectionService.GetLicenceByCompanyID(settings.company.Company_id);
                if (settings.licence != null)
                {
                    settings.connectionStringOrName = string.Format(@"Data Source={0};Initial Catalog={1};User ID={2};Password={3};Application Name=EntityFrameworkMUE", settings.licence.WS_DatabaseServer, settings.licence.WS_DatabaseName, settings.licence.WS_DatabaseUID, settings.licence.WS_DatabasePWD);                        
                    Session["Settings"] = settings;
                   
                    settings.user = _employeeService.GetEmployeeByLogin(login.UserName, login.Password);
                    if (settings.user != null)
                    {
                        FormsAuthentication.SetAuthCookie(string.Format("{0},{1}", settings.company.Company_id.ToString(), settings.user.Medewerker_ID.ToString()) , login.RememberMe);
                        return RedirectToAction("index", "home");                            
                    }
                }
            }
        }
        else
        {
            return RedirectToAction("index", "home");
        }

        return View();
    }
}

And of course the autofac bootstrapper:

private static void SetAutoFacContainer()
{
    var builder = new ContainerBuilder();
    builder.RegisterControllers(Assembly.GetExecutingAssembly());
    builder.RegisterType(typeof(UnitOfWork)).As(typeof(IUnitOfWork)).InstancePerHttpRequest();           
    builder.RegisterAssemblyTypes(typeof(UserRepository).Assembly)
            .Where(t => t.Name.EndsWith("Repository"))
            .AsImplementedInterfaces().InstancePerHttpRequest();
    builder.RegisterAssemblyTypes(typeof(ConnectionService).Assembly)
            .Where(t => t.Name.EndsWith("Service"))
            .AsImplementedInterfaces().InstancePerHttpRequest();

     builder.Register(c => new HttpContextWrapper(HttpContext.Current)).As<HttpContextBase>().InstancePerLifetimeScope();
     builder.RegisterModule(new AutofacWebTypesModule());

     builder.Register(att => new AuthorizeFilter(att.Resolve<IConnectionService>(), att.Resolve<IEmployeeLoginService>())).AsAuthorizationFilterFor<Controller>().InstancePerHttpRequest();
     builder.RegisterFilterProvider();

     IContainer container = builder.Build();
     DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}

My idea how to do this, is setting a session variable with the connection string after data retrieval from the one static database where the info is stored and inject session in the unit of work and somehow use it there, but I can't wrap my head around it.

The question(s):

Am I heading in the right direction trying to achieve this, or even is it possible? If not what steps would you take to achieve this

I know it's a long read I hope you guys can help me, I'm quite new to using these techniques all together. Thanks in advance - I really appreciate it!

like image 714
edgarpetrauskas Avatar asked Sep 29 '22 22:09

edgarpetrauskas


1 Answers

Your on the right track, I have used

var mtc = new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(mtc));

The identification strategy would be based on the logged in user. With defaults for when they aren't logged in.

public class CompanyNameIdentificationStrategy : ITenantIdentificationStrategy
    {

        public bool TryIdentifyTenant(out object tenantId)
        {
             var context = HttpContext.Current;
             if(context != null) 
             {
                var myUser = context.User as MyUserObject;
                if(myUser != null) 
                {
                    tenantId = myUser.CompanyName;
                    return true;
                }
             }
             return false; 
        }
}

Then you add to your autofact setup:

 var s = c.Resolve<ITenantIdentificationStrategy>();
                    object id;
                    if (s.TryIdentifyTenant(out id) && id != null)
                    {
                        return id;
                    }
                    return "default"; 
                }).Keyed<string>("CompanyName");



builder.Register<Settings>(c =>
                {
                    var companyName = c.ResolveKeyed<string>("companyName");
                    if (companyName == "default")
                    {
                        return new DefaultSettings();
                    }
                    var settings = new Settings(); 
                    return settings;

                }).InstancePerLifetimeScope();

You can resolve stuff inside these code blocks. I would probably setup a keyed default settings, and then when the user is logged in the settings would switch to their setup and the rest of the application should work.

like image 52
user3420988 Avatar answered Oct 07 '22 20:10

user3420988