1

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;
}
Asons
  • 84,923
  • 12
  • 110
  • 165
  • Configure the options via DI https://learn.microsoft.com/en-us/dotnet/core/extensions/options#use-di-services-to-configure-options – Nkosi Jun 11 '21 at 20:02
  • @Nkosi -- If you could provide an answer with a sample how that should look like, where I get both Options and IMemoryCache injected into the DatabaseFileProvider, I would be very grateful, as I can't see how that should be done (I am a really newbie when it comes to ASP.NET Core and its ways to be set up/configured). – Asons Jun 11 '21 at 23:21

1 Answers1

5

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();
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I have a concrete IFileProvider that has dependencies on scoped services. If I use this solution, I get the error "Cannot resolve scoped service {path to my class} from root provider." How can we get around this? – ctorx Dec 17 '21 at 16:17
  • 1
    @ctorx which approach did you use from the solution? The first or the second? – Nkosi Dec 17 '21 at 16:38
  • I actually tried both with no luck. I'm essentially needing the IFileProvider implementation to be able to resolve a scoped instance of an Entity Framework DBContext so that I can use it to fetch razor views from a context. – ctorx Dec 18 '21 at 17:58
  • 1
    @ctorx Then create a scope via the provider to resolve the context. – Nkosi Dec 19 '21 at 02:29
  • Thank you. That's exactly what I needed. – ctorx Dec 20 '21 at 16:52
  • None of my razor views compile when I do this, I get errors that basic stuff like System is not found – jjxtra Mar 14 '23 at 20:40