Search code examples
c#asp.netazure-functionsdeserializationhttpresponse

Is there a better way to return validation errors in ASP.NET than a 500 response when deserialization fails due to missing required fields?


To set the context:

  1. I have a yaml oapi file
  2. I used the OpenAPI generator to create C# classes
  3. Some properties have [IsRequired = true] attributes

When a required property is not included on a request, then deserialization fails and a http 500 response is returned. In this scenario, my code is never even hit.

I believe, if the problem is a missing field, a 400 response should be given and, ideally, should include a description of what the problem fields were.

The first solution to this problem that came to my mind was to simply remove the [IsRequired = true] attributes and validate on my own and that is what I'm currently doing. However, I don't love this solution because if the oapi specification is updated then I'm going to want to run the OpenAPI generator again which is going to revert it back to the original state. Also, doing this causes me to lose that metadata about which fields are required in my application which actually would be useful when doing my own validation.

  1. Is there a proper "Microsoft" way of handling this?
  2. If not, is there any standard or quasi-standard way that developers might normally handle this?

This seems like it would be a fairly common scenario, and it's just hard for me to believe that everyone is content to let the server return 500 errors simply because the default behavior for a serializer is to throw an exception when a required field is missing... and that step happens before your code is hit so you can't do anything about it.

Or is there a way for me to intercept the deserialization process to handle such exceptions?


Solution

  • When a required property is not included on a request, then deserialization fails and a 500 response is returned. In this scenario, my code is never even hit. I believe, if the problem is a missing field, a 400 response should be given and, ideally, should include a description of what the problem fields were

    Yes, its a default, 500 error comes when you don't send required parameter.

    To avoid that I use Custom Exception handling like below:

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    using System;
    
    namespace FunctionApp83
    {
        public static class Function1
        {
            public class RithwikModel
            {
                [Required(ErrorMessage = "The 'name' field is required.")]
                public string Id { get; set; }
    
            }
    
            [FunctionName("Function1")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
                ILogger log)
            {
                log.LogInformation("C# HTTP trigger function processed a request.");
    
                try
                {
                    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                    RithwikModel res = JsonConvert.DeserializeObject<RithwikModel>(requestBody);
    
                    var validationResults = new List<ValidationResult>();
                    if (!Validator.TryValidateObject(res, new ValidationContext(res), validationResults, true))
                    {
                        var errors = new BadRequestObjectResult(validationResults);
                        return errors;
                    }
    
                    string responseMessage = $"Hello Rithwik, {res.Id}. This HTTP triggered function executed successfully.";
                    return new OkObjectResult(responseMessage);
                }
                catch (Exception ex)
                {
                    log.LogError($"Error processing request: {ex.Message}");
                    return new BadRequestObjectResult("Hello Rithwik Invalid request payload");
                }
            }
        }
    }
    

    Output:

    When you don't send (any body) required parameters:

    image

    When you send some different payload other than required:

    image

    When you send required parameters:

    image