A custom validation attribute puts an application-specific input rule beside the value it checks. In a board-game API, clients choose a sort direction and column. Because those strings feed a dynamic ordering expression, the API must validate them on the server rather than trust client-side checks. section 6.1.3 section 6.1.4
To define and use a sort-direction rule:
- Derive SortOrderValidatorAttribute from ValidationAttribute. Give it an AllowedValues array containing ASC and DESC, and override IsValid to return ValidationResult.Success only for a nonempty allowed string. section 6.1.3
- Return a ValidationResult with a formatted error message when the check fails. The attribute also permits a custom ErrorMessage or a different AllowedValues array at its point of use. section 6.1.3
- Place [SortOrderValidator] on the GET action’s sortOrder parameter. A separate SortColumnValidatorAttribute accepts an entity type and checks whether the supplied name matches one of its properties. section 6.1.3
Listing 6.1 SortOrderValidatorAttribute
using System.ComponentModel.DataAnnotations;
namespace MyBGList.Attributes
{
public class SortOrderValidatorAttribute : ValidationAttribute
{
public string[] AllowedValues { get; set; } =
new[] { "ASC", "DESC" };
public SortOrderValidatorAttribute()
: base("Value must be one of the following: {0}.") { }
protected override ValidationResult? IsValid(
object? value,
ValidationContext validationContext)
{
var strValue = value as string;
if (!string.IsNullOrEmpty(strValue)
&& AllowedValues.Contains(strValue))
return ValidationResult.Success;
return new ValidationResult(
FormatErrorMessage(string.Join(",", AllowedValues))
);
}
}
}
The implementation shows the complete rule: allowed values, the IsValid override, a success result, and a formatted failure result. section 6.1.3
Attributes can also decorate properties on a request DTO. With [FromQuery], model binding fills those properties from the query string and validates each property. If a generic DTO needs its sort-column rule to depend on its type parameter, IValidatableObject can create and invoke the validator instead of placing that type-dependent rule directly on the property. section 6.1.5
With [ApiController], validation results enter ModelState. An invalid result produces an HTTP 400 response before the action runs. This protects the endpoint, but it does not automatically describe the custom rule to API clients: Swashbuckle documents many built-in attributes, not these custom ones. section 6.2.1 section 6.1.4
Make sorting rules visible in OpenAPI
Implement an IParameterFilter for each custom rule and register the filters with AddSwaggerGen. The filters inspect the attributes and add patterns to the generated parameter schemas. Check swagger.json for sortOrder’s ^ASC$|^DESC$ pattern and sortColumn’s property-name pattern. When validation attributes move from action parameters to DTO properties, the filter must inspect those properties too. section 6.1.4 section 6.1.5


