Skip to content

Basic Request Validation

Let's continue the create book API by adding

csharp
[ApiController]
[Route("[controller]")]
public class BookController(ILogger<BookController> logger) : Controller
{
    //...

    [HttpPost]
    public IActionResult Create(CreateBookRequest request)
    {
        logger.LogInformation("BookController.Create - Request {@request}", request");
        
        //TODO create book
        var id = 1;

        logger.LogInformation("BookController.Index - Response {@response}", id);
        return Ok(id);
    }
}



The class CreateBookRequest doesn't exist, right click it or with the cursor on it press Opt + Enter and choose Create type 'CreateBookRequest'alt text

It will generate the following code

csharp
public class CreateBookRequest
{
}


With cursor on it, press Cmd + R + O and move to folder Dtos. DTO stands for data transfer objects which as the name suggests, have no logic. alt text

Add these 2 fields with validation attribute [Required]

csharp
public class CreateBookRequest
{
    [Required]
    public string Name { get; set; }
    [Required]
    public int PublicationYear { get; set; }
}


Call the API and notice the response states The Name is required while PublicationYear was not stated as required. This is because int's default value is 0. alt text \

[MoreThanZero] attribute

To validate numbers like int, long, decimal and double, we can add a custom attribute. In Utilities folder, add MoreThanZero.cs

csharp
public class MoreThanZeroAttribute : RequiredAttribute
{
    private readonly string _propertyName;

    public MoreThanZeroAttribute([CallerMemberName] string propertyName = "")
    {
        ErrorMessage = $"{propertyName} must be more than 0";
        _propertyName = propertyName;
    }

    protected override ValidationResult? IsValid(object value, ValidationContext validationContext)
    {
        switch (value)
        {
            case int and > 0:
            case decimal and > 0:
            case long and > 0:
            case double and > 0:
                return ValidationResult.Success;
            default:
                return new ValidationResult(ErrorMessage, new List<string> { _propertyName });
        }
    }
}


Back in CreateBookRequest, for PublicationYear, use [MoreThanZero]

csharp
[MoreThanZero] //<<
public int PublicationYear { get; set; }


Test the API again, PublicationYear will be validated as need to be more than zero. alt text

[DateRequired]

csharp
public class DateRequiredAttribute : RequiredAttribute
{
    private readonly string _propertyName;

    public DateRequiredAttribute([CallerMemberName] string propertyName = "")
    {
        ErrorMessage = $"{propertyName} is required";
        _propertyName = propertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is DateTime dateTime)
        {
            if (dateTime != DateTime.MinValue)
            {
                return ValidationResult.Success;
            }
        }

        return new ValidationResult(ErrorMessage, new List<string> { _propertyName });
    }
}

[GuidRequired]

csharp
public class GuidRequiredAttribute : RequiredAttribute
{
    private readonly string _propertyName;

    public GuidRequiredAttribute([CallerMemberName] string propertyName = "")
    {
        ErrorMessage = $"{propertyName} is required and must be GUID";
        _propertyName = propertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()) || !Guid.TryParse(value.ToString(), out _))
        {
            return new ValidationResult(ErrorMessage, new List<string> { _propertyName });
        }
        
        return ValidationResult.Success;

    }
}

[ListRequired]

csharp
public class ListRequiredAttribute : ValidationAttribute
{
    private readonly int _minCount;

    public ListRequiredAttribute(int minCount = 1)
    {
        _minCount = minCount;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null || (value is IList list && list.Count < _minCount))
        {
            return new ValidationResult($"{validationContext.DisplayName} must have at least {_minCount} item(s)",
                new List<string> { validationContext.MemberName });
        }

        return ValidationResult.Success;
    }
}

Released under the MIT License.