In a previous post, I illustrate how one might develop a very simple contact form endpoint using Azure Functions. Continuing from previous examples, this article is focused specifically on implementing a validation pattern that can be used across Azure Functions HTTP triggers.

For those of you who have landed here with the strict interest of observing an Azure Functions validation example, the context of my previous posts shouldn't be necessary for you glean an understanding. Don't bother reading them.

Why Validate?

Firstly, let's quickly discuss the reasons for implementing server-side validation. After all, we've already programmed our HTML form to validate the user's input. Isn't that enough?

As I'm sure you're aware -- no -- client-side validation is not sufficient. It's considered a best practice to validate payloads on the backend. We should not assume that the current client (our HTML form) will be our only client. Perhaps we won't always have control over the consumer of our endpoints; a third-party application may one day consume our HTTP triggers. A tech-savvy user can easily circumvent a client application (our HTML form, in this case) and invoke our HTTP trigger directly.

In order to guarantee that our function always behaves as expected, we must validate the inputs before processing them.

Out of the Box

So what tools does Azure Functions provide for us to perform validation in our HTTP trigger functions?

Nothing.

For those of you coming from a .NET background, I'm sure you're familiar with the built-in validation attributes and the IValidatableObject interface. None of that is included in your Azure Functions by default. We're on our own.

That's fine with me. I'm content with stripping back some of the opinionated framework APIs in favor of filling those gaps via NuGet packages or custom implementation.

Stupid Validation

Before we start implementing validation for our endpoint, let's take a look at where we left off from our previous post.

[FunctionName("contact")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var json = await req.ReadAsStringAsync();
    var form = JsonConvert.DeserializeObject<ContactForm>(json);

    log.LogInformation($"Payload: {form.Name} - {form.Email} - {form.Message}");

    var sender = new MailgunSender(
        "sandboxcf5f41bbf2f84f15a386c60e253b5fe9.mailgun.org", // Mailgun Domain
        "key-8d32c046d7f14ada8d5ba8253e3e30de" // Mailgun API Key
    );

    Email.DefaultSender = sender;

    var email = Email
        .From("[email protected]")
        .To(form.Email)
        .Subject("Contact Form Submission")
        .Body($"{form.Name} submitted the following message: \n\n{form.Message}");

    var response = await email.SendAsync();

    if (!response.Successful)
    {
        throw new Exception($"Failed to send mail. {string.Join(", ", response.ErrorMessages)}");
    }

    return new OkResult();
}
public class ContactForm
{
    public string Name { get; set; }

    public string Email { get; set; }

    public string Message { get; set; }
}

We deserialize the JSON request body into our ContactForm model and then we immediately access the properties of that model to build and send an email. Never do we check those properties for validity. Are they populated? Is there actually a valid email address string in the Email property? Who knows...

Now let's see how we could accomplish minimal validation without leveraging any frameworks or libraries.

[FunctionName("contact")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var json = await req.ReadAsStringAsync();
    var form = JsonConvert.DeserializeObject<ContactForm>(json);

    if (!string.IsNullOrWhiteSpace(form.Name))
    {
        return new BadRequestObjectResult(new { Error = "Name is required." });
    }

    if (!string.IsNullOrWhiteSpace(form.Email))
    {
        return new BadRequestObjectResult(new { Error = "Email is required." });
    }

    if (!Regex.IsMatch(form.Email, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"))
    {
        return new BadRequestObjectResult(new { Error = "Email is not a valid email address." });
    }

    if (!string.IsNullOrWhiteSpace(form.Message))
    {
        return new BadRequestObjectResult(new { Error = "Message is required." });
    }

    if (form.Message.Length > 100)
    {
        return new BadRequestObjectResult(new { Error = "Message cannot have more than 100 characters." });
    }

    // Now I know that all the inputs are valid. Proceed with processing.

    log.LogInformation($"Payload: {form.Name} - {form.Email} - {form.Message}");

    var sender = new MailgunSender(
        "sandboxcf5f41bbf2f84f15a386c60e253b5fe9.mailgun.org", // Mailgun Domain
        "key-8d32c046d7f14ada8d5ba8253e3e30de" // Mailgun API Key
    );

    Email.DefaultSender = sender;

    var email = Email
        .From("[email protected]")
        .To(form.Email)
        .Subject("Contact Form Submission")
        .Body($"{form.Name} submitted the following message: \n\n{form.Message}");

    var response = await email.SendAsync();

    if (!response.Successful)
    {
        throw new Exception($"Failed to send mail. {string.Join(", ", response.ErrorMessages)}");
    }

    return new OkResult();
}

So now we have an ugly pile of if blocks near the top of our function to help determine the validity of the inputs. If one of the properties is found to be invalid, we immediately return a BadRequestObjectResult with a description of the validation infraction. This ends any further processing of our function and sends an HTTP 400 response back to the client.

If this is going to be the only Azure Function you ever create and you have no plans of expanding your API, perhaps this solution is good enough for you. It is far from elegant, but it does the job.

One could come up with many ways to improve on this validation implementation. Maybe you'd like to create a method in your model to handle all of this logic so you can simply call form.IsValid() from our function code. That's a reasonable approach and I'm sure there are dozens more.

Fluent Validation

The path I have been choosing in my Azure Functions projects is to leverage the FluentValidation library. This is an open-source library that can be added to your project via NuGet. It allows you to quickly build validation rules for your models without having to add any behavior to your POCO classes.

Take a look at what a FluentValidation validator class might look like for our ContactForm model.

public class ContactFormValidator : AbstractValidator<ContactForm>
{
    public ContactFormValidator()
    {
        RuleFor(x => x.Name).NotEmpty();
        RuleFor(x => x.Email).NotEmpty().EmailAddress();
        RuleFor(x => x.Message).NotEmpty().MaximumLength(100);
    }
}

Above, we've implemented a validator class that is responsible for defining the validation rules for our ContactForm model. As we add more functions and models, we can implement the respective validator class. Depending on the size and structure of the project, I often organize these class files within a /Models and /Validators directory.

In the above example, we are utilizing the base RuleFor() method to define all of the validation rules for our properties. This is considered a fluent API because almost every method returns the same type (IRuleBuilderInitial), which allows us to chain method calls together in one readable line of code. This is a small blip of declarative programming style sneaking its way into C# (a primarily imperative language). Many are familiar with the System.Linq library, which promotes a similar style of chaining.

The chaining style also makes the rule APIs in this library quite discoverable with the help of Visual Studio's IntelliSense or any other modern IDE's autocomplete features. Simply type a dot after your RuleFor() and all of your available options will appear with full documentation.

If a method doesn't exist for a validation rule you require, custom validation logic can be added with .Must() or .Custom(). There is a lot you can do with the library. Spend some time exploring the documentation if you have a lot of validating to do in your project.

So, now that we have a validator class that defines the validation rules for our ContactForm model, we must wire it up to our function. This is where our solution falls short of being perfectly DRY. When using FluentValidations with ASP.NET MVC, ASP.NET Web API, and ASP.NET Core the validation rules are automatically applied early in the request lifecycle, before the invocation of any controller actions. Unfortunately, it's not that streamlined with Azure Functions. We have to explicitly trigger validation, like so:

[FunctionName("contact")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var validator = new ContactFormValidator();
    var json = await req.ReadAsStringAsync();
    var form = JsonConvert.DeserializeObject<ContactForm>(json);

    log.LogInformation($"Payload: {form.Name} - {form.Email} - {form.Message}");

    var validationResult = validator.Validate(form);

    if (!validationResult.IsValid)
    {
        return new BadRequestObjectResult(validationResult.Errors.Select(e => new {
            Field = e.PropertyName,
            Error = e.ErrorMessage
        }));
    }

    // Everything above is likely to be repeated in other functions :(.

    var sender = new MailgunSender(
        "sandboxcf5f41bbf2f84f15a386c60e253b5fe9.mailgun.org", // Mailgun Domain
        "key-8d32c046d7f14ada8d5ba8253e3e30de" // Mailgun API Key
    );

    Email.DefaultSender = sender;

    var email = Email
        .From("[email protected]")
        .To(form.Email)
        .Subject("Contact Form Submission")
        .Body($"{form.Name} submitted a contact form with the following message: \n\n{form.Message}");

    var response = await email.SendAsync();

    if (!response.Successful)
    {
        throw new Exception($"Failed to send mail. Errors: {string.Join(", ", response.ErrorMessages)}");
    }

    return new OkResult();
}

As you can see, we're likely going to have to repeat some form of this boilerplate validation code in every HTTP trigger function that we implement. We'll address that later.

Let's test our server-side validation to see if it's working. I like to use Postman, but feel free to use any HTTP/REST client you are comfortable with. Here's the payload I'm posting to our endpoint:

{
	name: '',
	email: 'testinggmail.com',
	message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mattis enim ut tellus elementum sagittis vitae et leo. Ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant morbi. Platea dictumst quisque sagittis purus sit amet volutpat consequat. Morbi tristique senectus et netus et malesuada fames ac turpis. Senectus et netus et malesuada fames ac turpis. Imperdiet sed euismod nisi porta lorem mollis. Posuere urna nec tincidunt praesent semper feugiat nibh sed. Vulputate mi sit amet mauris commodo. Augue neque gravida in fermentum et. Id aliquet risus feugiat in ante metus. Blandit cursus risus at ultrices mi tempus. Neque egestas congue quisque egestas diam. Rutrum tellus pellentesque eu tincidunt tortor aliquam nulla facilisi cras. Imperdiet nulla malesuada pellentesque elit eget.'
}

This is the 400 response I get back from our endpoint:

[
    {
        "field": "Name",
        "error": "'Name' must not be empty."
    },
    {
        "field": "Email",
        "error": "'Email' is not a valid email address."
    },
    {
        "field": "Message",
        "error": "The length of 'Message' must be 100 characters or fewer. You entered 872 characters."
    }
]

Notice that a helpful, relevant error message is automatically generated by the FluentValidation library. If you'd like to customize this message you can use the .WithMessage() method like so:

RuleFor(x => x.Email)
    .NotEmpty().WithMessage("Email Required!")
    .EmailAddress().WithMessage("Invalid Email!");

The FluentValidation library also comes with a set of test helpers (using FluentValidation.TestHelper;) to make unit testing your validator classes a simple task. Here's what some unit tests (using xUnit) might look like for our ContactFormValidator class:

public class ContactFormValidatorTests
{
    [Theory]
    [InlineData(null, null, null)]
    [InlineData("", "", "")]
    [InlineData(" ", " ", " ")]
    public void FieldsEmpty_ReturnsValidationErrors(string name, string email, string message)
    {
        var validator = new ContactFormValidator();

        validator.ShouldHaveValidationErrorFor(x => x.Name, name);
        validator.ShouldHaveValidationErrorFor(x => x.Email, email);
        validator.ShouldHaveValidationErrorFor(x => x.Message, message);
    }

    [Theory]
    [InlineData("missing-at-sign.com")]
    [InlineData("[email protected]")]
    [InlineData("[email protected]")]
    [InlineData("@missing-username.net")]
    public void EmailIsInvalid_ReturnsValidationErrors(string email)
    {
        var validator = new ContactFormValidator();

        validator.ShouldHaveValidationErrorFor(x => x.Email, email);
    }

    [Fact]
    public void MessageIsOver100Chars_ReturnsValidationErrors()
    {
        var validator = new ContactFormValidator();

        validator.ShouldHaveValidationErrorFor(x => x.Message, new string('a', 101));
    }

    [Fact]
    public void MessageIsUnder100Chars_ValidationSucceeds()
    {
        var validator = new ContactFormValidator();

        validator.ShouldNotHaveValidationErrorFor(x => x.Message, new string('a', 99));
    }

    [Fact]
    public void MessageIs100Chars_ValidationSucceeds()
    {
        var validator = new ContactFormValidator();

        validator.ShouldNotHaveValidationErrorFor(x => x.Message, new string('a', 100));
    }

    [Theory]
    [InlineData("FirstName LastName", "[email protected]", "My test message!")]
    public void ContactFormIsValid_ValidationSucceeds(string name, string email, string message)
    {
        var validator = new ContactFormValidator();

        validator.ShouldNotHaveValidationErrorFor(x => x.Name, name);
        validator.ShouldNotHaveValidationErrorFor(x => x.Email, email);
        validator.ShouldNotHaveValidationErrorFor(x => x.Message, message);
    }
}

Notice my frequent use of the helper methods ShouldHaveValidationErrorFor() and ShouldNotHaveValidationErrorFor(). Hopefully this inspires you to cover your validation rules with some unit tests so you can be certain that they're correct.

Less Boilerplate

As for the repeated deserialization and validation code, I've opted to extract some of that into extension methods. Here's what the end result looks like for our function:

[FunctionName("contact")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    var form = await req.GetJsonBody<ContactForm, ContactFormValidator>();

    if (!form.IsValid)
    {
        log.LogInformation($"Invalid form data.");
        return form.ToBadRequest();
    }

    var sender = new MailgunSender(
        "sandboxcf5f41bbf2f84f15a386c60e253b5fe9.mailgun.org", // Mailgun Domain
        "key-8d32c046d7f14ada8d5ba8253e3e30de" // Mailgun API Key
    );

    Email.DefaultSender = sender;

    var email = Email
        .From("[email protected]")
        .To(form.Value.Email)
        .Subject("Contact Form Submission")
        .Body($"{form.Value.Name} submitted the following message: \n\n{form.Value.Message}");

    var response = await email.SendAsync();

    if (!response.Successful)
    {
        throw new Exception($"Failed to send mail. {string.Join(", ", response.ErrorMessages)}");
    }

    return new OkResult();
}

This is the code we're using to accomplish this:

public static class HttpRequestExtensions
{
    /// <summary>
    /// Returns the deserialized request body with validation information.
    /// </summary>
    /// <typeparam name="T">Type used for deserialization of the request body.</typeparam>
    /// <typeparam name="V">
    /// Validator used to validate the deserialized request body.
    /// </typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public static async Task<ValidatableRequest<T>> GetJsonBody<T, V>(this HttpRequest request)
        where V : AbstractValidator<T>, new()
    {
        var requestObject = await request.GetJsonBody<T>();
        var validator = new V();
        var validationResult = validator.Validate(requestObject);

        if (!validationResult.IsValid)
        {
            return new ValidatableRequest<T>
            {
                Value = requestObject,
                IsValid = false,
                Errors = validationResult.Errors
            };
        }

        return new ValidatableRequest<T>
        {
            Value = requestObject,
            IsValid = true
        };
    }

    /// <summary>
    /// Returns the deserialized request body.
    /// </summary>
    /// <typeparam name="T">Type used for deserialization of the request body.</typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public static async Task<T> GetJsonBody<T>(this HttpRequest request)
    {
        var requestBody = await request.ReadAsStringAsync();

        return JsonConvert.DeserializeObject<T>(requestBody);
    }
}

The generic GetJsonBody() method conveniently handles both the deserialization and the validation of the request for us.

public static class ValidationExtensions
{
    /// <summary>
    /// Creates a <see cref="BadRequestObjectResult"/> containing a collection
    /// of minimal validation error details.
    /// </summary>
    /// <param name="result"></param>
    /// <returns></returns>
    public static BadRequestObjectResult ToBadRequest(this ValidationResult result)
    {
        return new BadRequestObjectResult(result.Errors.Select(e => new {
            Field = e.PropertyName,
            Error = e.ErrorMessage
        }));
    }
}

The ToBadRequest() method conveniently projects the validation errors into a reasonable 400 response body.

public class ValidatableRequest<T>
{
    /// <summary>
    /// The deserialized value of the request.
    /// </summary>
    public T Value { get; set; }

    /// <summary>
    /// Whether or not the deserialized value was found to be valid.
    /// </summary>
    public bool IsValid { get; set; }

    /// <summary>
    /// The collection of validation errors.
    /// </summary>
    public IList<ValidationFailure> Errors { get; set; }
}

ValidatableRequest is the type returned from the GetJsonBody() extension method, which contains contextual validation information and the deserialized data we want to work with in our function.

Final Thoughts

That concludes my overview of validation in Azure Functions using the FluentValidation library. I hope my extension methods are helpful in getting you started and reducing boilerplate.

When I come across a better way to extract the deserialization/validation logic, I'll come back to this article and make a revision. If you beat me to a better solution, please let me know in a comment below and I'll update this post.

Keep an eye out for my next post in this series. I'm going to tackle setting up dependency injection in Azure Functions. We'll work on extracting all business logic into testable, single-purpose "service" classes.