DEV Community

Cover image for Customizing Umbraco ModelsBuilder Output
Søren Kottal
Søren Kottal

Posted on

Customizing Umbraco ModelsBuilder Output

Umbraco's ModelsBuilder is a fantastic tool that generates strongly-typed models from your document types. But what if you want to customize the generated output? Maybe you want to add property alias constants or other custom code to your models.

In this article, I'll show you how to create a custom ModelsGenerator that post-processes the generated files to add your own customizations.

The Problem

When working with Umbraco, you often need to reference property aliases as strings - for example, when using SetValue() while creating or editing content through the ContentService, or maybe you need the dynamic property access from IPublishedContent. This means scattering magic strings throughout your codebase:

// setting a property value on IContent
var title = content.SetValue("pageTitle", "New Title");

// checking for a property on IPublishedContent
var items = content.Children.Where(x => x.HasValue("featuredImage"));
Enter fullscreen mode Exit fullscreen mode

If a property alias changes, you'll need to hunt down every occurrence. It would be much better to have strongly-typed constants for each property alias, so you get compile-time safety and IntelliSense support.

ModelsBuilder does provide a way to get the property aliases from the document type, but it is rather verbose, and it requires a dependency on IPublishedContentTypeCache:

@using Umbraco.Cms.Core.PublishedCache
@inject IPublishedContentTypeCache _contentTypeCache;
@{
    var blocksPropertyAlias = ContentPage.GetModelPropertyType(_contentTypeCache, x => x.Blocks)?.Alias;
}
Enter fullscreen mode Exit fullscreen mode

The Solution

We can create a custom ModelsGenerator that runs after the standard generation and post-processes the output files. This approach:

  • Doesn't require modifying Umbraco's source code
  • Works with the existing ModelsBuilder pipeline
  • Allows for any kind of text manipulation on generated files

Here's the full implementation:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Text;
using System.Text.RegularExpressions;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Infrastructure.ModelsBuilder;
using Umbraco.Cms.Infrastructure.ModelsBuilder.Building;
using Umbraco.Extensions;

namespace MyProject.ModelsGenerators;

public class MyOwnModelsGenerator(
    UmbracoServices umbracoService,
    IOptionsMonitor<ModelsBuilderSettings> config,
    OutOfDateModelsStatus outOfDateModels,
    IHostEnvironment hostingEnvironment)
    : ModelsGenerator(umbracoService, config, outOfDateModels, hostingEnvironment), IModelsGenerator
{
    public new void GenerateModels()
    {
        base.GenerateModels();

        var modelsDirectory = config.CurrentValue.ModelsDirectoryAbsolute(hostingEnvironment);
        if (!Directory.Exists(modelsDirectory)) return;

        foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
        {
            var fileContents = File.ReadAllText(file);
            var newFileContents = AddPropertyAliasConstants(fileContents);

            if (newFileContents != fileContents)
            {
                File.WriteAllText(file, newFileContents);
            }
        }
    }

    private static string AddPropertyAliasConstants(string fileContents)
    {
        var marker = "public new const string ModelTypeAlias =";
        var index = fileContents.IndexOf(marker, StringComparison.Ordinal);
        if (index == -1) return fileContents;

        var pattern = @"\[.*ImplementPropertyType\(""([^""]+)""\)\][\s\S]*?public\s+(?:virtual\s+)?[\w\.:<>]+\s+([A-Za-z0-9_]+)\s*(?:{|=>)";
        var matches = Regex.Matches(fileContents, pattern);

        var sb = new StringBuilder();
        sb.Append("public new static class ModelPropertyAliases\n\t\t{\n");
        foreach (Match match in matches)
        {
            var alias = match.Groups[1].Value;
            var propName = match.Groups[2].Value;
            sb.Append($"\t\t\tpublic const string {propName} = \"{alias}\";\n");
        }
        sb.Append("\t\t}\n\n\t\t");

        return fileContents.Insert(index, sb.ToString());
    }
}
Enter fullscreen mode Exit fullscreen mode

How It Works

The custom generator does a few key things:

1. Inheriting from ModelsGenerator

By inheriting from ModelsGenerator and implementing IModelsGenerator, we can override the generation behavior while still using all the built-in functionality.

public class MyOwnModelsGenerator : ModelsGenerator, IModelsGenerator
Enter fullscreen mode Exit fullscreen mode

2. Post-Processing Generated Files

The GenerateModels() method first calls base.GenerateModels() to run the standard generation, then iterates through all *.generated.cs files to apply our customizations:

public new void GenerateModels()
{
    base.GenerateModels();

    var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment);
    foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
    {
        // Post-process each file...
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Adding Property Alias Constants

The AddPropertyAliasConstants method uses regex to find all properties marked with [ImplementPropertyType] and extracts both the alias and property name. It then generates a nested static class containing constants for each property:

public new static class ModelPropertyAliases
{
    public const string PageTitle = "pageTitle";
    public const string FeaturedImage = "featuredImage";
    public const string Blocks = "blocks";
}
Enter fullscreen mode Exit fullscreen mode

Registering the Custom Generator

To use your custom generator, you need to register it with Umbraco's dependency injection. Add this to your startup configuration:

builder.Services.AddSingleton<IModelsGenerator, MyOwnModelsGenerator>();
Enter fullscreen mode Exit fullscreen mode

Or if you're using a composer:

public class ModelsGeneratorComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton<IModelsGenerator, MyOwnModelsGenerator>();
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the Generated Constants

Once the models are regenerated, you can use the constants throughout your code:

// Before: magic strings
content.SetValue("pageTitle", "New Title");

// After: strongly-typed constants
content.SetValue(HomePage.ModelPropertyAliases.PageTitle, "New Title");

// Works great in LINQ queries too
var featured = content.Children
    .Where(x => x.HasValue(Article.ModelPropertyAliases.FeaturedImage));
Enter fullscreen mode Exit fullscreen mode

Now if a property alias changes, you'll get a compile-time error instead of a runtime surprise.

Final Thoughts

Customizing ModelsBuilder output gives you more control over your generated code without fighting against the built-in tooling. By post-processing the generated files, you can add compile-time safety features like property alias constants while keeping all the benefits of automatic model generation.

The approach shown here - inheriting from ModelsGenerator and processing files after generation - is clean, maintainable, and survives Umbraco upgrades since it doesn't modify any core code.

Top comments (0)