Introduction to validation

Inputs of an application should be validated first. This input can be sent by user or another application. In a web application, validation is usually implemented twice: in client and in the server. Client-side validation is implemented mostly for user experience. It's better to check a form first in the client and show invalid fields to the user. But, server-side validation is more critical and unavoidable.

Server side validation is generally implemented in application services. An application service method should first check (validate) input and then use it. ASP.NET Boilerplate provides a good infrastructure to automatically validate input of an application service method.

An application service method gets a DTO (Data Transfer Object) as input. ASP.NET Boilerplate has IValidate interface that can be implemented by DTOs to automatically validate them. Since IInputDto extends IValidate, you may only implement IInputDto for input DTOs to ensure validation.

Using data annotations

ASP.NET Boilerplate supports data annotation attributes. Assume that we're developing a Task application service that is used to create a task and gets an input as shown below:

public class CreateTaskInput : IInputDto
{
    public int? AssignedPersonId { get; set; }

    [Required]
    public string Description { get; set; }
}

Here, Description property is marked as Required. AssignedPersonId is optional. There are also many attributes (like MaxLength, MinLength, RegularExpression...) in System.ComponentModel.DataAnnotations namespace. See Task application service implementation:

public class TaskAppService : ITaskAppService
{
    private readonly ITaskRepository _taskRepository;
    private readonly IPersonRepository _personRepository;

    public TaskAppService(ITaskRepository taskRepository, IPersonRepository personRepository)
    {
        _taskRepository = taskRepository;
        _personRepository = personRepository;
    }

    public void CreateTask(CreateTaskInput input)
    {
        var task = new Task { Description = input.Description };

        if (input.AssignedPersonId.HasValue)
        {
            task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
        }

        _taskRepository.Insert(task);
    }
}

As you see, no validation code is written since ASP.NET Boilerplate does it automatically. ASP.NET Boilerplate also checks if input is null. Throws AbpValidationException if so. So, you don't have to write null-check code (guard clause). It also throws same exception if any of the input properties are invalid.

This machanism is similar to ASP.NET MVC's validation but notice that an application service class is not derived from Controller, it's a plain class and can work out of a web application.

Custom validation

If data annotations are not sufficient for your case, you can implement ICustomValidate interface as shown below:

public class CreateTaskInput : IInputDto, ICustomValidate
{
    public int? AssignedPersonId { get; set; }

    public bool SendEmailToAssignedPerson { get; set; }

    [Required]
    public string Description { get; set; }

    public void AddValidationErrors(List<ValidationResult> results)
    {
        if (SendEmailToAssignedPerson && (!AssignedPersonId.HasValue || AssignedPersonId.Value <= 0))
        {
            results.Add(new ValidationResult("AssignedPersonId must be set if SendEmailToAssignedPerson is true!"));
        }
    }
}

ICustomValidate interface declares AddValidationErrors method to be implemented. Here, we have a SendEmailToAssignedPerson property. If it's true, AssignedPersonId should be supplied, else it can be null. We must add ValidationResult objects to results list if there are validation errors.

Normalization

We may need to perform an extra operation to arrange DTO parameters after validation. ASP.NET Boilerplate defines IShouldNormalize interface that has Normalize method for that. If you implement this interface, Normalize method is called just after validation (before method call). Assume that our DTO gets a Sorting direction. If it's not supplied, we want to set a default sorting:

public class GetTasksInput : IInputDto, IShouldNormalize
{
    public string Sorting { get; set; }

    public void Normalize()
    {
        if (string.IsNullOrWhiteSpace(Sorting))
        {
            Sorting = "Name ASC";
        }
    }
}