Validation attributes
editThe Meziantou.Framework.DataAnnotations namespace contains two validation attributes. They work wherever data annotations are checked: Validator.TryValidateObject, ASP.NET Core model validation, or options validation with ValidateDataAnnotations.
Absolute URIs
[AbsoluteUri] checks that a string property holds an absolute URI:
public sealed class MyModel
{
[AbsoluteUri]
public string? Url { get; set; }
}var attribute = new AbsoluteUriAttribute();
Assert.True(attribute.IsValid("https://www.example.com/"));
Assert.False(attribute.IsValid("images/logo.png"));
// null and values that are not strings are valid: add [Required] to reject null
Assert.True(attribute.IsValid(null));
var result = attribute.GetValidationResult("images/logo.png", new ValidationContext(new object()));
Assert.NotNull(result);
Assert.Equal("Uri 'images/logo.png' is not an absolute URI", result.ErrorMessage);- A string is valid when
Uri.TryCreate(value, UriKind.Absolute, out _)succeeds and the value starts with a scheme. The attribute accepts any scheme, not onlyhttpandhttps: check the scheme in your code, for instance with IsHttpOrHttps (see Other helpers). - File paths, such as
/images/logo.png,C:\images\logo.pngor\\server\share, are not valid, althoughUrireads them asfile://URIs. Writefile:///images/logo.pngto accept a file. nulland values that are not strings are valid. Add[Required]to reject a missing value.- The default error message is
Uri '<value>' is not an absolute URI. A message passed to the constructor ([AbsoluteUri("{0} must be an absolute URI")]), toErrorMessage, or throughErrorMessageResourceNamereplaces it;{0}is the display name of the property. - The result has the name of the property in
MemberNames, so ASP.NET Core and Blazor show the error next to the field.
Collection items
Data annotations do not validate the objects referenced by a property. [ValidateCollectionItems] validates each item of a collection with its own data annotations:
public sealed class MyModel
{
// Validates each item using the data annotations of ChildModel
[ValidateCollectionItems]
public List<ChildModel>? Items { get; set; }
}
public sealed class ChildModel
{
[Required]
public string? Name { get; set; }
}With these classes, an item without a name makes the model invalid:
var model = new MyModel
{
Items = [new ChildModel { Name = "valid" }, new ChildModel { Name = null }],
};
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model), results, validateAllProperties: true);
// The result is the first error, and its member name tells which item is invalid
Assert.False(isValid);
var error = Assert.Single(results);
Assert.Equal("The Name field is required.", error.ErrorMessage);
Assert.Equal(["Items[1].Name"], error.MemberNames);- Each item is validated with
Validator.TryValidateObjectand all its properties. The validation context of each item has the service provider and theItemsof the context of the model. - Every item is validated. The attribute reports one CollectionItemsValidationResult: its message and member name are those of the first error, and its Errors property holds the errors of all the items.
- Member names are prefixed with the name of the collection and the index of the item, such as
Items[1].Name. An error without a member name, such as one fromIValidatableObject, gets the path of the item, such asItems[1]. - With a custom message (
[ValidateCollectionItems(ErrorMessage = "{0} contains invalid items")]), the result has this message and the member names of all the errors. - A
nullcollection is valid, andnullitems are skipped. Add[Required]to reject a missing collection. - The items are validated one level deep: a collection inside an item needs its own [ValidateCollectionItems].
ValidateCollectionItemsAttribute discovers the properties of the items at run time, so it is not compatible with trimming.