Class RequiredString<TSelf>
- Namespace
- Trellis
- Assembly
- Trellis.Primitives.dll
Base class for creating strongly-typed string value objects that cannot be null or empty. Provides a foundation for domain primitives like names, descriptions, codes, and other textual concepts.
public abstract class RequiredString<TSelf> : ScalarValueObject<TSelf, string>, IComparable<ValueObject>, IEquatable<ValueObject>, IConvertible where TSelf : RequiredString<TSelf>, IScalarValue<TSelf, string>
Type Parameters
TSelf
- Inheritance
-
ScalarValueObject<TSelf, string>RequiredString<TSelf>
- Implements
- Inherited Members
- Extension Methods
Examples
Creating a strongly-typed name value object:
// Define the value object (partial keyword enables source generation)
public partial class FirstName : RequiredString<FirstName>
{
}
// The source generator automatically creates:
// - IScalarValue<FirstName, string> interface implementation
// - public static Result<FirstName> TryCreate(string value)
// - public static Result<FirstName> TryCreate(string? value, string? fieldName = null)
// - public static FirstName Parse(string s, IFormatProvider? provider)
// - public static bool TryParse(string? s, IFormatProvider? provider, out FirstName result)
// - public static explicit operator FirstName(string value)
// - private FirstName(string value) : base(value) { }
// Usage examples:
// Create with validation
var result1 = FirstName.TryCreate("John");
// Returns: Success(FirstName("John"))
var result2 = FirstName.TryCreate("");
// Returns: Failure(ValidationError("First Name cannot be empty."))
var result3 = FirstName.TryCreate(null);
// Returns: Failure(ValidationError("First Name cannot be empty."))
var result4 = FirstName.TryCreate(" John ");
// Returns: Success(FirstName("John")) - automatically trimmed
// With custom field name for validation errors
var result5 = FirstName.TryCreate(input, "user.firstName");
// Error field will be "user.firstName" instead of default "firstName"
// Using in entity creation
public class Person : Entity<PersonId>
{
public FirstName FirstName { get; }
public LastName LastName { get; }
public static Result<Person> Create(string firstName, string lastName) =>
FirstName.TryCreate(firstName)
.Combine(LastName.TryCreate(lastName))
.Map((first, last) => new Person(PersonId.NewUnique(), first, last));
private Person(PersonId id, FirstName firstName, LastName lastName)
: base(id)
{
FirstName = firstName;
LastName = lastName;
}
}
ASP.NET Core automatic validation (no manual Result.Combine needed):
// 1. Register automatic validation in Program.cs
builder.Services
.AddControllers()
.AddScalarValueObjectValidation(); // Enables automatic validation!
// 2. Define your DTO with value objects
public record RegisterUserDto
{
public FirstName FirstName { get; init; } = null!;
public LastName LastName { get; init; } = null!;
public EmailAddress Email { get; init; } = null!;
}
// 3. Use in controllers - automatic validation!
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
[HttpPost]
public IActionResult Register(RegisterUserDto dto)
{
// If we reach here, dto is FULLY validated!
// No Result.Combine() needed - validation happens automatically during model binding
var user = new User(dto.FirstName, dto.LastName, dto.Email);
return Ok(user);
}
}
// Invalid request automatically returns 400 Bad Request:
// POST /api/users with { "firstName": "", "lastName": "Doe", "email": "test@example.com" }
// Response: 400 Bad Request
// {
// "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
// "title": "One or more validation errors occurred.",
// "status": 400,
// "errors": {
// "firstName": ["First Name cannot be empty."]
// }
// }
Using in API validation (manual approach):
// Request DTO
public record CreateUserRequest(string FirstName, string LastName, string Email);
// API endpoint with automatic validation
app.MapPost("/users", (CreateUserRequest request) =>
FirstName.TryCreate(request.FirstName, nameof(request.FirstName))
.Combine(LastName.TryCreate(request.LastName, nameof(request.LastName)))
.Combine(EmailAddress.TryCreate(request.Email, nameof(request.Email)))
.Bind((first, last, email) => User.Create(first, last, email))
.ToHttpResult());
// POST /users with empty FirstName:
// Response: 400 Bad Request
// {
// "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
// "title": "One or more validation errors occurred.",
// "status": 400,
// "errors": {
// "firstName": ["First Name cannot be empty."]
// }
// }
Multiple string-based value objects:
public partial class FirstName : RequiredString<FirstName> { }
public partial class LastName : RequiredString<LastName> { }
public partial class CompanyName : RequiredString<CompanyName> { }
public partial class ProductName : RequiredString<ProductName> { }
public partial class Description : RequiredString<Description> { }
public class Product : Entity<ProductId>
{
public ProductName Name { get; private set; }
public Description Description { get; private set; }
public Result<Product> UpdateName(ProductName newName) =>
newName.ToResult()
.Tap(name => Name = name)
.Map(_ => this);
// Compiler prevents mixing types:
// UpdateName(description); // Won't compile!
// UpdateName(firstName); // Won't compile!
}
Advanced: Adding custom validation to derived types:
// While RequiredString handles null/empty, you can add domain-specific rules
public partial class ProductSKU : RequiredString<ProductSKU>
{
// Additional validation can be done in factory methods
public static Result<ProductSKU> TryCreateWithValidation(string? value) =>
TryCreate(value) // Use generated validation first
.Ensure(sku => sku.Value.Length <= 20,
Error.Validation("SKU must be 20 characters or less", "sku"))
.Ensure(sku => sku.Value.All(c => char.IsLetterOrDigit(c) || c == '-'),
Error.Validation("SKU can only contain letters, digits, and hyphens", "sku"));
}
// Usage
var result = ProductSKU.TryCreateWithValidation("PROD-12345");
// Success
var invalid = ProductSKU.TryCreateWithValidation("PROD@12345");
// Failure: "SKU can only contain letters, digits, and hyphens"
Remarks
This class extends ScalarValueObject<TSelf, T> to provide a specialized base for string-based value objects
with automatic validation that prevents null or empty strings. When used with the partial keyword,
the PrimitiveValueObjectGenerator source generator automatically creates:
IScalarValue<TSelf, string>implementation for ASP.NET Core automatic validationTryCreate(string)- Factory method for non-nullable strings (required by IScalarValue)TryCreate(string?, string?)- Factory method with null/empty/whitespace validation and custom field nameIParsable<T>implementation (Parse,TryParse)- JSON serialization support via
ParsableJsonConverter<T> - Explicit cast operator from string
- OpenTelemetry activity tracing
Common use cases:
- Person names (FirstName, LastName, FullName)
- Product attributes (ProductName, Description, SKU)
- Location data (City, State, Country, PostalCode)
- Business identifiers (CompanyName, TaxId, AccountNumber)
- Any domain concept represented by required text
Benefits over plain strings:
- Type safety: Cannot mix FirstName with LastName
- Validation: Prevents null/empty strings at creation time
- Domain clarity: Self-documenting code that expresses intent
- Consistency: Centralized trimming and normalization
- Testability: Easy to test validation rules in isolation
Constructors
RequiredString(string)
Initializes a new instance of the RequiredString<TSelf> class with the specified string value.
protected RequiredString(string value)
Parameters
valuestringThe string value. Must not be null or empty.
Remarks
This constructor is protected and should be called by derived classes.
When using the source generator (with partial keyword), a private constructor
is automatically generated that includes validation and trimming.
Direct instantiation should be avoided. Instead, use the generated factory method:
TryCreate(string?, string?)- Create from string with validation and trimming
The generated TryCreate method automatically:
- Returns validation error for null values
- Returns validation error for empty strings
- Returns validation error for whitespace-only strings
- Trims leading and trailing whitespace from valid strings