Dependency inject a custom file provider for runtime compilation

992 Views Asked by At

I am experimenting with loading views from a database, and as suggested in the article one might want to add some caching to prevent hitting the database every time.

ConfigureServices:

services.AddHttpContextAccessor();
services.AddMemoryCache();

services.AddRazorPages()
    .AddRazorRuntimeCompilation(opt =>
    {
        opt.FileProviders.Add(new DatabaseFileProvider(Configuration["AppSettings:SQLConnectionString"]));
    });

DatabaseFileProvider constructor:

private string _connection;

public DatabaseFileProvider(string connection)
{
    _connection = connection;
}

How do I dependency inject an instance of IMemoryCache to the DatabaseFileProvider class?, as one can do with e.g. a singleton:

ConfigureServices:

services.AddSingleton<AppUtils>();

AppUtils constructor:

private static IMemoryCache _cache;

public AppUtils(IMemoryCache cache)
{
    _cache = cache;
}
1

There are 1 best solutions below

6
On BEST ANSWER

Use DI services to configure MvcRazorRuntimeCompilationOptions directly

Assuming a target provider like

public class DatabaseFileProvider : IFileProvider {
    private string connection;
    private IMemoryCache cache;

    public DatabaseFileProvider(string connection, IMemoryCache cache) {
        this.connection = connection;
        this.cache = cache;
    }

    //...

}

Creating the provider with the aid of the DI services will allow for any registered dependencies to be resolved and explicitly injected using the deferred configuration delegate.

Reference Use DI services to configure options

services.AddHttpContextAccessor();
services.AddMemoryCache();

services
    .AddOptions<MvcRazorRuntimeCompilationOptions>() 
    .Configure<IServiceProvider>((options, sp) => { //<-- Configuration here
        var cs = Configuration["AppSettings:SQLConnectionString"]);
        var provider = ActivatorUtilities.CreateInstance<DatabaseFileProvider>(sp, cs);
        options.FileProviders.Add(provider);
    });

services.AddRazorPages()
    .AddRazorRuntimeCompilation(); //remove configuration delegate here

Configure allows the use of up to five services to configure options, but if a IServiceProvider is injected, the provider can be used in resolve more dependencies if needed.

If that service locator approach is not preferred, the setup can be rearranged to follow a more pure DI design.

services.AddHttpContextAccessor();
services.AddMemoryCache();

service.AddTransient<IFileProvider, DatabaseFileProvider>(sp => {
    var cs = Configuration["AppSettings:SQLConnectionString"]);
    var provider = ActivatorUtilities.CreateInstance<DatabaseFileProvider>(sp, cs);
    return provider;
});

//... register other providers if any

services
    .AddOptions<MvcRazorRuntimeCompilationOptions>() 
    .Configure<IEnumerable<IFileProvider>>((options, providers) => {
        //add all registered providers
        foreach(IFileProvider provider in providers) {
            options.FileProviders.Add(provider);
        }
    });

services.AddRazorPages()
    .AddRazorRuntimeCompilation();