How to copy exception from modelstate to MudBlazor errors ValidationSummary

Viewed 200

In my backend api service, I am using fluent validation. If payload request fail validation, the endpoint returns this

enter image description here

How to copy exception from modelstate to MudBlazor error string array? This is my blazor code.

@page "/marketingeventcreate"

@inject HttpClient Http
@inject NavigationManager NavManager

@using System.Text.RegularExpressions
@using System.ComponentModel.DataAnnotations
@using OnlinePlatform.Support.Domain.Entities
@using OnlinePlatform.Support.Domain.Dtos

<EditForm Model="@model" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator/>
<MudGrid>
    <MudItem xs="12" sm="7">
        <MudCard>
            <MudCardContent>                
                <MudTextField Label="Event Name" HelperText=""
                              @bind-Value="model.Name" For="@(() => model.Name)"/>
                <MudTextField Label="Remark" Class="mt-3"
                              @bind-Value="model.Remark" For="@(() => model.Remark)"/>
                <MudTextField Label="Event Date" HelperText="" Class="mt-3" InputType="InputType.Date" 
                              Mask="@(new DateMask("dd.MM.yyyy"))" DateFormat="dd.MM.yyyy" 
                              @bind-Value="model.EventDate" For="@(() => model.EventDate)" />
                <MudTextField Label="Venue" HelperText="" Class="mt-3" 
                              @bind-Value="model.Venue" For="@(() => model.Venue)" />
                <MudTextField Label="State" HelperText="" Class="mt-3" 
                              @bind-Value="model.State" For="@(() => model.State)" />
                <MudTextField Label="Start Time" HelperText="i.e. 2000" Class="mt-3"
                              @bind-Value="model.StartTime" For="@(() => model.StartTime)" />
                <MudTextField Label="End Time" HelperText="i.e. 2100" Class="mt-3"
                              @bind-Value="model.EndTime" For="@(() => model.EndTime)"  />
            </MudCardContent>
            <MudCardActions>
                <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">Submit</MudButton>
            </MudCardActions>
        </MudCard>
    </MudItem>
    <MudItem xs="12" sm="5">
        <MudPaper Class="pa-4 mud-height-full">
            <MudText Typo="Typo.subtitle2">Validation Summary</MudText>
            @if (success)
            {
                <MudText Color="Color.Success">Success</MudText>
            }
            else
            {
                <MudText Color="@Color.Error">
                    <ValidationSummary />
                </MudText>
            }
        </MudPaper>
    </MudItem>    
</MudGrid>
</EditForm>
@code {
    MarketingEvent model = new MarketingEvent();
    bool success;
    string[] errors = { };

    private async Task OnValidSubmit(EditContext context)
    {
        var mydate = new DateTime(model.EventDate.Year, model.EventDate.Month, model.EventDate.Day);

        var request = new CreateMarketingEventRequest(model.Name, model.Remark, mydate, model.Venue, 
            model.State, model.StartTime?.ToString(), model.EndTime?.ToString());


        try
        {
            var response = await Http.PostAsJsonAsync("api/marketingevent", request);

            var serverCustomErrors = await response.Content
               .ReadFromJsonAsync<Dictionary<string, List<string>>>();

            
            //for (int x = 0; x < serverCustomErrors.Count; x++)
            //{
            //    errors[x] = serverCustomErrors.ElementAt(x).Value.ToArray();
            //}
    
            response.EnsureSuccessStatusCode();

            if (response.StatusCode == System.Net.HttpStatusCode.Created)
            {
                success = true;
                NavManager.NavigateTo("/");
                StateHasChanged();    
            }            
        }
        catch (Exception ex)
        {
            
        }            
    }    
}

In my Swagger, I tested it and the response is in application/json and not application/problem+json. Example, enter image description here

2 Answers

That JSON error response - of which the picture only shows the Errors dictionary - follows the ProblemDetails spec.

A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.

You can define a similar class in your Blazor project (since you're most likely nog going to reference the Microsoft.AspNetCore.Mvc namespace).

public class ProblemDetails
{
    public IDictionary<string, IList<string>> Errors { get; set; }

    public string Title { get; set; }
}

To deserialize you make the below call.
Do make sure that the clientside JSON serializer settings match with the serverside ones to handle e.g. Pascal vs Camel casing.

response.Content.ReadFromJsonAsync<ProblemDetails>();

Such a JSON payload might look like the following.

{
    "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title":"One or more validation errors occurred.",
    "status":400,
    "traceId":"00-aeb74d510cffa1011e23109b55aae6ab-54a551c44656f2e4-00",
    "errors": 
    {
        "Name":
        [
            "Maximum of 200 characters allowed."
        ]
    }
}

To get these errors in the <ValidationSummary /> or <ValidationMessage For="..." /> tags , you'll going to have to transform your form into using an EditContext.

<EditForm EditContext="@MyEditContext">

That one is backed with e.g. a property

var MyEditContext  = new EditContex(model);

You then copy the errors in the ProblemDetails into the ValidationMessageStore.

var messageStore = new ValidationMessageStore(MyEditContext);

foreach (var error in problemDetails.Errors)
{
    messageStore.Add(MyEditContext.Field(error.Key), error.Value);
}

target.NotifyValidationStateChanged();

Reference: ASP.NET Core Blazor forms and validation

Since it was an internal error from the server we can create an customException which returns BadRequest and the error message

In your service

 if(responseMessage.StatusCode == HttpStatusCode.BadRequest)
 {
  string errorContent = await responseMessage.Content.ReadAsStringAsync();
  throw HttpException.HttpErrorMessage(errorContent);
 }

Then create a anotehr class for your custome exception

public class HttpException
{
    public static ApplicationException HttpErrorMessage(string customMessage)
    {
      return new ApplicationException(customMessage);
    }
}

Lastly in your code

try
  {

  }catch(Exception ex)
  {
   //Thsi the message that came from the server which have the internal error
  }
Related