I have a bunch of services that get registered as singletons by my IoC container on startup, all of these services have a constructor which takes in a connection string which is used by the underlying storage provider.
This has been fine given the connection string was static and never changed during a session, however, I now need to implement the ability for users to change the connection string at runtime (e.g. they can choose to point to a local/cloud DB).
Ultimately, what this means is I need to somehow invalidate all my services in the IoC container and as far as I could see my chosen IoC container doesn't appear to provide a means of doing this, so it's down to me to come up with a practical solution.
Now there are many ways I could do this but I am looking for feedback, suggestions (from experience if possible) on what would be considered the optimal solution. Here is a simplified service
public class Service
{
public Service (string connectionString)
{
this.ConnectionString = connectionString;
}
// marked as protected as derived services may need access to them
protected string ConnectionString { get; set; }
...
}
And here is how it is currently registered
public static void StartUp(IAppSettings appSettings)
{
container.Register<IService, Service> (new Service(appSettings.ConnectionString));
...
}
The simplest solution, which I personally don't like, is simply to have all my services take a reference to IAppSettings
rather than ConnectionString
and use a read-only property e.g.
protected string ConnectionString { get { return appSettings.ConnectionString; } }
However, given all that the services need from IAppSettings
is the connection string this just seems wrong from a modelling point of view. Keeping with this approach, but improving on it slightly would be to introduce a new interface which exposes only the information needed from IAppSettings
e.g.
public interface IDbSettings
{
string ConnectionString { get; set; }
}
public class AppSettings : IAppSettings, IDbSettings
{
...
}
...
container.Register<IService, Service> (new Service(appSettings as IDbSettings));
I say slightly because ultimately I still have the same problem (services are still referencing a large object for one simple property).
The type of approach I would like, and what I feel would be best, would be to extend the current IoC container to introduce an Invalidate
method whereby I can simply set the internal instance to null
and have it re-initialized when resolved. I see this looking something like
appSettings.ConnectionString = "database=db1";
container.Register<IService, Service>(new Service(appSettings.ConnectionString));
...
var svc = container.Resolve<IService>(); // connects to db1
appSettings.ConnectionString = "database=db2";
...
var svc = container.Resolve<IService>(); // still connects to db1
container.Invalidate<IService>();
var svc = container.Resolve<IService>(); // connects to db2
For me this gives all round benefits
- You control exactly when you want the changes to take affect
- It keeps with the Singleton pattern
- It means the only thing that changes is the IoC container
I would be interested in everyones thoughts, critiques etc.