Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
teneko opened this issue Nov 24, 2024 · 0 comments

Comments

@teneko
Copy link

teneko commented 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:

if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
{
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
EditContext.OnValidationStateChanged -= ValidationStateChanged;
EditContext.OnValidationStateChanged += ValidationStateChanged;
}

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 up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant