Updating a dependency injected singleton

Viewed 3001

I generate a singleton at runtime

public void ConfigureServices(IServiceCollection services)
{
    var applications = Utils.generateApplications()
    services.AddSingleton<ApplicationModel[]>(applications);
    services.AddMvc();
}

How can I later update this dependency injected ApplicationModel[] with a completely new ApplicationModel[]. I have a feature in my application that the user can use to trigger a data refresh on the applications, but how can I update the underlying injected Singleton so all future injections will use the updated applications? I have a function Utils.generateApplications() that gets an up to date list of applications and returns a ApplicationModel[]. How do I overwrite the old injected Singleton with a new object to be injected into future calls if that makes sense?

I have some code :

public void UpdateData()
{
    var applications = Utils.generateApplications()
    //How do I set applications to replace the injected singleton for all future injections?
}
2 Answers

You should use an additional layer of indirection. I think the simplest way is to use an abstract factory. Define an interface something like this:

interface IApplicationModelFactory
{
    public ApplicationModel[] GetModel();
}

Define a second interface with the method (or methods) needed to update the model:

interface IApplicationModelUpdate
{
    void UpdateModel();
}

You can then change your ApplicationModel[] registration from single instance to scoped and delegate to the factory:

var modelFactory = new ApplicationModelFactory();
services.AddSingleton<IApplicationModelFactory>(provider => modelFactory);
services.AddSingleton<IApplicationModelUpdate>(provider => modelFactory);
services.AddScoped<ApplicationModel[]>(provider => modelFactory.GetModel());

Inject IApplicationModelUpdate into the types that update the model and ApplicationModel[] into the types that use it. This has the advantage that all types resolved for the same request will get a consistent view of the model, even if it changes in the middle of processing that request.

You could also inject IApplicationModelFactory into the consumer code, but I think injecting the model directly is better. Using the factory can lead to different bits of code seeing different models during the same request. The mutability of the model is also an implementation detail that consumer code shouldn't have to worry about.

I wouldn't monkey with dependency injection that way. Instead, inject a factory, and write whatever logic you need to return the proper instance.

Simple factory:

interface IApplicationModelFactory
{
    ApplicationModel[] Model { get; }
}

class ApplicationModelFactory : IApplicationModelFactory
{
    public ApplicationModel[] Model { get; set; }
}

Registration:

services.AddSingleton<IApplicationModelFactory>
(
    new ApplicationModelFactory[] { Model = util.generateApplications() }
)

class receiving the injection:

class Foo
{
    protected readonly IApplicationModelFactory _factory;

    public Foo(IApplicationModelFactory injected)
    {
        _factory = injected;
    }

    protected ApplicationModel[] => _factory.Model;

    public void Bar()
    {
        DoSomethingWithModel(this.ApplicationModel);
    }
}      
Related