The New Data Annotations in .NET 8
The latest LTS version, .NET 8, introduces new DataAnnotations for enhanced validation of strings and numbers. This article explores these annotations and shows you how to use them i practice.
In the latest LTS version named .NET 8, we will get some new DataAnnotations
for better validation of strings and numbers. In this article, I will show you the new options we are provided with.
To be more specific we are talking DataAnnotations
to validate
- Minimum and maximum string lengths.
- Minimum and maximum range for numeric values.
- The option to specify values to deny or allow.
- Built-in validation of Base64 strings.
I have created a new .NET 8 Web API project based on the latest available template from dotnet
to demonstrate these new DataAnnotaions
in real-life. When you are done reading this post about the new data annotations in .NET 8, you will be fully up to date, and ready to implement them in your applications.
The Data Model
To give you a better understanding I have created a new model for a book where I have used the new data annotations to show you how they are used.
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
public class Book
{
public int Id { get; set; }
[Length(5, 50)]
public string? Name { get; set; }
[Base64String]
public string? Description { get; set; }
[Range(1, 250, MinimumIsExclusive = true, MaximumIsExclusive = false)]
public double Price { get; set; }
[Length(10,10)]
public string? ISBN { get; set; }
[AllowedValues("Fantasy", "Horror", "Sci-Fi")]
[DeniedValues("Cooking")]
public string? Category { get; set; }
[DeniedValues("Christian Schou")]
public string? Author { get; set; }
}
}
Great! For now, you don't have to worry about this model, I just want you to take a look at it and remember the data annotation attributes on the properties.
The Controller
With the model in place, let's create a new controller to submit our book requests to validate if the new attributes do their job. This is the code for my book controller.
using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
[HttpPost]
public IActionResult CreateBook(Book book)
{
if(!ModelState.IsValid) return BadRequest();
return Ok("All good!");
}
}
}
It's just a simple API controller in .NET inheriting from ControllerBase
- nothing special here. I have created a new POST
endpoint for creating a new book using the data model we just created.
Testing And Explanation Of Each Data Annotation
Great! Now that we have an API with a POST
endpoint for creating new books, let's test the new annotations and see how the API will respond when we trigger the validation for each of the data annotations.
The Length Data Annotation
The first attribute I have used in my Book
model is the Length
the attribute. Let's see if we can trigger it by sending the wrong payload. The property I am targeting now is this one:
[Length(5, 50)]
public string? Name { get; set; }
The System.ComponentModel.DataAnnotations.LengthAttribute
, specifies the lowest and upper bounds for strings or collections. In my example above the property Name
requires the length to be at least 5 characters and a maximum of 50 characters.
Below is the data I submitted in the request.
{
"id": 0,
"name": "str"
}
This is the response I got from the API.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Name": [
"The field Name must be a string or collection type with a minimum length of '5' and maximum length of '50'."
]
},
"traceId": "00-70943710b17477143f7a56b9a52e04b8-a994545884d57e72-00"
}
Great! That works very well. We even got a nice error message telling us the exact problem with our request. Let's move on! ✌️
The Base64String Data Annotation
Next to Name
of the book, we have the Description
property, and we would like that one to be a well-formed Base64String
.
This is the property we are targeting now.
[Base64String]
public string? Description { get; set; }
This is the payload I am submitting now.
{
"id": 0,
"name": "Data Annotations",
"description": "string"
}
The response from the API resulted in this.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Description": [
"The Description field is not a valid Base64 encoding."
]
},
"traceId": "00-697eac3539e603124c4644bed62be036-6af83866f784fafb-00"
}
Whoops - we are not getting an error message telling us that our Description
is not a valid Base64
string. Cool!
The Range Data Annotation
Now for the Price
property with the Range
attribute. This data annotation allows us to give clear instructions for the minimum and maximum allowed values. In this example, we allow a book to be as cheap as 1 and the max price to be 250.
[Range(0, 250, MinimumIsExclusive = true, MaximumIsExclusive = false)]
public double Price { get; set; }
Maybe you noted the two extra parameters in the Range
attribute? 😅 Let me explain those to you.
- The
MinimumIsExclusive = true
means that the minimum value (0) is not included in the valid range, so thePrice
must be greater than 0. - The
MaximumIsExclusive = false
means that the maximum value (250) is included in the valid range, so thePrice
can be equal to 250.
Let's test that out and see what the response we get back is. Here is the current payload for the POST request.
{
"id": 0,
"name": "Data Annotations",
"description": "aHR0cHM6Ly9jaHJpc3RpYW4tc2Nob3UuZGsv",
"price": 0
}
Here is the response from the API.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Price": [
"The field Price must be between 1 exclusive and 250."
]
},
"traceId": "00-e121ff1ee1a9247deadc3f229111181c-3650ddb2f150e2b0-00"
}
Wooha! And we even got told that the price must be between 1 exclusive, just like we specified on the property in the model!
The Allowed And Denied Values Data Annotation
The [AllowedValues]
attribute specifies the valid values for the Category
property. In this case, the Category
can be "Fantasy", "Horror", or "Sci-Fi".
The [DeniedValues]
attribute specifies the values that are not allowed for the Category
property. In this case, the Category
cannot be "Cooking". Here is the property.
[AllowedValues("Fantasy", "Horror", "Sci-Fi")]
[DeniedValues("Cooking")]
public string? Category { get; set; }
Let's see what happens if we intentionally trigger these attributes' validation. I am sending this POST request payload.
{
"id": 0,
"name": "Data Annotations",
"description": "aHR0cHM6Ly9jaHJpc3RpYW4tc2Nob3UuZGsv",
"price": 25,
"category": "Cooking"
}
This is the response from the API.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Category": [
"The Category field does not equal any of the values specified in AllowedValuesAttribute.",
"The Category field equals one of the values specified in DeniedValuesAttribute."
]
},
"traceId": "00-4f58eeadc604a2f3b1369da4cfcf1039-f21e0f80c56df026-00"
}
Yay! We get both error messages as I was expecting. We get them both because we do not send any of the allowed categories and the denied value was the exact one that is not allowed.
Final Payload
Now that we know what a payload cannot look like, I would like to show you the final one where everything is OK.
{
"id": 0,
"name": "Data Annotations",
"description": "aHR0cHM6Ly9jaHJpc3RpYW4tc2Nob3UuZGsv",
"price": 25,
"category": "Horror",
"author": "Tech with Christian"
}
The response from the API was just as expected. Status 200!
All good!
Summary 🎉
In this short .NET post about the new DataAnnotation
's introduced in .NET 8, you learned that you now can have even more power in the validation pipeline of your properties.
It's now possible to do min- and max-length limits on properties for both strings and numeric values. My personal favorite is the Base64String
validator! I can now go ahead and remove my custom implementations of this validation check in my validation behaviors.
If you learned something new, then remember to share it with your friends and colleagues, they might learn something as well. If you have any questions, please let me know in the comments below. Until next time - Happy coding! ✌️