Reputation: 3062
Ok, so I have a class instance being added to the services collection in startup.cs
during runtime like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<WidgetProvider, BlueWidgetProvider>();
}
However, while running I want the application to be able to replace the BlueWidgetProvider
with a RedWidgetProvider
. How would I go about doing this? I know you can use the Remove
method on an IServicesCollection
, but how would I expose this to my application? I can expose the IServiceProvider
that has the WidgetProvider
added like below, but I'm not sure how I'd go about accessing the underlying services collection.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
Current.Services = host.Services; //Here
host.Run(Current.AppCancellationSource.Token);
}
Upvotes: 2
Views: 2828
Reputation: 1778
You are not supposed to change the service collection while the application is running. If one of the services must be polymorphic and change the implementation dynamically, you should introduce intermediate "selector" service, which is aware of all existing implementations and delegates the calls to the one active at the moment.
Something like this:
interface IWidgetProvider
{
Widget GetWidget();
}
class RedWidgetProvider: IWidgetProvider
{
public Widget GetWidget()
{
//return the red widget, whatever it means
}
}
class BlueWidgetProvider: IWidgetProvider
{
public Widget GetWidget()
{
//return the blue widget, whatever it means
}
}
interface IWidgetProviderSelector
{
IWidgetProvider GetWidgetProvider();
}
class WidgetProviderSelector: IWidgetProviderSelector
{
private IMoodSource _mood;
private RedWidgetProvider _red;
private BlueWidgetProvider _blue;
public WidgetProviderSelector(IMoodSource mood, RedWidgetProvider red, BlueWidgetProvider blue)
{
_mood = mood;
_red = red;
_blue = blue;
}
public IWidgetProvider GetWidgetProvider()
{
return _mood.IsBlue() ? _blue : _red;
}
}
Then you register all of those in the DI container:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IWidgetProviderSelector, WidgetProviderSelector>();
services.AddSingleton<RedWidgetProvider>();
services.AddSingleton<BlueWidgetProvider>();
services.AddSingleton<IMoodSource, MyMoodSource>();
}
Then you use the selector to get the current provider and use whatever it returns to get a widget.
Upvotes: 0
Reputation: 3062
Well one method that's working for me is to cancel the host.Run
method and recursively call Main
again like this:
Make some setting change that tells startup to use a different widget provider when configuring services:
public void UseRedWidgetProvider() {
database.UseRedWidgetProvider();
Restart();
}
Method to cancel host.Run
method:
public void Restart()
{
Current.AppCancellationSource.Cancel();
}
Static class to contain cancellation token:
public static class Current
{
public static CancellationTokenSource AppCancellationSource = new CancellationTokenSource();
}
Recursively start Main
after cancellation token is invoked:
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
CoreCurrent.Protector = ActivatorUtilities.CreateInstance<DataProtect>(host.Services);
Current.Services = host.Services;
Current.SetDbConfigurationState();
host.Run(Current.AppCancellationSource.Token);
//reset token and call main again
host.Dispose();
Current.AppCancellationSource = new System.Threading.CancellationTokenSource();
Main(args);
}
This appears to be working ok for me. Once I invoke the cancellation token the app appears to restart fairly quickly and the new widget provider is now used. Not entirely sure if this is a good practice or if there are any components that might be messed up.
Upvotes: 2