Dissecting a little-known feature of Umbraco: Runtime modes

tagged with Configuration Developer v10

What if there were a way to automatically set up your project with all the best practices for modern development and a safe and high-performance production environment? Let's look at a feature that "almost" fulfils this wish.

Introduction

One of the main strengths of Umbraco is the easiness of development, the fast learning curve and the fact that developers can be productive from the first moment they launch the project.

However, this comes at the cost of more work to make your project "production-ready", following the best practices until now..

Or better, until last summer, when a new feature was released in Umbraco v10.1: Runtime modes.

In this article I'll explain what they are, how they work, and why you should start using them immediately.

What are Runtime modes

Runtime modes are validators that ensure your project is configured correctly for the development experience you want to achieve. You can see it as automatically running specific health checks at startup.

Umbraco will automatically run the validators specific to the selected mode at startup. If some of the prerequisites for the mode are not met, Umbraco will not start and will throw a BootFailedException.

There are currently three modes:

  • BackofficeDevelopment, the default, targeted, as the name implies, to the default development experience done just using the Umbraco backoffice;
  • Development, the mode to use when developing using an IDE;
  • Production, the mode to use when running in production.

On top of having validators, runtime modes also turn some features on or off.

Let's now see in detail what each mode does.

BackofficeDevelopment Mode

This is the default mode if nothing is specified (and probably how most people are still using Umbraco): it allows for rapid development in the backend. Your models are automatically updated in memory, and you can edit templates directly in the backend since Razor files are compiled at runtime.

Development Mode

If you develop on Umbraco using an IDE, like Visual Studio or VS Code and the dotnet CLI, you might want to enable Development mode, since it is the recommended setup for later using Production mode in the actual production environment.

This mode turns off in-memory model generation and ensures the ModelsBuilder:ModelsMode is not set to InMemoryAuto (it won't work anyway). You should set it to either SourceCodeAuto (to have model files generated automatically) or to SourceCodeManual (in this case, you need to regenerate them via the dashboard). In either case, you are required to recompile your project for the changes done to Content Types to be available in your views.

The validation is done by the ModelsBuilderModeValidator, which checks the value of ModelsMode and fails the validation if it is set to InMemoryAuto.

Razor views can still be compiled at runtime, like in the default mode, but it's better to start configuring your project to compile them at build-time so that you are ready to set Production mode for the production environment. This ensures that every mistake in the views (syntax errors or referencing deleted properties or models) are reported during the build and not at runtime.

To enable Development mode you need to change the appsettings.json file by including these configurations:


{
    "Umbraco": {
        "CMS": {
            "Runtime": {
                "Mode" : "Development"
            },
            "ModelsBuilder":{
                "ModelsMode": "SourceCodeAuto" //or SourceCodeManual
            }
        }
    }
}

appsettings.json

Optionally, to enable build-time compilation of Razor files, you must update your csproj file, ensuring the following settings are set to true.


<PropertyGroup>
    <RazorCompileOnBuild>true</RazorCompileOnBuild>
    <RazorCompileOnPublish>true</RazorCompileOnPublish>
    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
</PropertyGroup>

csproj file

Production Mode

When the project is completed, it's time to go live in a production environment. You can now set the Production runtime mode to ensure the best performances.

To begin with, like it was already in Development mode, the in-memory generation of models is disabled. In addition, runtime compilation of Razor views is also disabled. If you didn't configure your csproj as explained a few lines above, Umbraco will not be able to find the view templates and will return a 404 (Page Not Found) error.

This mode has five additional validators that ensure your site is configured according to the best practices:

  • JITOptimizerValidator ensures the application has been built in Release mode (using the --configuration Release flag during the build).
  • UmbracoApplicationUrlValidator ensures that the UmbracoApplicationUrl is set to a valid URL. Sometimes, production environments are accessed via WAF or reverse proxy, and the first request might come from those appliances using an internal domain. The application URL will then be set to the wrong URL, and all the various functionalities that rely on it (like password reset, email notifications, keep-alive, etc.) will not work correctly.
  • UseHttpsValidator verifies that the UseHttps setting is set to true, to prevent security issues.
  • RuntimeMinificationValidator checks that the configuration of the minifier is set correctly. It should not be Timestamp but must be set to either Version (to have the cache updated at each new version of the project) or AppDomain (to update the cache at each restart of the app).
  • ModelsBuilderModeValidator ensures that model builder mode is set to Nothing when in production mode.

If any of these checks fail, the application will not start, and it will raise a BootFailedException.

It finally turns off some features in the backend since they won't work anyway with the current configuration:

  • the ModelsBuilder dashboard is removed.
  • Editing of razor views in the backend is disabled and a warning is displayed if you open one in the backend.

To enable Production runtime mode you need to update (or add, in case you don't have one yet) the appsettings.Production.json file to include the following lines.


{
  "Umbraco": {
    "CMS": {
      "Runtime": {
        "Mode": "Production"
      },
      "Global": {
        "UseHttps": true
      },
      "ModelsBuilder": {
        "ModelsMode": "Nothing"
      },
      "WebRouting": {
        "UmbracoApplicationUrl": "https://example.com/"
      },
      "RuntimeMinification": {
        "CacheBuster": "Version", //or AppDomain
        "Version": "12345678"
      }
    }
  }
}

appsettings.Production.json

How to extend or modify runtime modes

As with anything inside Umbraco, you can write your custom runtime validators (and remove the default one if you wish).

A validator is a class that implements the IRuntimeModeValidator interface which only contains the Validate method.

Inside this method, you should check the current runtime mode to decide if you want to have any validation or not and then perform any check you want. If you only want to perform validation in production mode, you can also inherit from RuntimeModeProductionValidatorBase.

These validators are added to the RuntimeModeValidators collection during the startup process.

The following code shows how to create an extension method for the UmbracoBuilder to remove one validator and add your own.


public static class CustomizeRuntimeValidatorsExtensions
{
    public static IUmbracoBuilder AddAzureOptimizationsValidator(this IUmbracoBuilder builder)
    {
        builder.RuntimeModeValidators()
            .Remove<JITOptimizerValidator>()
            .Add<AzureOptimizationsValidator>()
        return builder;
    }
}

UmbracoBuilder extension method

And then you can use it in the ConfigureService, chained to the call to AddUmbraco.


 services.AddUmbraco(_env, _config)
            .AddBackOffice()
            .AddWebsite()
            .AddAzureOptimizationsValidator()
            .Build();

Snippet of the ConfigureService method in the Startup.cs file

The custom validator makes sure that your application is configured correctly to be able to run without performance and file locking issues on Azure, by checking that the LocalTempStorageLocation is set to EnvironmentTemp.


public class AzureOptimizationsValidator : IRuntimeModeValidator
{
    private readonly IOptionsMonitor<HostingSettings> _hostingSettings;

    public AzureOptimizationsValidator(IOptionsMonitor<HostingSettings> hostingSettings)
        => _hostingSettings = hostingSettings;

    public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage)
    {
        //Only checks if runtime mode is production and it's actually a Production server
        if (runtimeMode == RuntimeMode.Production && hostingSettings.CurrentValue.LocalTempStorageLocation!=LocalTempStorage.EnvironmentTemp)
        {
            validationErrorMessage = "Change the LocalTempStorageLocation setting to EnvironmentTemp to avoid locking and performance issues.";
            return false;
        }
        validationErrorMessage = null;
        return true;
    }
}

The custom Runtime Mode validator: AzureOptimizationsValidator

Have a look at the Umbraco documentation if you want to know more about setting up your website on Azure.

Conclusions

In this article, we have looked at what runtime modes are and how you can configure Umbraco beyond the basic backend development experience.

We have also looked at how you can add your runtime validators to start enforcing your own best practices in your projects.

Do you think it's a useful feature? Will you use it in your project? Let me know your comments or questions by contacting me using the links below.