Search code examples
asp.net-corexamarin.formsdotnet-httpclient

How to properly deserialize asp.net core Model State errors to object in xamarin forms


I am having a silly problem trying to deserialize asp.net core model state errors to an object. My code is like this

For backend register method

 [HttpPost("register-user")]
        [ValidateModel]
        public async Task<IActionResult> Index(RegisterDto registerDto)
        {
            try
            {
                Data.Models.User user = mapper.Map<RegisterDto, Data.Models.User>(registerDto);
                user.LockoutEnd = DateTimeOffset.Now;
                user.Warehouse = configuration["Config:Warehouse"];
                user.SiteId = Convert.ToInt32(configuration["Config:SiteId"]);
                IdentityResult result = await userManager.CreateAsync(user, registerDto.Password);
                if (result.Succeeded)
                {
                    AddLogInformation(logger, "User created a new account with password.");

                    string token = await userManager.GenerateEmailConfirmationTokenAsync(user);
                    token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));
                    string confirmationUrl = Url.Action("Index", "EmailConfirmation",
                        new {userId = user.Id, code = token}, Request.Scheme);

                    emailSender.SendEmailAsync(registerDto.Email, "Confirm your email",
                        GetEmailConfirmationTemplate(registerDto.FirstName, registerDto.LastName,
                            confirmationUrl));

                    ApplicationRole retailPersonRole =
                        await roleManager.FindByNameAsync(RoleHelper.GetRetailUserRoleName());
                    if (retailPersonRole != null) await userManager.AddToRoleAsync(user, retailPersonRole.Name);

                    if (userManager.Options.SignIn.RequireConfirmedAccount)
                    {
                        AddLogInformation(logger, "Sent email confirmation email to user");
                        return Ok(SuccessResult(null));
                    }

                    //If confirm account is set to false
                    await signInManager.SignInAsync(user, false);
                    return Ok(SuccessResult(null));
                }
                
                // If we got this far, something failed, redisplay form
                return Ok(FailedMessage(logger, "Cannot register user at this time. Please try again later."));
            }
            catch (Exception e)
            {
                return ServerErrorJsonResult(logger, "Error while trying to register user. Error message is: " + e.Message);
            }
        }

And I am catching the model state error in action filter and returning a response as below.

public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var modelState = context.ModelState;

            if (!modelState.IsValid)
                context.Result = new  BadRequestObjectResult(new JsonResult(new {modelError = true, Errors = modelState}));
        }
    }

Now in the front-end (xamarin), I have a model to where the error should be deserialised so that I can display a proper error to user.

My register model in the front end is like this

 public class RegisterDto: BaseDto
    {
        public string FirstName { get; set; }

        public string MiddleName { get; set; }

        public string LastName { get; set; }
        public string PhoneNumber { get; set; }

        public string Email { get; set; }
        
        public string Password { get; set; }

        public string ConfirmPassword { get; set; }

        public AddressDto BillingAddress { get; set; }

        public RegisterDto Errors { get; set; }
    }

My Address Dto is like this

public class AddressDto
    {
       
        public string Address1 { get; set; }

       
        public string Address2 { get; set; }

       
        public string City { get; set; }

       
        public string PostCode { get; set; }
        
       
        public string State { get; set; }

    }

I am creating the post request in xamarin like this.

 public async Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "")
        {
            try
            {
                HttpClient httpClient = CreateHttpClient(token);

                if (!string.IsNullOrEmpty(header))
                {
                    AddHeaderParameter(httpClient, header);
                }

                var content = new StringContent(JsonConvert.SerializeObject(data));
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                HttpResponseMessage response = await httpClient.PostAsync(uri, content);

                 
                await HandleResponse(response);
                string serialized = await response.Content.ReadAsStringAsync();

                TResult result = await Task.Run(() =>
                    JsonConvert.DeserializeObject<TResult>(serialized, serializerSettings));

                return result;
            }
            catch (Exception e)
            {
                return default;
            }
        }

And finally, in the view model, I am doing this

--

userToBeRegistered is an instance of RegisterDto 
await something.PostAsync(UrlHelper.RegisterUrl, userToBeRegistered);

The serialized string output is like this

{
   "errors":{
      "Email":[
         "Email is required"
      ],
      "LastName":[
         "Last name is required"
      ],
      "Password":[
         "Password is required"
      ],
      "FirstName":[
         "First name is required"
      ],
      "PhoneNumber":[
         "Phone number is required"
      ],
      "BillingAddress.City":[
         "Suburb is required"
      ],
      "BillingAddress.State":[
         "State is required"
      ],
      "BillingAddress.Address1":[
         "Street address is required"
      ],
      "BillingAddress.PostCode":[
         "Postcode is required"
      ]
   },
   "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title":"One or more validation errors occurred.",
   "status":400,
   "traceId":"|3c2d7d70-49a6eceecbeedab8."
}

My question is how can I deserialise it to an errors object. If I keep running the above code, then I get this error.

"Unexpected character encountered while parsing value: [. Path 'errors.Email', line 1, position 20."

Can anyone help me with this?


Solution

  • Try the below method.

    Create ErrorInfor class:

    class ErrorInfor
    {
        public MyError errors { get; set; }
        public string type { get; set; }
        public string title { get; set; }
        public int status { get; set; }
        public string traceId { get; set; }
    
        public class MyError
        {
            public List<string> FirstName { get; set; }
    
            public List<string> LastName { get; set; }
            public List<string> PhoneNumber { get; set; }
    
            public List<string> Email { get; set; }
    
            public List<string> Password { get; set; }
    
            [JsonProperty("BillingAddress.City")]
            public List<string> Citiy { get; set; }
    
            [JsonProperty("BillingAddress.State")]
            public List<string> State { get; set; }
    
            [JsonProperty("BillingAddress.Address1")]
            public List<string> Address1 { get; set; }
    
            [JsonProperty("BillingAddress.PostCode")]
            public List<string> PostCode { get; set; }
        }
     }
    

    then you could get the data from your above json string.

    ErrorInfor errorInfor = JsonConvert.DeserializeObject<ErrorInfo>(json);