RadzenPickList: Validation depends on private lists, leading to validation contract violations #1496

Open
opened 2026-01-29 17:54:27 +00:00 by claunia · 0 comments
Owner

Originally created by @teneko on GitHub (Nov 24, 2024).

When using RadzenPickList I wondered that you do not implement SourceExpression, nor TargetExpression to derive the FieldIdentifier. Because of this assumed that you do not have in-built validation for Source nor Target. To my surprise The following exception popped up:

Screenshot_1

This should really not happen. As soon as you allow to bind data and validate against it or its compution, here Target and Source, then at some degrees you enter a contract, at least in view of EditContext and its implicit descendants, namely those various validator components (e..g DataAnnotationsValidator or my FluentValidation specific Validator) or those message components (e.g. ValidatorMessage or my FluentValidationMessage).

The FieldIdentifier should not include the RadzenPickList component as model of FieldIdentifier, but the constant of TargetExpression (more about the expression binding here) for example. What is much more troublesome is, that you want to validate your private list.

Possible workarounds

  1. Nop out the validation for RadzenPickList by wrapping it by a component that cascades an empty EditContext:
    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.Forms;
    using Microsoft.AspNetCore.Components.Rendering;
    using Radzen.Blazor;
    
    public class RadzenPickListEx<TItem> : RadzenPickList<TItem>
    {
        private static readonly RadzenPickListEx<TItem> s_sentinel = new();
        public static readonly EditContext s_editContext = new(s_sentinel);
    
        private readonly RenderFragment _renderFragment;
    
        public RadzenPickListEx() => _renderFragment = base.BuildRenderTree;
    
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            builder.OpenComponent<CascadingValue<EditContext>>(0);
            builder.AddComponentParameter(1, "IsFixed", true);
            builder.AddComponentParameter(2, "Value", s_editContext);
            builder.AddComponentParameter(3, nameof(CascadingValue<EditContext>.ChildContent), _renderFragment);
            builder.CloseComponent();
        }
    }
    
  2. If your FluentValidation supports either nested paths or multi models and corresponding validators, then you can nop out them via singletons:
    public class RadzenPickListDummy<T> : RadzenPickList<T>
    {
        public static readonly RadzenPickListDummy<T> Default = new();
    }
    
    public class RadzenPickListValidatorDummy<T> : AbstractValidator<RadzenPickList<T>>
    {
        public static readonly RadzenPickListValidatorDummy<T> Default = new();
    }
    

Solutions

I suggest, that you either do not validate internals or open up the validation by introducing a SourceExpression if _selectedSourceItems is equivalent to Source. If not then allow the user to to bind your _selectedSourceItems compution and use that -Expression for FieldIdentifier.Create.

EDIT: You bind _selectedSourceItems here and here you trigger the validation.

I digged into the code and found by accident a possible bug: 95672569c5/Radzen.Blazor/DataBoundFormComponent.cs (L336-L341)

The EditContext must not be the same reference. If the EditContext changes, it does not imply that the DataBoundFormComonent is getting destroyed. EditForm uses OpenRegion with the hash code of EditContext as sequence to ensure that the tree gets completely renewed, but cascading values can be shadowed, so this implementation is not bullet proof.

Originally created by @teneko on GitHub (Nov 24, 2024). When using `RadzenPickList` I wondered that you do not implement SourceExpression, nor TargetExpression to derive the FieldIdentifier. Because of this assumed that you do not have in-built validation for Source nor Target. To my surprise The following exception popped up: <img width="956" alt="Screenshot_1" src="https://github.com/user-attachments/assets/2df5aa11-a445-4be1-b832-d4e714910deb"> This should really not happen. As soon as you allow to bind data and validate against it or its compution, here Target and Source, then at some degrees you enter a contract, at least in view of EditContext and its implicit descendants, namely those various validator components (e..g DataAnnotationsValidator or my FluentValidation specific Validator) or those message components (e.g. ValidatorMessage or my FluentValidationMessage). The FieldIdentifier should not include the RadzenPickList component as model of `FieldIdentifier`, but the constant of TargetExpression (more about the expression binding [here](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-9.0#bound-field-or-property-expression-tree)) for example. What is much more troublesome is, that you want to validate your private list. ## Possible workarounds 1. Nop out the validation for RadzenPickList by wrapping it by a component that cascades an empty EditContext: ``` using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Rendering; using Radzen.Blazor; public class RadzenPickListEx<TItem> : RadzenPickList<TItem> { private static readonly RadzenPickListEx<TItem> s_sentinel = new(); public static readonly EditContext s_editContext = new(s_sentinel); private readonly RenderFragment _renderFragment; public RadzenPickListEx() => _renderFragment = base.BuildRenderTree; protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenComponent<CascadingValue<EditContext>>(0); builder.AddComponentParameter(1, "IsFixed", true); builder.AddComponentParameter(2, "Value", s_editContext); builder.AddComponentParameter(3, nameof(CascadingValue<EditContext>.ChildContent), _renderFragment); builder.CloseComponent(); } } ``` 2. If your FluentValidation supports either nested paths or multi models and corresponding validators, then you can nop out them via singletons: ``` public class RadzenPickListDummy<T> : RadzenPickList<T> { public static readonly RadzenPickListDummy<T> Default = new(); } public class RadzenPickListValidatorDummy<T> : AbstractValidator<RadzenPickList<T>> { public static readonly RadzenPickListValidatorDummy<T> Default = new(); } ``` ## Solutions I suggest, that you either do not validate internals or open up the validation by introducing a SourceExpression if `_selectedSourceItems` is equivalent to `Source`. If not then allow the user to to bind your `_selectedSourceItems` compution and use that -Expression for `FieldIdentifier.Create`. EDIT: You bind `_selectedSourceItems` [here](https://github.com/radzenhq/radzen-blazor/blob/95672569c597623e366dc8818c118ccbbca25da4/Radzen.Blazor/RadzenPickList.razor#L36) and [here](https://github.com/radzenhq/radzen-blazor/blob/95672569c597623e366dc8818c118ccbbca25da4/Radzen.Blazor/DropDownBase.cs#L1192) you trigger the validation. I digged into the code and found by accident a possible bug: https://github.com/radzenhq/radzen-blazor/blob/95672569c597623e366dc8818c118ccbbca25da4/Radzen.Blazor/DataBoundFormComponent.cs#L336-L341 The EditContext must not be the same reference. If the EditContext changes, it does not imply that the DataBoundFormComonent is getting destroyed. EditForm uses OpenRegion with the hash code of EditContext as sequence to ensure that the tree gets completely renewed, but cascading values can be shadowed, so this implementation is not bullet proof.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1496