Skip to content

Conversation

@AndyButland
Copy link
Contributor

@AndyButland AndyButland commented Jan 16, 2026

Description

This is an alternative attempt to resolve the issue that triggered the proposed changes in #21287, which, as per my comments in review, I don't think the approach taken there is correct as it's currently generating invalid C#.

So rather than changing the workings of the TextBuilder class that fundamentally, I've just implemented the TODO around handling nested generic types, which is currently indicated as not supported.

Change Summary

  • Fixes the string-based WriteClrType method which was using naïve comma splitting to parse generic type arguments.
  • This broke for nested generics like List<Tuple<string, string>> because splitting on , produced invalid fragments.
  • Added SplitGenericArguments helper method that tracks bracket depth to correctly split generic arguments.
  • Added unit tests for the string-based WriteClrType overload (this includes additional tests for existing functionality, as well as initially failing tests for nested generic types that now pass).
  • Renamed existing tests to clarify which overload they test (WriteClrType_Type_* vs WriteClrType_String_*)
  • Tidied up some existing code quality warnings.

Test plan

Unit tests are provided to cover the change.

Manually I've regenerated models for an existing site and don't see any changes to the generated C# code (other than expected things relating to versions in comments).

I've also used the testing approach suggested in #21287 to register a custom property value converter for the text string property, that turns the value into a list of tuples. This is correct when looking at the generated C# and also works when rendering in the front-end.

Copilot AI review requested due to automatic review settings January 16, 2026 12:11
@AndyButland AndyButland changed the title Models Builder: Fix nested generic type handling in WriteClrType Models Builder: Fix nested generic type handling in WriteClrType Jan 16, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug in the ModelsBuilder's WriteClrType method that incorrectly handled nested generic types. The string-based overload was using naive comma splitting, which broke when parsing types like List<Tuple<string, string>> because it split on all commas regardless of bracket nesting depth.

Changes:

  • Added SplitGenericArguments helper method that properly tracks bracket depth when parsing generic type arguments
  • Fixed the string-based WriteClrType method to use the new helper instead of naive string splitting
  • Renamed existing test methods to clarify which overload they test (WriteClrType_Type_* vs WriteClrType_String_*)
  • Added comprehensive unit tests for the string-based WriteClrType overload covering simple, nested, and deeply nested generics
  • Marked the parameterless TextBuilder constructor and WriteClrType(StringBuilder, Type) method as obsolete for V19
  • Applied minor code modernizations (range operators, removed unused parameter)

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs Fixed nested generic type parsing by adding SplitGenericArguments helper, marked constructors/methods as obsolete, applied code modernizations
tests/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs Renamed existing tests to clarify overloads, added comprehensive tests for string-based WriteClrType, added obsolete warnings

@kjac kjac self-requested a review January 26, 2026 14:28
Copy link
Contributor

@kjac kjac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested manually (successfully!) using a custom value converter with various property value types.

Here's an example:

using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;

namespace Umbraco.Cms.Web.UI.Custom;

public class MyTextStringValueConverter : PropertyValueConverterBase
{
    public override bool IsConverter(IPublishedPropertyType propertyType)
        => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TextBox;

    public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
        => typeof(IEnumerable<Tuple<string, IDictionary<string, List<decimal>>>>);
}

...which generates:

public global::System.Collections.Generic.IEnumerable<global::System.Tuple<string, global::System.Collections.Generic.IDictionary<string, global::System.Collections.Generic.List<decimal>>>> Title => this.Value<global::System.Collections.Generic.IEnumerable<global::System.Tuple<string, global::System.Collections.Generic.IDictionary<string, global::System.Collections.Generic.List<decimal>>>>>(_publishedValueFallback, "title");

@kjac kjac merged commit 883b6a4 into main Jan 26, 2026
25 of 26 checks passed
@kjac kjac deleted the v17/bugfix/handle-nested-generics-in-models-builder branch January 26, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants