SGuard.DataAnnotations
0.2.0
dotnet add package SGuard.DataAnnotations --version 0.2.0
NuGet\Install-Package SGuard.DataAnnotations -Version 0.2.0
<PackageReference Include="SGuard.DataAnnotations" Version="0.2.0" />
<PackageVersion Include="SGuard.DataAnnotations" Version="0.2.0" />
<PackageReference Include="SGuard.DataAnnotations" />
paket add SGuard.DataAnnotations --version 0.2.0
#r "nuget: SGuard.DataAnnotations, 0.2.0"
#:package SGuard.DataAnnotations@0.2.0
#addin nuget:?package=SGuard.DataAnnotations&version=0.2.0
#tool nuget:?package=SGuard.DataAnnotations&version=0.2.0
SGuard.DataAnnotations
SGuard.DataAnnotations provides localized and extensible DataAnnotations support for .NET, including:
- Localizable validation attributes (with robust fallback and custom error handling)
- Collection, conditional, and property comparison validators are not found in standard DataAnnotations
- Guard pattern (
Is.*for boolean return,ThrowIf.*for exception-throwing) for model validation - Seamless integration with DataAnnotations and SGuard's fail-fast/callback philosophy
- Well-tested and extensible for real-world application scenarios
Note: For fluent validation/guard support, see the upcoming
SGuard.FluentValidationpackage.Important: If you want to implement custom callback, fail-fast, or chainable guard logic, you should also review the SGuard core project, which provides advanced guard and callback APIs used by this library.
Table of Contents
- Installation
- Features
- Supported Languages
- Supported Attributes
- Guard Pattern API
- Localization & Fallback
- Extending SGuard.DataAnnotations
- Minimal API Example (Real World)
- Advanced Topics
- FAQ / Tips
- Contributing
- License
Installation
Install via NuGet:
dotnet add package SGuard.DataAnnotations
Requires .NET 8, 9 or 10, and brings in SGuard 3.0 or later. For .NET 6 and 7, use version 0.1.1.
Features
- Localized error messages via resource files (
.resx), with fallback to default or custom messages. - Advanced collection and conditional validation (min/max count, required-if, required collection, collection element validation, etc.).
- Comparison attributes for property-to-property or value-to-value checks (greater than, less than, between, compare, etc.).
- Full DataAnnotations compatibility—use SGuard attributes anywhere a standard attribute is accepted.
- Guard pattern API (
Is/ThrowIf) for easy imperative validation and exception/callback handling.
Supported Languages
SGuard.DataAnnotations ships with built-in resource support for the following languages:
| Language | Culture Code | Localized Resource File |
|---|---|---|
| English (default) | en |
SGuardDataAnnotations.resx |
| Turkish | tr |
SGuardDataAnnotations.tr.resx |
| German | de |
SGuardDataAnnotations.de.resx |
| French | fr |
SGuardDataAnnotations.fr.resx |
| Russian | ru |
SGuardDataAnnotations.ru.resx |
| Japanese | ja |
SGuardDataAnnotations.ja.resx |
| Hindi | hi |
SGuardDataAnnotations.hi.resx |
Note:
- If the current UI culture is not found, SGuard will fallback to English or to the fallback message if provided.
- You can add your own resource files to support additional languages.
- How to add your own language?
Supported Attributes
String & Common Validators
| Attribute | Purpose | Supported Types | Example Usage |
|---|---|---|---|
SGuardRequiredAttribute |
Required field (localized) | Any | [SGuardRequired(typeof(Resources.SGuardDataAnnotations), "Username_Required")] |
SGuardMinLengthAttribute |
Minimum string length | string, array, ICollection |
[SGuardMinLength(5, typeof(Resources.SGuardDataAnnotations), "Username_MinLength")] |
SGuardMaxLengthAttribute |
Maximum string length | string, array, ICollection |
[SGuardMaxLength(20, typeof(Resources.SGuardDataAnnotations), "Username_MaxLength")] |
SGuardStringLengthAttribute |
Min/max string length | string |
[SGuardStringLength(12, typeof(Resources.SGuardDataAnnotations), "Username_MaxLength")] |
SGuardRegularExpressionAttribute |
Regex pattern | string |
[SGuardRegularExpression("^[a-zA-Z0-9_]+$", typeof(Resources.SGuardDataAnnotations), "Username_InvalidCharacters")] |
SGuardEmailAddressAttribute |
Email format | string |
[SGuardEmailAddress(typeof(Resources.SGuardDataAnnotations), "Email_InvalidFormat")] |
SGuardPhoneAttribute |
Phone format | string |
[SGuardPhone(typeof(Resources.SGuardDataAnnotations), "Profile_Phone_Invalid")] |
SGuardUrlAttribute |
URL format | string |
[SGuardUrl(typeof(Resources.SGuardDataAnnotations), "Common_Url_Invalid")] |
SGuardCreditCardAttribute |
Credit card format | string |
[SGuardCreditCard(typeof(Resources.SGuardDataAnnotations), "Common_CreditCard_Invalid")] |
SGuardRangeAttribute |
Value must be within a numeric range | int, double |
[SGuardRange(1, 10, typeof(Resources.SGuardDataAnnotations), "Common_Range")] |
Collection Validators
| Attribute | Purpose | Supported Types | Example Usage |
|---|---|---|---|
SGuardRequiredCollectionAttribute |
Collection must not be null/empty | IEnumerable, arrays, etc. |
[SGuardRequiredCollection(typeof(Resources.SGuardDataAnnotations), "Common_Collection_Required")] |
SGuardMinCountAttribute |
Minimum item count in collection | IEnumerable, arrays, etc. |
[SGuardMinCount(2, typeof(Resources.SGuardDataAnnotations), "Common_Collection_MinCount")] |
SGuardMaxCountAttribute |
Maximum item count in collection | IEnumerable, arrays, etc. |
[SGuardMaxCount(5, typeof(Resources.SGuardDataAnnotations), "Common_Collection_MaxCount")] |
SGuardCollectionItemsMatchAttribute |
Each item must match one/more validators (e.g. regex, required) | IEnumerable, arrays, etc. |
[SGuardCollectionItemsMatch(typeof(EmailAddressAttribute), typeof(Resources.SGuardDataAnnotations), "Email_InvalidFormat", AggregateAllErrors = true)] |
Details:
SGuardCollectionItemsMatchAttributecan take multiple validators and will apply them to each item.AggregateAllErrors(default:false): Iftrue, collects all errors; iffalse, returns on first failure.MaxErrors(default:10): withAggregateAllErrors, validation stops after this many errors, so a large collection doesn't produce a message for every item.- Each validator type must be a non-abstract
ValidationAttributewith a public parameterless constructor; other types throwArgumentException. - Supported on any
IEnumerable(e.g.,List<T>, arrays, custom collections). - The collection attributes (
SGuardRequiredCollection,SGuardMinCount,SGuardMaxCount,SGuardCollectionItemsMatch) throwInvalidOperationExceptionfor anIQueryable, because enumerating it would run the query. Validate a materialized collection (for exampleToList()) instead.
Conditional Validators
| Attribute | Purpose | Supported Types | Example Usage |
|---|---|---|---|
SGuardRequiredIfAttribute |
Field required if another property equals value | Any | [SGuardRequiredIf("Country", "USA", typeof(Resources.SGuardDataAnnotations), "Address_Required")] |
Comparison Validators
| Attribute | Purpose | Supported Types | Example Usage |
|---|---|---|---|
SGuardCompareAttribute |
Values must be equal (like CompareAttribute) | Any | [SGuardCompare("Password", typeof(Resources.SGuardDataAnnotations), "Password_Mismatch")] |
SGuardBetweenAttribute |
Value must be between two properties | IComparable types | [SGuardBetween("Min", "Max", true, typeof(Resources.SGuardDataAnnotations), "Common_Between")] |
SGuardGreaterThanAttribute |
Value must be greater than another property | IComparable types | [SGuardGreaterThan("MinAge", typeof(Resources.SGuardDataAnnotations), "Profile_BirthDate_MinimumAge")] |
SGuardLessThanAttribute |
Value must be less than another property | IComparable types | [SGuardLessThan("MaxAge", typeof(Resources.SGuardDataAnnotations), "Profile_BirthDate_MaximumAge")] |
Supported types: int, double, decimal, DateTime, string, etc. (anything implementing IComparable)
Guard Pattern API
SGuard.DataAnnotations provides two imperative APIs for runtime validation, following the SGuard pattern.
Is.* Methods
Purpose: Return
boolfor validation checks. An invalid model returnsfalse; only an attribute that is declared wrong throws (see Error Handling).Callback: Optional
SGuardCallbackinvoked withGuardOutcome.Success/Failure.
For advanced callback usage, see the SGuard project documentation.Example:
if (Is.DataAnnotationsValid(model)) { // model is valid }With callback:
bool valid = Is.DataAnnotationsValid(model, callback: outcome => { if (outcome == GuardOutcome.Failure) Console.WriteLine("Validation failed!"); });Get all validation errors:
bool valid = Is.DataAnnotationsValid(model, out var results); foreach (var err in results) Console.WriteLine($"{string.Join(", ", err.MemberNames)}: {err.ErrorMessage}");
ThrowIf.* Methods
Purpose: Throw exception if validation fails.
Callback: Invoked before throw (
GuardOutcome.Failure) or on pass (GuardOutcome.Success).Example:
ThrowIf.DataAnnotationsInValid(model); // Throws DataAnnotationsException if model is invalid.Custom exception:
ThrowIf.DataAnnotationsInValid<ArgumentException>(model, new ArgumentException("Custom error!"));Custom exception from a factory (the factory runs only when validation fails):
ThrowIf.DataAnnotationsInValid(model, () => new ArgumentException("Custom error!"));The overload that takes constructor arguments (
new object[] { "Custom error!" }) is obsolete (SGUARDDA001): it creates the exception with reflection, which the compiler can't check.
Exception Details
- Throws
DataAnnotationsExceptionby default, which contains all validation errors. - Extract errors (see also
SGuard.DataAnnotations.Extensions):catch (DataAnnotationsException ex) { if (ex.TryGetValidationErrors(out var errors)) { foreach (var err in errors) Console.WriteLine($"{string.Join(", ", err.Members)}: {err.Message}"); } }
Localization & Fallback
- All SGuard attributes support:
ErrorMessageResourceTypeandErrorMessageResourceName(standard .NET resource workflow)FallbackResourceName: Used if the main resource key is missing.FallbackMessage: Used if both resource keys are missing.
- Culture: Error messages are localized to the current
UICulture. - Example:
[SGuardMinLength(3, typeof(Resources.SGuardDataAnnotations), "Username_MinLength", FallbackResourceName = "Common_MinLength", FallbackMessage = "Min length required.")] public string Username { get; set; }
How to add a custom language?
- Copy
SGuardDataAnnotations.resxand rename to e.g.SGuardDataAnnotations.es.resxfor Spanish. - Translate all keys/values.
- Rebuild and set your thread/UI culture accordingly.
Extending SGuard.DataAnnotations
Want to add your own fully localized validation attribute?
Inherit from SGuardValidationAttributeBase and implement IsValid:
public class MyCustomLocalizedAttribute : SGuardValidationAttributeBase
{
public MyCustomLocalizedAttribute(Type resourceType, string resourceName)
: base(resourceType, resourceName) {}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
// Your logic here
if (value == null)
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
return ValidationResult.Success;
}
}
Minimal API Example (Real World)
Here's how you use SGuard.DataAnnotations in an ASP.NET Core Minimal API:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using SGuard.DataAnnotations;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/register", (UserRegistration model) =>
{
if (!Is.DataAnnotationsValid(model, out var errors))
return Results.BadRequest(errors.Select(e => new { e.MemberNames, e.ErrorMessage }));
// If valid, continue
return Results.Ok("Registration successful!");
});
app.Run();
public class UserRegistration
{
[SGuardRequired(typeof(Resources.SGuardDataAnnotations), "Username_Required")]
[SGuardMinLength(3, typeof(Resources.SGuardDataAnnotations), "Username_MinLength")]
[SGuardMaxLength(20, typeof(Resources.SGuardDataAnnotations), "Username_MaxLength")]
public string Username { get; set; }
[SGuardRequired(typeof(Resources.SGuardDataAnnotations), "Email_Required")]
[SGuardEmailAddress(typeof(Resources.SGuardDataAnnotations), "Email_InvalidFormat")]
public string Email { get; set; }
[SGuardRequired(typeof(Resources.SGuardDataAnnotations), "Password_Required")]
[SGuardStringLength(100, typeof(Resources.SGuardDataAnnotations), "Password_MaxLength")]
public string Password { get; set; }
[SGuardCompare("Password", typeof(Resources.SGuardDataAnnotations), "Password_Mismatch")]
public string ConfirmPassword { get; set; }
[SGuardRequiredCollection(typeof(Resources.SGuardDataAnnotations), "Profile_Phone_Required")]
[SGuardCollectionItemsMatch(typeof(SGuardPhoneAttribute), typeof(Resources.SGuardDataAnnotations), "Profile_Phone_Invalid", AggregateAllErrors = true)]
public List<string> PhoneNumbers { get; set; }
}
Advanced Topics
Error Handling
- Guard methods: Return
boolor throw, never both. - Attributes: Return a
ValidationResultfor invalid values. They throwInvalidOperationExceptiononly when the attribute itself is declared wrong, for example whenSGuardCompare,SGuardGreaterThan,SGuardLessThan,SGuardBetweenorSGuardRequiredIfnames a property that the model doesn't have. That keeps property and type names in your logs instead of in the validation messages that clients see. - All exceptions: Contain full error detail, member names, and support for extraction/extension.
- For advanced fail-fast, callback, or custom guard usage:
See SGuard project documentation.
FAQ / Tips
Q: Can I use SGuard attributes in ASP.NET Core, Blazor, WinForms, etc.?
A: Yes, SGuard attributes implement the standard DataAnnotations contract.
Q: What happens if a resource key is missing?
A: The attribute will use FallbackResourceName if provided, otherwise FallbackMessage, otherwise [ResourceKey].
Q: How do I validate a collection’s items?
A: Use [SGuardCollectionItemsMatch(...)]. See Collection Validators.
Q: Can I chain SGuard and standard DataAnnotations attributes?
A: Yes, you can stack any combination on your model.
Q: Will SGuard.DataAnnotations work with FluentValidation?
A: Yes, as long as you use DataAnnotations integration. For a full fluent API, see the upcoming SGuard.FluentValidation package.
Q: I get "ThrowIf is an ambiguous reference" (CS0104).
A: SGuard and SGuard.DataAnnotations both have ThrowIf and Is classes, so a file that imports both namespaces has to
say which one it means. Qualify the call or add an alias:
using SGuard;
using DataAnnotationsGuard = SGuard.DataAnnotations.ThrowIf;
ThrowIf.NullOrEmpty(model.Name); // SGuard
DataAnnotationsGuard.DataAnnotationsInValid(model); // SGuard.DataAnnotations
Q: How do I quickly test everything?
A:
- Run all tests (requires .NET SDK):
dotnet test - For a quick validation in your app, call:
if (!Is.DataAnnotationsValid(model, out var results)) // handle errors, see 'results'
Contributing
Pull requests, issues, and suggestions are very welcome!
See CONTRIBUTING.md for guidelines.
License
MIT License. See LICENSE.
See Also
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.