Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling API validation errors in Blazor WebAssembly

I am learning Blazor, and I have a WebAssembly client application.

I created a WebAPI at the server which does some additional validation over and above the standard data annotation validations. For example, as it attempts to write a record to the database it checks that no other record exists with the same email address. Certain types of validation can't reliably happen at the client, particularly where race conditions could produce a bad result.

The API controller returns a ValidationProblem result to the client, and Postman shows the body of the result as:

{
    "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."
        ]
    }
}

Note that the validation error is in the "errors" array in the JSON.

Back in the Blazor Client application, I have the typical HandleValidSubmit function that posts the data to the API and receives a response, as shown here:

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?
    }
}

My question is, how to best process server-side validation errors? The user experience ought to be the same as any other validation error, with the field highlighted, the validation message shown, and the summary at the top of the page.

like image 914
RogerMKE Avatar asked May 07 '20 15:05

RogerMKE


People also ask

How do you handle errors in Blazor?

When an error occurs, Blazor apps display a light yellow bar at the bottom of the screen: During development, the bar directs you to the browser console, where you can see the exception. In production, the bar notifies the user that an error has occurred and recommends refreshing the browser.

What is EditForm in Blazor?

The EditForm component is Blazor's approach to managing user-input in a way that makes it easy to perform validation against user input. It also provides the ability to check if all validation rules have been satisfied, and present the user with validation errors if they have not.


1 Answers

I ended up solving this by creating a ServerValidator component. I'll post the code here in case it is helpful for others seeking a solution to the same problem.

This code assumes you are calling a Web API endpoint that returns a ValidationProblem result if there are issues.

 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; }
    }
}

To use this new component, you will need to add the component within your EditForm:

<EditForm Model="agency" OnValidSubmit="HandleValidSubmit">
        <ServerValidator @ref="serverValidator" />
        <ValidationSummary />

        ... put all your form fields here ...

</EditForm>

Lastly, you can kick off the validation in your @code section:

@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());
        }
    }

}

In theory, this ought to allow you to bypass client validation entirely and rely on your Web API to do it. In practice, I found that Blazor performs client validation when there are annotations on your model, even if you don't include a <DataAnnotationsValidator /> in your form. However, it will still catch any validation issues at the server and return them to you.

like image 118
RogerMKE Avatar answered Sep 21 '22 04:09

RogerMKE