MinValue & MaxValue attribute for properties

Learn minvalue & maxvalue attribute for properties with practical examples, diagrams, and best practices. Covers c#, attributes development techniques with visual explanations.

Enforcing Property Value Ranges with MinValue and MaxValue Attributes in C#

Hero image for MinValue & MaxValue attribute for properties

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.

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.

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.

Hero image for MinValue & MaxValue attribute for properties

Attributes promote separation of concerns in validation.