MinValue & MaxValue attribute for properties
Categories:
Enforcing Property Value Ranges with MinValue and MaxValue Attributes in C#

Explore how to define and validate property value ranges using custom MinValue and MaxValue attributes in C#, enhancing data integrity and application robustness.
In C# development, ensuring data integrity is paramount. Often, properties within your classes need to adhere to specific value constraints, such as being within a minimum and maximum range. While direct validation logic can be embedded within property setters, using custom attributes offers a more declarative, reusable, and cleaner approach. This article delves into creating and utilizing MinValue
and MaxValue
attributes to enforce such range constraints, making your code more robust and easier to maintain.
The Need for Range Validation Attributes
Consider a scenario where you have a Quantity
property that must always be between 1 and 100, or an Age
property that cannot be less than 0. Without attributes, you might implement validation directly in the setter, leading to repetitive code across multiple properties or classes. Custom attributes centralize this logic, allowing you to apply validation declaratively and separate concerns. This approach aligns with the principle of Don't Repeat Yourself (DRY) and improves code readability.
flowchart TD A[Define Property] --> B{Has MinValue/MaxValue Attribute?} B -->|No| C[Assign Value Directly] B -->|Yes| D[Retrieve Min/Max Values] D --> E{Is Value within Range?} E -->|Yes| C E -->|No| F[Throw Validation Exception] C --> G[Property Set]
Flowchart illustrating property validation logic with custom attributes.
Implementing Custom MinValue and MaxValue Attributes
To create MinValue
and MaxValue
attributes, we'll inherit from System.Attribute
and define constructors to accept the minimum or maximum value. For actual validation, we'll leverage the System.ComponentModel.DataAnnotations.ValidationAttribute
class, which provides a convenient IsValid
method to implement our custom validation logic. This allows our attributes to integrate seamlessly with existing validation frameworks, such as those used in ASP.NET Core or WPF.
using System;
using System.ComponentModel.DataAnnotations;
// MinValue Attribute
public class MinValueAttribute : ValidationAttribute
{
private readonly double _minValue;
public MinValueAttribute(double minValue)
{
_minValue = minValue;
ErrorMessage = "The field {0} must be greater than or equal to {1}.";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
if (double.TryParse(value.ToString(), out double doubleValue))
{
if (doubleValue < _minValue)
{
return new ValidationResult(string.Format(ErrorMessage, validationContext.DisplayName, _minValue));
}
}
else
{
return new ValidationResult($"The field {validationContext.DisplayName} must be a numeric value.");
}
return ValidationResult.Success;
}
}
// MaxValue Attribute
public class MaxValueAttribute : ValidationAttribute
{
private readonly double _maxValue;
public MaxValueAttribute(double maxValue)
{
_maxValue = maxValue;
ErrorMessage = "The field {0} must be less than or equal to {1}.";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
if (double.TryParse(value.ToString(), out double doubleValue))
{
if (doubleValue > _maxValue)
{
return new ValidationResult(string.Format(ErrorMessage, validationContext.DisplayName, _maxValue));
}
}
else
{
return new ValidationResult($"The field {validationContext.DisplayName} must be a numeric value.");
}
return ValidationResult.Success;
}
}
C# code for custom MinValue and MaxValue validation attributes.
MinValue
and MaxValue
into a single RangeAttribute
(which already exists in System.ComponentModel.DataAnnotations
), or create a custom BetweenAttribute
that takes both min and max values in its constructor.Applying and Using the Attributes
Once the attributes are defined, applying them to your properties is straightforward. You simply decorate the desired properties with [MinValue(value)]
and [MaxValue(value)]
. To trigger the validation, you'll typically use Validator.ValidateProperty
or Validator.ValidateObject
from the System.ComponentModel.DataAnnotations
namespace. This allows you to check property values at various points in your application lifecycle, such as before saving to a database or processing user input.
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
public class Product
{
[Required]
public string Name { get; set; }
[MinValue(1.0)]
[MaxValue(1000.0)]
public double Price { get; set; }
[MinValue(0)]
[MaxValue(100)]
public int StockQuantity { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var product1 = new Product { Name = "Laptop", Price = 1200.0, StockQuantity = 50 };
ValidateProduct(product1);
var product2 = new Product { Name = "Mouse", Price = 50.0, StockQuantity = -5 }; // Invalid StockQuantity
ValidateProduct(product2);
var product3 = new Product { Name = "Keyboard", Price = 0.5, StockQuantity = 10 }; // Invalid Price
ValidateProduct(product3);
}
private static void ValidateProduct(Product product)
{
var validationContext = new ValidationContext(product, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(product, validationContext, validationResults, validateAllProperties: true);
Console.WriteLine($"\nValidating Product: {product.Name}");
if (!isValid)
{
foreach (var result in validationResults)
{
Console.WriteLine($" Error: {result.ErrorMessage}");
}
}
else
{
Console.WriteLine(" Product is valid.");
}
}
}
Example of applying and validating properties with MinValue and MaxValue attributes.
ValidationContext
object passed to IsValid
provides useful information, such as the property's display name (validationContext.DisplayName
), which can be used to generate more user-friendly error messages.Considerations and Best Practices
While custom validation attributes are powerful, it's important to use them judiciously. For complex validation logic that involves multiple properties or external dependencies, a dedicated validation service or pattern (like the Specification pattern) might be more appropriate. However, for simple, self-contained range checks, attributes are an excellent choice. Always ensure your error messages are clear and informative to aid debugging and user experience. Also, consider supporting different numeric types (e.g., int
, decimal
) in your attributes, perhaps by using generics or by handling IComparable
.

Attributes promote separation of concerns in validation.