.NET 8 — New Data Annotations

Henrique Siebert Domareski
5 min readJan 23, 2024

--

.NET 8 brings new DataAnnotations that you can use to validate minimum and maximum string lengths, minimum and maximum range for numeric values, specify allowed and denied values, and validate Base64 strings. In this article, I present how to use these new attributes.

For demonstration purposes, I created a .NET 8 Web API project, which contains a Product model class and a controller with a POST endpoint. In this project, there is a .HTTP file with examples of the request with different request body (for valid and invalid requests). To send the requests, you can make use of Swagger, Postman, or if you are using Visual Studio you can use the .HTTP file that is in the solution.

If you want to know more about the .HTTP file, check my article “.http Files in Visual Studio”.

I’m going to explain each of the new Data Annotations in the next topic, but below you can see the complete Product class with all the new attributes:

public class Product
{
[Length(2, 20)]
public string Name { get; set; }

[Range(1, 1000, MinimumIsExclusive = true, MaximumIsExclusive = false)]
public double Price { get; set; }

[AllowedValues("Computers", "Video Games")]
public string Category { get; set; }

[DeniedValues("Printers")]
public string SubCategory { get; set; }

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

This is the endpoint which I’m going to send the HTTP request to demonstrate the validations:

[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
[HttpPost]
public IActionResult Post(Product product)
{
if (!ModelState.IsValid) return BadRequest();

return Ok("Valid ModelState");
}
}

LengthAttribute

The System.ComponentModel.DataAnnotations.LengthAttribute, specifies the lowest and upper bounds for strings or collections. For example, for the property Name, the minimum value should be 2, and the maximum value should be 20:

[Length(2, 20)]
public string Name { get; set; }

This means that the Name must contain between 2 and 20 characters, otherwise will not be valid. In the example below, there are two requests with invalid values in the name property, the first one with only a single character, and the second request with 21 characters:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "a",
"price": 1000,
"category": "Computers",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "aaaaaaaaaaaaaaaaaaaaa",
"price": 1000,
"category": "Computers",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

The result will be:

{
"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 '2' and maximum length of '20'."
]
},
"traceId": "00-58c98391eb6f9120395914371234dca5-687e31c0e69b5339-00"
}

{
"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 '2' and maximum length of '20'."
]
},
"traceId": "00-feced6c3c3be0a1d80246f316ec87e59-2fef27b78e9443e5-00"
}

MinimumIsExclusive & MaximumIsExclusive

The RangeAttribute.MinimumIsExclusive and RangeAttribute.MaximumIsExclusive, specifies whether a number is included in the allowable range.

For the Price property, there is the Range attribute, which specifies that the minimum value of 1 is exclusive, which means that the minimum acceptable value is 2, and the maximum value of 100 is not exclusive, which means that the maximum value is 100:

[Range(1, 100, MinimumIsExclusive = true, MaximumIsExclusive = false)]
public double Price { get; set; }

Below there are two requests with invalid values in the price property. The first request contains the value 1, and the second contains the value 101:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1,
"category": "Computers",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1001,
"category": "Computers",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

The result will be:

{
"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 1000."
]
},
"traceId": "00-de08f4da83c21e5a16055372d2f45464-ab1c5ed37db857b1-00"
}

{
"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 1000."
]
},
"traceId": "00-de08f4da83c21e5a16055372d2f45464-5927434ebb6c06af-00"
}

Base64StringAttribute

The Base64StringAttribute validates that a string is a valid Base64 representation.

For the Description property, there is the Base64String DataAnnotation, which means that the value for Description should be a base64 string:

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

In the request below there are two requests with an invalid base64 string in the description:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1000,
"category": "Computers",
"subCategory": "Laptops",
"description": "abc#"
}

The result will be:

{
"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-de08f4da83c21e5a16055372d2f45464-95bc9914949640a3-00"
}

AllowedValuesAttribute & DeniedValuesAttribute

The AllowedValuesAttribute and DeniedValuesAttribute specifies allowed and denied values.

For the Category property, the AllowedValues are only “Laptop” and “Smartphone”, which means that any value different than these two, will not be valid:

[AllowedValues("Computers", "Video Games")]
public string Category { get; set; }

Below there is a request with an invalid value in the category property:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1000,
"category": "Smartwatch",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

The result will be:

{
"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."
]
},
"traceId": "00-de08f4da83c21e5a16055372d2f45464-226065e5054bd14b-00"
}

For the SubCategory property, the DeniedValues are only “Printers”, which means that any value different than that will be valid:

[DeniedValues("Printers")]
public string SubCategory { get; set; }

Below there is a request with an invalid value in the subCategory property:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1000,
"category": "Computers",
"subCategory": "Printers",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

The result will be:

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"SubCategory": [
"The SubCategory field equals one of the values specified in DeniedValuesAttribute."
]
},
"traceId": "00-de08f4da83c21e5a16055372d2f45464-c5c49c88ea3291b5-00"
}

Valid Request Example

Below there is an example of a valid request body, with data that attends all the specified configurations used for the Product:

POST {{DotNet8NewDataAnnotations_HostAddress}}/products
Content-Type: application/json

{
"name": "Surface Pro",
"price": 1000,
"category": "Computers",
"subCategory": "Laptops",
"description": "UHJvZHVjdCBkZXNjcmlwdGlvbg=="
}

This is the result:

Conclusion

The latest DataAnnotations in .NET allows you to have useful validation capabilities, enabling developers to easily have validations such as minimum and maximum string lengths, minimum and maximum range for numeric values, specify allowed or denied values, and validate Base64 strings. These enhancements provide a useful set of attributes for meeting specific validation requirements in applications.

This is the link for the project in GitHub: https://github.com/henriquesd/DotNet8NewDataAnnotations

If you like this demo, I kindly ask you to give a ⭐️ in the repository.

Thanks for reading!

--

--