我正在学习Blazor,我有一个WebAssembly客户端应用程序。
我在服务器上创建了一个WebAPI,它在标准数据注释验证之外进行一些额外的验证。例如,当它试图将一条记录写入数据库时,它会检查是否不存在具有相同电子邮件地址的其他记录。某些类型的验证无法在客户端可靠地进行,尤其是在竞争条件可能产生糟糕结果的情况下。
API控制器向客户端返回ValidationProblem结果,Postman将结果的主体显示为:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f06d4ffe-4aa836b5b3f4c9ae.",
"errors": {
"Email": [
"The email address already exists."
]
}
}
请注意,验证错误在JSON中的"errors"数组中。
回到Blazor客户端应用程序,我有一个典型的HandleValidSubmit函数,它将数据发布到API并接收响应,如下所示:
private async void HandleValidSubmit()
{
var response = await Http.PostAsJsonAsync<TestModel>("api/Test", testModel);
if (response.StatusCode != System.Net.HttpStatusCode.Created)
{
// How to handle server-side validation errors?
}
}
我的问题是,如何最好地处理服务器端验证错误?用户体验应该与任何其他验证错误相同,突出显示字段,显示验证消息,并在页面顶部显示摘要。
我最终通过创建ServerValidator组件解决了这个问题。我会在这里发布代码,以防对其他寻求相同问题解决方案的人有帮助。
此代码假设您正在调用Web API端点,如果出现问题,该端点将返回ValidationProblem结果。
public class ServerValidator : ComponentBase
{
[CascadingParameter]
EditContext CurrentEditContext { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
if (this.CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(ServerValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(ServerValidator)} " +
$"inside an EditForm.");
}
}
public async void Validate(HttpResponseMessage response, object model)
{
var messages = new ValidationMessageStore(this.CurrentEditContext);
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var body = await response.Content.ReadAsStringAsync();
var validationProblemDetails = JsonSerializer.Deserialize<ValidationProblemDetails>(body);
if (validationProblemDetails.Errors != null)
{
messages.Clear();
foreach (var error in validationProblemDetails.Errors)
{
var fieldIdentifier = new FieldIdentifier(model, error.Key);
messages.Add(fieldIdentifier, error.Value);
}
}
}
CurrentEditContext.NotifyValidationStateChanged();
}
// This is to hold the response details when the controller returns a ValidationProblem result.
private class ValidationProblemDetails
{
[JsonPropertyName("status")]
public int? Status { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("errors")]
public IDictionary<string, string[]> Errors { get; set; }
}
}
要使用这个新组件,您需要在EditForm:中添加该组件
<EditForm Model="agency" OnValidSubmit="HandleValidSubmit">
<ServerValidator @ref="serverValidator" />
<ValidationSummary />
... put all your form fields here ...
</EditForm>
最后,您可以在@code
部分开始验证:
@code {
private TestModel testModel = new TestModel();
private ServerValidator serverValidator;
private async void HandleValidSubmit()
{
var response = await Http.PostAsJsonAsync<TestModel>("api/TestModels", testModel);
if (response.StatusCode != System.Net.HttpStatusCode.Created)
{
serverValidator.Validate(response, testModel);
}
else
{
Navigation.NavigateTo(response.Headers.Location.ToString());
}
}
}
理论上,这应该允许您完全绕过客户端验证,并依靠Web API进行验证。在实践中,我发现Blazor会在模型上有注释时执行客户端验证,即使您的表单中没有<DataAnnotationsValidator />
。但是,它仍然会在服务器上发现任何验证问题并将其返回给您。
如何最好地处理服务器端验证错误?用户体验应该与任何其他验证错误相同,突出显示字段,显示验证消息,并在页面顶部显示摘要。
我不知道response
中有什么,所以我制作了一个组件的通用版本,可以满足您的需求。
-
获取EditContext 的级联参数
[CascadingParameter] public EditContext EditContext { get; set; }
-
有一个
ValidationMessageStore
来保存错误,并有一个显示错误的功能private ValidationMessageStore _messageStore; private EventHandler<ValidationRequestedEventArgs> OnValidationRequested => (s, e) => { _messageStore.Clear(); }; private EventHandler<FieldChangedEventArgs> OnFieldChanged => (s, e) => { _messageStore.Clear(e.FieldIdentifier); }; protected override void OnInitialized() { base.OnInitialized(); if (EditContext != null) { _messageStore = new ValidationMessageStore(EditContext); EditContext.OnFieldChanged += OnFieldChanged; EditContext.OnValidationRequested += OnValidationRequested; } } public override void Dispose() { base.Dispose(); if (EditContext != null) { EditContext.OnFieldChanged -= OnFieldChanged; EditContext.OnValidationRequested -= OnValidationRequested; } } private void AddFieldError(ERROR_CLASS_YOU_ARE_USING validatorError) { _messageStore.Add(EditContext.Field(validatorError.FIELD_NAME), validatorError.ERROR_MESSAGE); }
-
使用引用调用组件的函数
private async void HandleValidSubmit() { var response = await Http.PostAsJsonAsync<TestModel>("api/Test", testModel); if (response.StatusCode != System.Net.HttpStatusCode.Created) { // How to handle server-side validation errors? // You could also have a foreach or a function that receives an List for multiple fields error display MyHandleErrorComponent.AddFieldError(response.ERROR_PROPERTY); } }
https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation有一个如何处理服务器端验证错误的示例:
private async Task HandleValidSubmit(EditContext editContext)
{
customValidator.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Count() > 0)
{
customValidator.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
使用两阶段验证。
当电子邮件被输入时,连接一个事件;IsEmailUnique"方法。这为您的用户提供了实时验证信息。也许禁用";保存";按钮,直到在服务器上验证电子邮件为止。
然后,您可以像处理任何其他服务器端错误一样处理Bad Request。