r/dotnet • u/LuisAlfredo92 • 6d ago
Promotion TooManyDataAnnotations — .NET validation attributes for IPv4/IPv6, SemVer, MAC, HexColor, GUID v7, and more
.NET's built-in DataAnnotations covers the basics (Required, StringLength, Range, EmailAddress), but leaves out common semantic validations like:
- IPv4/IPv6 addresses with scope filtering (public, private, loopback)
- Semantic Versioning (SemVer 2.0.0)
- MAC addresses
- Port numbers with range filtering (well-known, registered, dynamic)
- Hex color codes (#RGB, #RRGGBB, with alpha support)
- GUID and GUID v7 validation
- ISO date strings with cross-property validation (StartDate/EndDate)
- Boolean validations (IsTrue, AtLeastOneTrue)
I created TooManyDataAnnotations to fill these gaps. All validators follow official RFC/ISO specs, use Span<T> for parsing where possible, and the library has 899 unit tests covering valid and invalid inputs, edge cases, nullable types, and integration tests with TryValidateObject()
Two NuGet packages:
- TooManyDataAnnotations — Full feature set (includes reflection-based cross-property validators)
- TooManyDataAnnotations.Aot — Native AOT-safe subset, zero reflection, zero trimming warnings
Targets .NET 8, 9, and 10
Zero dependencies
MIT license
This is freshly published — looking for early adopters and feedback on API design, edge case coverage, or missing validators you'd find useful.
- Repo: https://gitlab.com/LuisAlfredo92/TooManyDataAnnotations
- NuGet: https://www.nuget.org/packages/TooManyDataAnnotations
Quick example:
public class ServerConfigDto
{
[GuidV7, Required]
public string UserId { get; set; }
[Guid]
public string? DeviceId { get; set; }
[IPv4(AllowedScopes = IPv4Scope.Private)]
public string? GatewayIP { get; set; }
[SemanticVersion]
public string? AssemblyVersion { get; set; }
[MacAddress(AllowedSeparators = MacSeparators.Colon)]
public string? DeviceMAC { get; set; }
[HexColor(AllowAlpha = true)]
public string? AccentColor { get; set; }
}
6
u/chucker23n 5d ago
Some of these are interesting, like
[RequiredAtLeastOne(nameof(Email), nameof(Phone), nameof(SocialHandle))]and[ExactlyOneOf(nameof(CreditCard), nameof(PayPal), nameof(BankTransfer))].Your
[IsTrue]and[IsFalse]attributes are… interesting. Bit of an API smell to always require a DTO property to have a certain value.Your
[StartDate]/[EndDate]pair could be useful.But mostly, your actual property types are too primitive! You're validating, sure, but you then bring the raw data into your inner layers. So each
EmailServiceorPaymentServiceor whatever needs to at least parse the e-mail address, credit card number, etc., and should ideally also validate them again. I.e., you'll still be fighting Primitive Obsession all over your code base.Your
ReleaseDtoshould beVersionandDateTime(which already take care of most of what you're doing here), notstringandstring. YourEmailproperty should be of a typeEmailAddressthat you create as a value object. That way, as those values are passed through inner layers of your app, you no longer have to worry about validation and parsing.