Interface IScalarValue<TSelf, TPrimitive>
- Namespace
- Trellis
- Assembly
- Trellis.Core.dll
Interface for scalar value objects that can be created with validation. Enables automatic ASP.NET Core model binding and JSON serialization.
public interface IScalarValue<TSelf, TPrimitive> where TSelf : IScalarValue<TSelf, TPrimitive> where TPrimitive : IComparable
Type Parameters
TSelfThe value object type itself (CRTP pattern)
TPrimitiveThe underlying primitive type (must be IComparable)
Examples
Modern canonical pattern using the source generator:
// Modern canonical pattern — generator emits IScalarValue<EmailAddress, string>
// implementation, TryCreate factory, JSON converter, IParsable, and equality.
public sealed partial class EmailAddress : RequiredString<EmailAddress>;
// Custom validation via the partial method hook:
public partial class EmailAddress
{
static partial void ValidateAdditional(string value, string fieldName, ref string? errorMessage)
{
if (!value.Contains('@')) errorMessage = "Email must contain '@'.";
}
}
Remarks
This interface uses the Curiously Recurring Template Pattern (CRTP) to enable static abstract methods on the value object type. This allows model binders and JSON converters to call TryCreate(TPrimitive, string?) without reflection.
When a type implements this interface, it can be automatically validated during:
- ASP.NET Core model binding (from route, query, form, or header values)
- JSON deserialization (from request body)
Properties
Value
Gets the underlying primitive value for serialization.
TPrimitive Value { get; }
Property Value
- TPrimitive
The primitive value wrapped by this scalar value.
Methods
Create(TPrimitive)
Creates a validated scalar value from a primitive value. Throws an exception if validation fails.
public static TSelf Create(TPrimitive value)
Parameters
valueTPrimitiveThe raw primitive value
Returns
- TSelf
The validated scalar value
Examples
Use in tests or with known-valid values:
// ✅ Good - Test data
var email = EmailAddress.Create("test@example.com");
// ✅ Good - Building from validated data
var total = Money.Create(item1.Amount + item2.Amount, "USD");
// ❌ Bad - User input (use TryCreate instead)
var email = EmailAddress.Create(userInput);
Remarks
Use this method when you know the value is valid (e.g., in tests, with constants,
or when building from other validated values). This provides cleaner code
than manually unwrapping a TryCreate(...) result at each call site.
⚠️ Don't use this method with user input or uncertain data - use TryCreate(TPrimitive, string?) instead to handle validation errors gracefully.
The default implementation calls TryCreate(TPrimitive, string?) and throws if validation fails. You can override this if you need custom error handling behavior.
Why this and ScalarValueObject<TSelf, T>.Create coexist. This static-virtual
is the entry point for generic-constraint dispatch — call sites of the form
T.Create(value) where T : IScalarValue<T, P>. Concrete-type call sites
such as EmailAddress.Create("x") bind to the regular static method on the
ScalarValueObject<TSelf, T> base class, because C# does not surface interface
static-virtual defaults through concrete-type dispatch. The two methods produce identical
results — they just exist for the two different call-site shapes.
Exceptions
- InvalidOperationException
Thrown when validation fails
TryCreate(string?, string?)
Attempts to create a validated scalar value from a string representation.
public static abstract Result<TSelf> TryCreate(string? value, string? fieldName = null)
Parameters
valuestringThe raw string value to parse and validate
fieldNamestringOptional field name for validation error messages. If null, implementations should use a default field name based on the type name.
Returns
- Result<TSelf>
Success with the scalar value, or Failure with validation errors
TryCreate(TPrimitive, string?)
Attempts to create a validated scalar value from a primitive value.
public static abstract Result<TSelf> TryCreate(TPrimitive value, string? fieldName = null)
Parameters
valueTPrimitiveThe raw primitive value
fieldNamestringOptional field name for validation error messages. If null, implementations should use a default field name based on the type name (e.g., "emailAddress" for EmailAddress type).
Returns
- Result<TSelf>
Success with the scalar value, or Failure with validation errors
Remarks
This method is called by model binders and JSON converters to create scalar values with validation. The validation errors are collected and returned through the standard ASP.NET Core validation infrastructure.
When called from ASP.NET Core model binding or JSON deserialization, the fieldName parameter is automatically populated with the property name from the DTO.