Revisiting the Singleton pattern – Dependency Injection

The Singleton pattern is obsolete, goes against the SOLID principles, and we replace it with a lifetime, as we’ve already seen. This section explores that lifetime and recreates the good old application state, which is nothing more than a singleton-scoped dictionary.We explore two examples: one about the application state, in case you were wondering where that feature disappeared to. Then, the Wishlist project also uses the singleton lifetime to provide an application-level feature. There are also a few unit tests to play with testability and to allow safe refactoring.

Project – Application state

You might remember the application state if you programmed ASP.NET using .NET Framework or the “good” old classic ASP with VBScript. If you don’t, the application state was a key/value dictionary that allowed you to store data globally in your application, shared between all sessions and requests. That is one of the things that ASP always had and other languages, such as PHP, did not (or do not easily allow).For example, I remember designing a generic reusable typed shopping cart system with classic ASP/VBScript. VBScript was not a strongly typed language and had limited object-oriented capabilities. The shopping cart fields and types were defined at the application level (once per application), and then each user had their own “instance” containing the products in their “private shopping cart” (created once per session).In ASP.NET Core, there is no more Application dictionary. To achieve the same goal, you could use a static class or static members, which is not the best approach; remember that global objects (static) make your application harder to test and less flexible. We could also use the Singleton pattern or create an ambient context, allowing us to create an application-level instance of an object. We could even mix that with a factory to create end-user shopping carts, but we won’t; these are not the best solution either. Another way could be to use one of the ASP.NET Core caching mechanisms, memory cache, or distributed cache, but this is a stretch.We could also save everything in a database to persist the shopping cart between visits, but that is not related to the application state and requires more work, potentially a user account, so we will not do that either.We could save the shopping cart on the client-side using cookies, local storage, or any other modern mechanism to save data on the user’s computer. However, we’d get even further from the application state than using a database.For most cases requiring an application state-like feature, the best approach would be to create a standard class and an interface and then register the binding with a singleton lifetime in the container. Finally, you inject it into the component that needs it, using constructor injection. Doing so allows the mocking of dependencies and changing the implementations without touching the code but the composition root.

Sometimes, the best solution is not the technically complex ones or design pattern-oriented; the best solution is often the simplest. Less code means less maintenance and fewer tests, resulting in a simpler application.

Let’s implement a small program that simulates the application state. The API is a single interface with two implementations. The program also exposes part of the API over HTTP, allowing users to get or set a value associated with the specified key. We use the singleton lifetime to ensure the data is shared between all requests.The interface looks like the following:

public interface IApplicationState
{
    TItem?
Get<TItem>(string key);
    bool Has<TItem>(string key);
    void Set<TItem>(string key, TItem value) where TItem : notnull;
}

We can get the value associated with a key, associate a value with a key (set), and validate whether a key exists.The Program.cs file contains the code responsible for handling HTTP requests. We can swap the implementations by commenting or uncommenting the first line of the Program.cs file, which is #define USE_MEMORY_CACHE. That changes the dependency registration, as highlighted in the following code:

var builder = WebApplication.CreateBuilder(args);
#if USE_MEMORY_CACHE
        builder.Services.AddMemoryCache();
        builder.Services.AddSingleton<IApplicationState, ApplicationMemoryCache>();
#else
        builder.Services.AddSingleton<IApplicationState,
ApplicationDictionary>();
#endif
var app = builder.Build();
app.MapGet(“/”, (IApplicationState myAppState, string key) =>
{
    var value = myAppState.Get<string>(key);
    return $”{key} = {value ??
“null”}”;
});
app.MapPost(“/”, (IApplicationState myAppState, SetAppState dto) =>
{
    myAppState.Set(dto.Key, dto.Value);
    return $”{dto.Key} = {dto.Value}”;
});
app.Run();
public record class SetAppState(string Key, string Value);

Let’s now explore the first implementation.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy