There are many built-in model validation attributes available – such as [Required] and [Range] – which you can use to handle most validation scenarios. When these aren’t sufficient, you can create a custom validation attribute with your own validation logic. I’ll show an example of how to do that.
1 – Subclass ValidationAttribute and implement validation logic
To create a custom model validation attribute, subclass ValidationAttribute, override the IsValid() method, and implement your validation logic. Here’s an example:
using System.ComponentModel.DataAnnotations;
public class FutureDateTimeAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value is DateTimeOffset dateTimeOffset && dateTimeOffset > DateTimeOffset.Now)
return true;
return false;
}
}
Code language: C# (cs)
When a request comes in (and you’re using the attribute on a property), the framework automatically calls IsValid() with the property object during the model validation step. If it returns false, validation fails and it returns a validation error response.
Note: Since this logic gets executed on potentially every request (when the attribute is being used), keep your validation logic as light as possible.
2 – Change the error message (optional)
The default error response looks like this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "0HMHK72C15DT8:0000000B",
"errors": {
"Showtime": [
"The field Showtime is invalid."
]
}
}
Code language: JSON / JSON with Comments (json)
The default error message – “The field <property name> is invalid” – is quite vague. If you want to provide a more specific error message, one option is to override the FormatErrorMessage() method and hardcode the error message (with the property name in it):
using System.ComponentModel.DataAnnotations;
public class FutureDateTimeAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value is DateTimeOffset dateTimeOffset && dateTimeOffset > DateTimeOffset.Now)
return true;
return false;
}
public override string FormatErrorMessage(string name)
{
return $"{name} must be a future date";
}
}
Code language: C# (cs)
Hardcoding the error message like this is fine if you’re creating a very specific validation attribute.
If you’re creating a general-purpose validation attribute, you can use the ErrorMessage property to allow the error message to be specified from the outside. Overriding FormatErrorMessage() is optional.
Note: Use caution if you plan on using string.Format() with ErrorMessage. This is a recipe for runtime FormatException’s.
3 – Use the attribute
To use the attribute, stick it in on a model property:
public class MovieTicketOrder
{
public string Movie { get; set; }
public int Seats { get; set; }
[FutureDateTime]
public DateTimeOffset? Showtime { get; set; }
}
Code language: C# (cs)
Note: The validation logic is checking ‘object is DateTimeOffset’, which is false if the object is null or not a DateTimeOffset. Hence why it’s using a nullable property here.
Send a request with a value that should fail validation (at the time I’m sending this, this is a past datetime):
Code language: plaintext (plaintext)POST /movies/buytickets { "movie": "Doctor Strange in the Multiverse of Madness", "seats": 2, "showtime": "2022-05-12T13:00-04:00" }
This correctly results in a 400 – Bad Request with a validation error response:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "0HMHK8RVAMMQN:00000001",
"errors": {
"Showtime": [
"Showtime must be a future date"
]
}
}
Code language: JSON / JSON with Comments (json)
Now send a request with a value that should pass validation (at the time I’m sending this, this is a future datetime):
Code language: plaintext (plaintext)POST /movies/buytickets { "movie": "Doctor Strange in the Multiverse of Madness", "seats": 2, "showtime": "2022-05-12T19:00-04:00" }
This results in a 200 – OK response since it correctly passed validation.