Skip to content

Instantly share code, notes, and snippets.

@SteveSandersonMS
Created August 26, 2019 13:25
Show Gist options
  • Save SteveSandersonMS/86a0bd3be0826f57af1ae2d3bd5fa95c to your computer and use it in GitHub Desktop.
Save SteveSandersonMS/86a0bd3be0826f57af1ae2d3bd5fa95c to your computer and use it in GitHub Desktop.
@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";
}
}
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();
}
}
}
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; }
}
public class ValidateRecursiveAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
MyDataAnnotationsValidator.TryValidateRecursive(value, validationContext);
return ValidationResult.Success;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment