locked
How to return custom bad request response in asp.net core API RRS feed

  • Question

  • User1501362304 posted

    Hi,

    I am using Default project of .Net Core Web API. In this I have a model validated through data annotations.
    For simplicity, I have a basic model with Name required property. In Web API action I want to return custom error message as BadRequest when validation fails but it seems to be returning default format which .Net Core API sends.
    Below is error message which .Net Core API is sending.

    {
        "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
        "title": "One or more validation errors occurred.",
        "status": 400,
        "traceId": "|ab22995a-483ab490bf59f517.",
        "errors": {
            "Name": [
                "Name is required"
            ]
        }
    }   

    While I am expecting this 

    {
        "Succeeded": false,
        "Message": "One or more validation errors occurred.",
        "Errors": {
            "Name": [
                "Name is required"
            ]
        }
    }

    My custom response model is 

    public class APIResponse<T>
        {
            public bool Succeeded { get; set; }
            public T Data { get; set; }
            public string Message { get; set; }
            public Dictionary<string, List<string>> Errors { get; set; }
        }

    My custom validation filter looks like

    public class ValidateModelAttribute : ActionFilterAttribute
        {
            public override void OnActionExecuting(ActionExecutingContext context)
            {
                if (!context.ModelState.IsValid)
                {
                    APIResponse<bool> apiResponse = new APIResponse<bool>
                    {
                        Succeeded = false,
                        Message = "One or more validation errors occurred.",
                        Errors = new Dictionary<string, List<string>>()
                    };
    
                    foreach (var modelState in context.ModelState)
                    {
                        apiResponse.Errors.Add(modelState.Key, modelState.Value.Errors.Select(a => a.ErrorMessage).ToList());
                    }
    
                    //context.Result = new BadRequestObjectResult(context.ModelState);
                    context.Result = new BadRequestObjectResult(apiResponse);
                }
                //base.OnActionExecuting(context);
            }
        }

    And Core API action seems

    [Route("[action]")]
            [HttpPost]
            [ValidateModel]
            public async Task<IActionResult> RegisterUser(UserModel model)
            {
                APIResponse<bool> result = await userAccount.SaveUserAsync(model);
                return Ok(result);
            }

    Please let me know how I can send my own custom response within BadRequest?

    Thanks!!

    Monday, October 19, 2020 7:56 AM

Answers

  • User999133815 posted

    So what was causing such behavior? I've just copied your code and got the same result.

    I've noticed that default validation takes place earlier than custom. That's why default bad request body was returned.

    That helped: https://stackoverflow.com/questions/54942192/how-to-customize-error-response-in-web-api-with-net-core

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Sunday, January 3, 2021 5:56 PM

All replies

  • User1312693872 posted

    Hi,vkagrawal

    I tested your code but can't reproduce your problem, can you offer more information?

    If you just want to show three parameters, then you can delete the Data in APIResponse.

    My test procedures:

    Best Regards,

    Jerry Cai

    Tuesday, October 20, 2020 9:25 AM
  • User1501362304 posted

    I tested your code but can't reproduce your problem, can you offer more information?

    Hi Jerry, are you using .net core 3.1?

    Wednesday, October 21, 2020 8:06 AM
  • User1312693872 posted

    Hi,vkagrawal

    Yes, asp.net core 3.1 with web api.

    Best Regards,

    Jerry Cai

    Wednesday, October 21, 2020 8:10 AM
  • User1501362304 posted

    Yes, asp.net core 3.1 with web api.

    That's strange, in my case I am not even able to debug the custom validation filter while testing through postman and I leave some model errors.

    Wednesday, October 21, 2020 8:36 AM
  • User-1699898665 posted

    I have used an instance of a "Problem" class:

            [HttpPost]
            public async Task<IActionResult> Login(UserCredentials userCredentials)
            {
                string passwordHash = "";
                SecurityService securityService = new SecurityService();
                ClientTicket clientTicket = new ClientTicket();

                    if (ModelState.IsValid == false)
                    {
                        return BadRequest(ModelState);
                    }
                    else
                    {
                        //Get the user id and password date
                        await _dataService.GetUserCredentials(userCredentials);

                        if (userCredentials.UserId == 0)
                        {
                            return Problem(statusCode: 400, title: "Invalid user name");
                        }

                        //Create the password hash from the password date
                        passwordHash = securityService.GetPasswordHash(userCredentials);

                        if (string.IsNullOrEmpty(passwordHash))
                        {
                            throw new Exception("SecurityController.Login : Unable to generate password hash");
                        }

                        userCredentials.PasswordHash = passwordHash;

                        //Check if the password is valid
                        bool isPasswordValid = await _dataService.IsValidPassword(userCredentials);

                        if (isPasswordValid)
                        {
                            //Create the ticket to be returned
                            clientTicket.Username = userCredentials.Username;
                            clientTicket.Ticket = securityService.GetTicket();
                        }
                        else
                        {
                            return Problem(statusCode: 403, title: "Invalid password");
                        }
                    }

                return Ok(clientTicket);
            }

    Highlighted in bold.  There are some other examples of use on other forums, but the official mention comes here.  

    I'm not sure how to consume these using a MVC/Blazor UI, but can help with JS.

    The ProblemDetails class is the HTTP standard for handling bespoke errors (read this) and it essentially returns a simple JSON object that you need to consume with care.  Critically the response header status code goes back as 200, so you have to have to enquire into the response body :

                fetch(postURI, {
                    mode: 'cors',
                    method: 'POST',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(credentials)
                })
                    .then(response => response.json())
                    .then(body => {
                        if (body != null) {

                            if (body.status == undefined) {
                                ticket = body.ticket;

                                //Save to a cookie
                                window.sessionStorage.setItem("username",txtName.value);
                                window.sessionStorage.setItem("ticket", ticket);

                                //Set redirect address to the home page
                                if (window.location.port == "") {
                                    redir = window.location.protocol + "//" + window.location.hostname + "/";
                                }
                                else {
                                    redir = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/";
                                }
                            }
                            else {
                                success = false;
                                throw new Error(body.status + " " + body.title);
                            }

                            //Reload this form, if saved succesfully
                            if (success) {
                                window.location.replace(redir);     //Using replace means that history is overwritten and new records do not get needlessly recreated
                            }

                        }
                        else {
                            throw new Error("Login : response body is empty");
                        }
                    })
                    .catch(error => {
                        window.alert("An error occurred, please see the console for details.");
                        console.error('Login : ', error)
                    });

    I'm afraid my JS skills are somewhat basic, but hopefully you get the point.

    Kind regards, Paul

    Wednesday, October 21, 2020 10:24 AM
  • User-1699898665 posted

    Hi Jerry,

    Apologies for the digression.  What tool are you using to grab the screen actions?  

    Kind regards, paul

    Wednesday, October 21, 2020 10:29 AM
  • User1312693872 posted

    Hi,PJM8765

    The tool is 'Screen to gif'.

    Best Regards,

    Jerry Cai

    Thursday, October 22, 2020 1:35 AM
  • User999133815 posted

    So what was causing such behavior? I've just copied your code and got the same result.

    I've noticed that default validation takes place earlier than custom. That's why default bad request body was returned.

    That helped: https://stackoverflow.com/questions/54942192/how-to-customize-error-response-in-web-api-with-net-core

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Sunday, January 3, 2021 5:56 PM
  • User1501362304 posted

    Yes, while I was waiting for replies I found that it can be suppressed in startup file and it worked as expected.

    Wednesday, January 13, 2021 7:34 PM