Custom Data Types

Custom data types let you define reusable metadata and rules once, then apply them consistently across many properties.

Why create one?

Create a custom data type when a value represents a distinct business concept, not just a CLR primitive.

Examples:

  • ProperNoun
  • ProductCode
  • Percent
  • DocumentNumber

Basic inheritance

[DataAnnotations("Multi-line text", ["MaxLength(4000)"])]
public partial class MultilineText : Text
{
}

Derived types inherit parent annotations by default.

Override annotations intentionally

If you need different limits/formatting than the base type, define those annotations on the derived type.

[DataAnnotations("Short code", ["MaxLength(30)"])]
public partial class ShortCode : Text
{
}

Keep overrides explicit so the effective rules are easy to reason about.

Add reusable logic

Use data type events when behavior should apply to every property using the type.

[Logic]
public class ProductCodeBL(ProductCode.Logic productCode)
{
    [RegisterLogic]
    public void Normalize()
    {
        productCode.AutoCorrect().Transform(value => value?.Trim().ToUpperInvariant());
    }

    [RegisterLogic]
    public void Validate()
    {
        productCode.Validate()
            .RejectIf(value => string.IsNullOrWhiteSpace(value))
            .WithMessage("Product code is required");
    }
}

Guidance

  • Keep type names semantic and business-focused.
  • Use data type logic for cross-entity consistency.
  • Use property logic when a rule is entity-specific.