Skip to content

Instantly share code, notes, and snippets.

@SteveSandersonMS
Created August 26, 2019 13:25
Show Gist options
  • Select an option

  • Save SteveSandersonMS/86a0bd3be0826f57af1ae2d3bd5fa95c to your computer and use it in GitHub Desktop.

Select an option

Save SteveSandersonMS/86a0bd3be0826f57af1ae2d3bd5fa95c to your computer and use it in GitHub Desktop.

Revisions

  1. SteveSandersonMS created this gist Aug 26, 2019.
    42 changes: 42 additions & 0 deletions Index.razor
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    @page "/"

    <h1>Hello, world!</h1>

    Welcome to your new app.

    <EditForm Model="@_editingPerson" OnValidSubmit="@OnValidSubmit" OnInvalidSubmit="@OnInvalidSubmit">
    <p>
    Name: <InputText @bind-Value="_editingPerson.Name" />
    <ValidationMessage For="@(() => _editingPerson.Name)" />
    </p>
    <p>
    Address L1: <InputText @bind-Value="_editingPerson.HomeAddress.Line1" />
    <ValidationMessage For="@(() => _editingPerson.HomeAddress.Line1)" />
    </p>
    <p>
    Address city: <InputText @bind-Value="_editingPerson.HomeAddress.City" />
    <ValidationMessage For="@(() => _editingPerson.HomeAddress.City)" />
    </p>

    <button type="submit">Submit</button>

    <MyDataAnnotationsValidator />
    <ValidationSummary />
    </EditForm>

    <pre>@_message</pre>

    @code {
    string _message = "";
    private Person _editingPerson = new Person();

    void OnValidSubmit()
    {
    _message += "Valid submit\n";
    }

    void OnInvalidSubmit()
    {
    _message += "Invalid submit\n";
    }
    }
    73 changes: 73 additions & 0 deletions MyDataAnnotationsValidator.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,73 @@
    public class MyDataAnnotationsValidator : ComponentBase
    {
    private static object _validationContextValidatorKey = new object();
    private ValidationMessageStore _messages;

    [CascadingParameter] EditContext EditContext { get; set; }

    protected override void OnInitialized()
    {
    _messages = new ValidationMessageStore(EditContext);

    // Perform object-level validation (starting from the root model) on request
    EditContext.OnValidationRequested += (sender, eventArgs) =>
    {
    _messages.Clear();
    ValidateObject(EditContext.Model);
    EditContext.NotifyValidationStateChanged();
    };

    // Perform per-field validation on each field edit
    EditContext.OnFieldChanged += (sender, eventArgs) =>
    ValidateField(EditContext, _messages, eventArgs.FieldIdentifier);
    }

    private void ValidateObject(object value)
    {
    var validationContext = new ValidationContext(value);
    validationContext.Items.Add(_validationContextValidatorKey, this);
    var validationResults = new List<ValidationResult>();
    Validator.TryValidateObject(value, validationContext, validationResults, true);

    // Transfer results to the ValidationMessageStore
    foreach (var validationResult in validationResults)
    {
    foreach (var memberName in validationResult.MemberNames)
    {
    var fieldIdentifier = new FieldIdentifier(value, memberName);
    _messages.Add(fieldIdentifier, validationResult.ErrorMessage);
    }
    }
    }

    internal static void TryValidateRecursive(object value, ValidationContext validationContext)
    {
    if (validationContext.Items.TryGetValue(_validationContextValidatorKey, out object validator))
    {
    ((MyDataAnnotationsValidator)validator).ValidateObject(value);
    }
    }

    private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
    {
    // DataAnnotations only validates public properties, so that's all we'll look for
    var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName);
    if (propertyInfo != null)
    {
    var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
    var validationContext = new ValidationContext(fieldIdentifier.Model)
    {
    MemberName = propertyInfo.Name
    };
    var results = new List<ValidationResult>();

    Validator.TryValidateProperty(propertyValue, validationContext, results);
    messages.Clear(fieldIdentifier);
    messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));

    // We have to notify even if there were no messages before and are still no messages now,
    // because the "state" that changed might be the completion of some async validation task
    editContext.NotifyValidationStateChanged();
    }
    }
    }
    11 changes: 11 additions & 0 deletions Person.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    public class Person
    {
    [Required] public string Name { get; set; }
    [ValidateRecursive] public Address HomeAddress { get; } = new Address();
    }

    public class Address
    {
    [Required] public string Line1 { get; set; }
    [Required] public string City { get; set; }
    }
    8 changes: 8 additions & 0 deletions ValidateRecursiveAttribute.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    public class ValidateRecursiveAttribute : ValidationAttribute
    {
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
    MyDataAnnotationsValidator.TryValidateRecursive(value, validationContext);
    return ValidationResult.Success;
    }
    }