Defensive Programming 101: Validation

Defensive Programming 101: Validation

·

7 min read

Introduction

Validation is an essential part of any system that takes external input. It helps us protect our application by ensuring the data it receives is expected and can be processed. A strong validation setup for our application provides the following benefits:

  • Improved security standing by restricting the data accepted
  • A better user experience, as bad input can be reported back to them
  • A standard way to enforce rules that avoids spreading them through the application

Layers of Validation

Input Validation

Input validation is an early line of defence in your setup. It should be focused on ensuring the shape of data received is as expected and not so much on whether the content matches up to business rules. When working on ASP.NET applications, this step can be plugged into the request flow such that validation is automatically performed for you. This limits the amount of processing required in a scenario where the given data is invalid.

Business Rule Validation

There are often rules you want your data to follow that go beyond basic input validation. For example, consider an application where a user can claim a reward once a month and you need to ensure the application prevents a user from claiming more frequently. To implement such a rule, we will need to store when the user last claimed their reward and decide if they are able to do so again. This presents itself as a business driven restriction rather than a requirement on the shape of the data received, and so it should not be handled by input validation.

Why the Separation?

There are three main reasons:

  1. Speed: Validating the shape of data is something that will happen with every request and may fail several times for the same caller. It should fail as fast as possible and require minimal processing.
  2. Cost: If you rely on external services that work on costing per operation you may end up using operations in situations where the shape of data is invalid anyway. Alternatively, if you rely on your own services you may end up making unnecessary calls.
  3. Separation of Concerns: This will vary from application to application but in many cases the shape of the data is not related to the business rules you wish to apply to it. Performing all validation in one step may require you to pull in a lot of business context when deciding what and how to validate against business rules.

However, if you feel performing both validation steps together is right for your application, then I would recommend performing any business rule validation only if the input validation has not already failed. This should tackle the issues outlined in reason 1 and 2.

Other Layers

While the two layers outlined above will cover the majority of use cases, there are situations where you may find yourself needing to validate at other stages. For example, you may wish to validate:

  • The data returned by services you call. This can offer extra assurances around the quality of the data you are using within your application
  • The data your application is about to commit to storage. This can act as a last line of defence against storing bad data no matter what happens prior

A Note on User Experience

For the best user experience and where possible, you should implement the same validation rules on the frontend as the backend. This provides a quick feedback loop for users as the frontend will stop requests that are known to be invalid from being sent to the backend. However, as frontend validation can easily be circumvented you should not use it instead of backend validation, only in addition to.

Validation in Practice

As ASP.NET applications have a validation setup built in, they do not require much work in order to get input validation up and running. If you are implementing additional validation beyond input, you will need to explicitly call validators to get a result. Below are some of the most common ways to add validation to an application; all of them will work with the ASP.NET application validation setup.

Data Annotations

Data Annotations are a quick and simple way to start producing validation errors. You apply data annotation attributes to the properties of a class you wish to be validated. If you wish to do this outside of the default ASP.NET application validation setup, you can use the Validator class and target it at the object to validate.

Pros

  • Quick and simple setup
  • Low learning curve
  • Built in annotations cover a lot of basic validation requirements
  • Added to data contracts, so extra classes are not needed

Cons

  • As the validation is baked into the contract it is hard to modify what should be considered valid on the fly
  • The built in annotations struggle with more complex scenarios but custom ones can be made
  • As the validation rules are tied to contracts it is harder to reuse a set of rules
  • Testing requires some additional setup to fire off the validation

Example

Annotated contract:

public class DataContract
{
    [Required]
    [StringLength(20)]
    public string PropertyOne { get; set; }
}

Manual validation using the static Validator class:

var objectToValidate = new DataContract { PropertyOne = "Test" };
var validationErrors = new List<ValidationResult>();
var validationContext = new ValidationContext(objectToValidate);

var validationResult = Validator.TryValidateObject
(
    objectToValidate, 
    validationContext, 
    validationErrors
);

IValidatableObject

Implementing this interface on your data contract offers a way to validate it at any time without the need of a supporting class. It allows for more control over the validation process and the results it creates.

Pros

  • Quick and simple way to add more complex validation to your contracts compared to data annotations
  • Gives you a method of validation which is built into the contract class and does not require the use of additional classes
  • Allows for more control over the results produced by the validation process

Cons

  • As the validation is baked into the contract it is hard to modify what should be considered valid on the fly
  • Hard to reuse validation rules without repeating them

Example

Data contract implementing the interface:

public class DataContract : IValidatableObject
{
    public string PropertyOne { get; set; }
    public string PropertyTwo { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var results = new List<ValidationResult>();

        if (PropertyOne == "Foo" && PropertyTwo != "Bar")
            results.Add(new ValidationResult("When PropertyOne is Foo, PropertyTwo must be Bar"));

        return results;
    }
}

Fluent Validation

This is a powerful NuGet package that works well for both the ASP.NET application validation setup and any custom needs. It uses separate validator classes which are related to the class you wish to validate. It provides a wide range of out of the box validation rules, as well as easy ways to create your own rules. Furthermore, it is built in a way that makes it simple to reuse rules for different validation scenarios.

Pros

  • Provides a flexible and configurable way to implement validation that works well for simple to complex scenarios
  • Sets of rules can be grouped and executed by name to change your validation based on context
  • Makes it easy to reuse whole validators or just particular rules
  • Testing does not require any more consideration than testing your average class

Cons

  • A slightly steeper learning curve when starting out
  • Requires the creation of more classes as each class to validate will typically need its own validator
  • Needs a little bit more setup than the other built in options

Example

Data contract:

public class DataContract
{
    public string PropertyOne { get; set; }
}

Validator:

public class DataContractValidator : AbstractValidator<DataContract>
{
    public DataContractValidator()
    {
        RuleFor(contract => contract.PropertyOne).MaximumLength(20);
    }
}

Conclusion

Choosing a method of validation is down to your preference and the specifics of your project, as all of the above will work well in the majority of scenarios. Data annotations are easy to understand and get you going quickly, which makes them great when starting out. If they do not satisfy your use case, IValidatableObject implementations will allow you some more control over the validation process. If you find yourself wanting a more configurable and flexible way to perform validation then you should take a look at the Fluent Validation package, which is my method of choice.

Further Reading