Connection String Management

3/28/2023

By: Shaun Walker

One of the areas of the framework which has created some support issues in the past is connection string managemement. .NET developers generally expect connection strings to be located in appsettings.json, so the fact that Oqtane had a master connection string in appsettings.json but maintained its tenant connection strings inside of the master database caused some confusion. This was especially problematic when troubleshooting installation issues or trying to migrate Oqtane to new infrastructure. The framework has now been enhanced to store all connection strings in appsettings.json.

The challenge was introducing this change in an upgradeable and backwards compatible manner. Since database connections are so central to the functionality of the framework, including third party module integrations, great care had to be taken to ensure a seamless upgrade experience.

The biggest challenge was related to the DBContextBase class... and in particular the challenges of inheritance in .NET when using dependency injection. It turns out that are many articles documenting this problem, as well as a variety of solutions, however by the time you run into this dilemma it may already be too late to code your way out of it without introducing a breaking change.

In a nutshell, base classes generally have dependencies... and these dependencies need to be passed to the base class from your derived classes. This all works well until you need to add an additional dependency to the base class - as it means that all derived classes will need to be modified to support this change (ie. a breaking change). In the case of Oqtane's DBContextBase, it had 2 dependencies already... but it needed an additional dependency for IConfigurationRoot which would allow the framework to obtain a connection string from appsettings.json.

The general recommendation is that if you are developing a new base class and there is any chance that it may require additonal functionality in the future, it is best to create a dedicated "dependencies" class. This class would have the flexibility to add/remove dependencies without affecting any derived classes. DBContextBase has now been modified to have its own dependencies class. This allowed the framework to add a reference to IConfigurationRoot and in the future it will be able to accommodate other changes as well.

This works well for the future but what about existing derived classes which are dependent upon the current DBContextBase interface? Luckily .NET included a ConfigurationBuilder capability which allows you to create the Config service without dependency injection. This is clearly an anti-pattern and is less than optimal, but it allows the framework to support the old constructor for backward compatibility while still supporting the new connection string behavior (a compiler message has also been included to let developers know they should upgrade their code to the newer method).

So how would a module developer upgrade their code to the new method? The main change would be in the Context class which is usually located in the Repository namespace. The contructor needs to be modified to use the new IDBContextDependencies rather than the old ITenantManager and IHttpContextAccessor dependencies:

Old

public ProjectContext(ITenantManager tenantManager, IHttpContextAccessor accessor) : base(tenantManager, accessor)

New

public BlogContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)

In addition, modules which are using EF Core Migrations have a Manager class for executing the Migrate method of the MigratableModuleBase class. The Manager class will have to be modified to use the new IDBContextDependencies rather than the old ITenantManager and IHttpContextAccessor dependencies:

Old

private readonly IDBContextDependencies _DBContextDependencies;

public BlogManager(IBlogRepository Blogs, ISqlRepository sql, ITenantManager tenantManager, IHttpContextAccessor accessor)
{
    _Blogs = Blogs;
    _sql = sql;
    _tenantManager = tenantManager;
    _accessor = accessor;
}

public bool Install(Tenant tenant, string version)
{
    return Migrate(new BlogContext(_tenantManager, _accessor), tenant, MigrationType.Up);
}

public bool Uninstall(Tenant tenant)
{
    return Migrate(new BlogContext(_tenantManager, _accessor), tenant, MigrationType.Down);
}

New

public BlogManager(IBlogRepository Blogs, ISqlRepository sql, IDBContextDependencies DBContextDependencies)
{
    _Blogs = Blogs;
    _sql = sql;
    _DBContextDependencies = DBContextDependencies;
}

public bool Install(Tenant tenant, string version)
{
    return Migrate(new BlogContext(_DBContextDependencies), tenant, MigrationType.Up);
}

public bool Uninstall(Tenant tenant)
{
    return Migrate(new BlogContext(_DBContextDependencies), tenant, MigrationType.Down);
}


Do You Want To Be Notified When Blogs Are Published?
RSS