处理Blazor WebAssembly中的API验证错误



我正在学习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中有什么,所以我制作了一个组件的通用版本,可以满足您的需求。

  1. 获取EditContext 的级联参数

    [CascadingParameter]
    public EditContext EditContext { get; set; }
    
  2. 有一个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);
    }
    
  3. 使用引用调用组件的函数

    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。

相关内容

  • 没有找到相关文章

最新更新