HTTP Trigger Function Request Validation

Posted by

UPDATE 2019/11/10
I’ve made this post available as a one-page guide that outlines the above approach in a more graphical format.

I’ve always struggled to find a clever way to validate HTTP requests with a JSON body, be it in ASP.NET, ASP.NET Core, or an Azure Function. I found myself tackling this issue once again on an HTTP trigger function, and decided to document my preferred approach to the problem.

Validations in ASP.NET Core

The standard way to validate requests in ASP.NET Core is to use the ModelState property of the controller. It raises binding errors when deserializing the request body to the model and checks business rules by way of validation attributes. Here’s what that looks like in a traditional ASP.NET application:

public class FlightsController : ApiController
{
    public HttpResponseMessage Post(Flight flight)
    {
        if (ModelState.IsValid)
        {
            return new HttpResponseMessage(HttpStatusCode.OK);
        }
        else
        {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }
     }
}

public class Flight
{
    public int Id { get; set; }

    [Required]
    [StringLength(4)]
    public string Departing { get; set; }

    [Required]
    [StringLength(4)]
    public string Arriving { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime Scheduled { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime Revised { get; set; }
}

But the ModelState doesn’t exist in Azure Functions, and while it might be adopted at some point down the road, I need a solution that’s going to work today.

An Alternative Needed

My ideal solution fits the following criteria:

  • Take on as few additional NuGet packages as possible.
  • Integrate seamlessly within the function trigger.
  • Easily definable validations, with support for complex rules.
  • Give detailed feedback on validation errors.
  • Easily map valid data to a POCO.

I’d love to say that I tried a few different approaches before settling on the solution below. But the reality is that my first crack at it solved most of the the concerns above. So I decided to play the hand that I was given rather than try for something better.

The Newtonsoft.Json.Schema package validates JSON documents as per the JSON Schema specification. An Azure Function project comes with Newtonsoft.Json pre-installed, so using its close cousin keeps everything nice and tidy from a packaging perspective.

A JSON Schema is nothing more than a JSON document which defines all the validations to perform. I created the schema on JsonSchema.net by providing it a sample of the JSON object to be validated. It got the boilerplate of the schema out of the way, and let me focus on handling special validations.

Let’s have a look at the function trigger’s code before reviewing how it works. I’ve removed some of the code for clarity’s sake. You can find the complete source here.

[FunctionName("Scheduler")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    [Blob("schemas/SchedulerSchema.json", FileAccess.Read)] Stream validationSchema)
{
    try
    {
        string schemaJson = await new StreamReader(validationSchema).ReadToEndAsync();
        JSchema parsedSchema = JSchema.Parse(schemaJson);

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        var parsedRequest = JObject.Parse(requestBody);

        IList<string> errorMessages = new List<string>();
        bool validRequest = parsedRequest.IsValid(parsedSchema, out errorMessages);

        if (!validRequest)
        {
            return new BadRequestObjectResult(errorMessages);
        }

        var flight = parsedRequest.ToObject<Flight>();
        await _store.Add(flight);
        return (ActionResult)new OkObjectResult(flight);
    }
    catch (Exception ex)
    {
        log.LogError(ex.Message);
        return (ActionResult)new InternalServerErrorObjectResult();
    }

}

The function trigger is invoked on every POST request. It passes in the HttpRequest as the first parameter, containing the headers, body, query string, and more. In this example, all I care about is the request body.

The second parameter to the function trigger leverages a feature called input bindings to get a stream of the JSON schema, stored on an Azure Storage blob container. An input binding is a way to get additional input data without having to do any of the heavy lifting of accessing an Azure resource.

The schema isn’t going to change very frequently, if at all. But the input binding retrieves the file stream for every invocation of the function. That’s a bit wasteful, but I’m not sure how to fix it, or if it has any impact on performance.

Let’s look at the code within the function trigger. The first thing it does is read and parse the JSON Schema. The next step is to parse the request body into a JObject, which JsonSchema can then work its magic upon. Calling JObject’s IsValid extension method tells me if the request is valid, with a list of failure messages, if any.

The function returns a 400 Bad Request if there are any failed validations. The list of errors is returned as is, but in a real production scenario it’s possible that some messages need to be made more user friendly, or obfuscated for security reasons.

A successful validation means that we can take action on the sanitized data. That’s why the code maps it from a JObject to a POCO that matches the expected model. From this POCO, the function can do whatever business logic is required on the validated model.

Wrapping Up

Overall, the approach above has worked well for me, with little effort to implement it. This solution won’t work for every scenario, but I suspect that it will work for a majority of cases.

As always, please let me know if you have any suggestions on how to improve the above code!


Did you find this post useful? If so, please consider retweeting it. If it helped you, it could help someone else too.

3 comments

  1. Wonderful solution! I would suggest storing the parsed JSchema on a static variable so you won’t have to read the file for every function call. Improves performance which is important for Azure Functions.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s