CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
2.8k stars 277 forks source link

The MemberNames returned by GetErrors() are empty #879

Closed CodingOctocat closed 1 month ago

CodingOctocat commented 1 month ago

Describe the bug

I'm using MAUI and I like to use ValidationAttribute to handle data validation, to simplify matters I made a simple WPF sample.

I customized a ValidationAttribute to check if the elements in the collection satisfy the conditions.

GetErrorsIssue.zip

public partial class ViewModel : ObservableValidator
{
    [RequiredCheckableItemValidation]
    public ObservableCollection<CheckableItem<string>> ProjectItems { get; } = [];

    public void Validate()
    {
        ValidateProperty(ProjectItems, nameof(ProjectItems));
        var errors = GetErrorsAsDict();
        // MAUI: System.Reflection.TargetInvocationException: 'Exception has been thrown by the target of an invocation.'
    }

    private Dictionary<string, IEnumerable<string>> GetErrorsAsDict()
    {
        return GetErrors()
            .GroupBy(x => x.MemberNames.First()) // Why MemberNames is Empty!
            .ToDictionary(g => g.Key, g => g.Select(x => x.ErrorMessage!));
    }
}
public class RequiredCheckableItemValidationAttribute : ValidationAttribute
{
    public RequiredCheckableItemValidationAttribute()
    {
        ErrorMessage = "Select at least one element!";
    }

    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        if (value is IEnumerable<ICheckable> items)
        {
            return items.Any(x => x.IsChecked) ? ValidationResult.Success : new ValidationResult(ErrorMessage);
        }

        return null;
    }
}
public interface ICheckable
{
    bool IsChecked { get; set; }
}

public partial class CheckableItem<T> : ObservableObject, ICheckable
{
    [ObservableProperty]
    private bool _isChecked;

    [ObservableProperty]
    private T _value;

    public CheckableItem(T value, bool isChecked = false)
    {
        Value = value;
        IsChecked = isChecked;
    }

    public override string ToString()
    {
        return $"{Value}: {IsChecked}";
    }
}

Regression

No response

Steps to reproduce

1. Open sample solution
2. Add Breakpoint at ViewModel line41
3. Debug and click test button

Expected behavior

MemberNames should Contains property names ProjectItems

Screenshots

No response

IDE and version

VS 2022

IDE version

17.9.6

Nuget packages

Nuget package version(s)

8.2.2

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item

### Tasks
CodingOctocat commented 1 month ago

Fixed: The validationContext.MemberName must be provided manually.

protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
    if (value is IEnumerable<ICheckable> items)
    {
        if (items.Any(x => x.IsChecked))
        {
            return ValidationResult.Success;
        }

        // The validationContext.MemberName must be provided manually.
        return new ValidationResult(ErrorMessage, [validationContext.MemberName!]);
    }

    return null;
}