Disintegrated Parts


#software-development #dotnet #email

There are several articles detailing how one can render Razor views without the whole ASP.NET Core web hosting infrastructure. Some of these articles are detailing how to use the RazorViewToStringRenderer approach as first seen in this repo. Another approach is to manually use the RazorProjectEngine to compile templates on runtime. Both approaches have their down and upsides.

The main distinction between these two approaches is the way they deal with templates. In this regard the RazorViewToStringRenderer is ideally used to render pre-compiled razor templates, while the RazorProjectEngine approach is best used in situations where dynamic compilation is required. Think for example about templates loaded from a database or another dynamic data-source.

Additionally the RazorViewToStringRenderer supports rendering complex templates more easily because the dependencies are properly compiled at compile time (no pun intended). Having missing dependencies using the RazorProjectEngine method is one of the more tricky issues to solve consistently.

This post details the instantiation of the RazorViewToStringRenderer using Asp.Net Core 3. Excellent samples for both approaches can be found in the answers to this question on StackOverflow.

Implementing the RazorViewToStringRenderer

There are a few subtle differences when migrating from .NET Core 2.x to .NET Core 3.x which make it so that the RazorViewToStringRenderer does not work anymore. Issues which might be rising is that certain (DI) dependencies are missing, or that the views cannot be found.

The best way to use the RazorViewToStringRenderer is to register it with the DI container, and let the DI container resolve all dependencies. The most straightforward way to achieve this is to use the WebHostBuilder. See the following code:

private static RazorViewToStringRenderer GetRenderer()
{
    var services = new ServiceCollection();
    
    var appDirectory = Directory.GetCurrentDirectory();

    var webhostBuilder = new WebHostBuilder().ConfigureServices(services =>
    {
        services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
        {
            options.FileProviders.Add(new PhysicalFileProvider(appDirectory));
        });

        services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

        var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
        services.AddSingleton<DiagnosticListener>(diagnosticSource);
        services.AddSingleton<DiagnosticSource>(diagnosticSource);

        services.AddLogging();

        services.AddRazorPages();

        services.AddSingleton<RazorViewToStringRenderer>();
    }).UseStartup<Startup>().Build();

    return webhostBuilder.Services.GetRequiredService<RazorViewToStringRenderer>();
}

internal class Startup
{
    public void Configure()
    {

    }
}

The alternative to the GetRenderer method is to register the RazorViewToStringRenderer within the standard DI container of your app, but this only works when you have a web application due to a dependency on an IWebHostEnvironment implementation. I have not looked into ways around this, and for portability reasons I construct my own IWebHost instance from which I pull the required dependency. For performance reasons I make sure that one instance to the RazorViewToStringRenderer instance is cached.

About the changes

One problem when migrating to .NET Core 3 is that the IRazorViewEngineOptions class does not exist anymore. It has been pointed out on GitHub that instead of the IRazorViewEngineOptions, the MvcRazorRuntimeCompilationOptions class should be used to register file providers.

The other problem there was related to the environment, which has changed with the hosting model to the generic host. These changes have been reflected in the sample code up above.

Application

One of my favourite applications for the RazorViewToStringRenderer is for use with model-to-view approach as outlined in this post (called “Rendering Razor views by supplying a model instance”). The essence there is that I use reflection to dig through the compiled view assemblies to be able to automagically render a template based on the supplied viewmodel.

Happy hacking!


No webmentions were found.