I ran into a use case recently around async initialization with a web api that I realized I didn’t know how to implement. I had built a caching service using IDistributedCache that instantiated a singleton cache that I wanted to share across HTTP requests.
This is straight forward using dependency injection like this:
services.AddDistributedMemoryCache();
services.AddSingleton<ICachingService<Person>, PersonCache>();
Where my cache implementation would take the IDistributedCache instance (in this case in-memory) via dependency injection. But what if you wanted to initialize the cache with data. The cache implementation I wrote included a method to do this called WarmAsync. This method would perform async operations to read from a datastore and pre-populate the cache.
Usually custom initialization can be performed in a function during the creation of the scope:
services.AddSingleton<ICachingService<Person>, PersonCache>((provider) =>
{
var personCache = new PersonCache();
//do initialization in here - async calls not supported :(
return personCache;
});
The issue here is that this is not an async function, so without jumping through hoops or using hacks like GetAwaiter().GetResult() async methods can not be used here. Luckily there is a pretty simple way to get this to work. It involves leveraging the boilerplate code in Program.cs.
The default looks like this:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
Changing this method to be async and leveraging the ServiceProvider that is part of the web host gives us what we want:
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
//a serviceProvider instance is available here
var serviceProvider = host.Services;
var personCache =
serviceProvider.GetRequiredService<ICachingService<Person>>();
//async initialization supported here as well
await personCache.WarmAsync();
await host.RunAsync();
}
And that’s it – async support during initialization.