Class RequiredGuid<TSelf>
- Namespace
- Trellis
- Assembly
- Trellis.Primitives.dll
Base class for creating strongly-typed GUID value objects that cannot have the default (empty) GUID value. Provides a foundation for entity identifiers and other domain concepts represented by GUIDs.
public abstract class RequiredGuid<TSelf> : ScalarValueObject<TSelf, Guid>, IComparable<ValueObject>, IEquatable<ValueObject>, IConvertible where TSelf : RequiredGuid<TSelf>, IScalarValue<TSelf, Guid>
Type Parameters
TSelf
- Inheritance
-
ScalarValueObject<TSelf, Guid>RequiredGuid<TSelf>
- Implements
- Inherited Members
- Extension Methods
Examples
Creating a strongly-typed entity identifier:
// Define the value object (partial keyword enables source generation)
public partial class CustomerId : RequiredGuid<CustomerId>
{
}
// The source generator automatically creates:
// - IScalarValue<CustomerId, Guid> interface implementation
// - public static CustomerId NewUniqueV4() => new(Guid.NewGuid()); // Version 4 (random)
// - public static CustomerId NewUniqueV7() => new(Guid.CreateVersion7()); // Version 7 (time-ordered)
// - public static Result<CustomerId> TryCreate(Guid value)
// - public static Result<CustomerId> TryCreate(Guid? value, string? fieldName = null)
// - public static Result<CustomerId> TryCreate(string? value, string? fieldName = null)
// - public static CustomerId Parse(string s, IFormatProvider? provider)
// - public static bool TryParse(string? s, IFormatProvider? provider, out CustomerId result)
// - public static explicit operator CustomerId(Guid value)
// - private CustomerId(Guid value) : base(value) { }
// Usage examples:
// Generate a new unique ID (Version 4 - random)
var customerId = CustomerId.NewUniqueV4();
// Generate a new unique ID (Version 7 - time-ordered, better for database indexes)
var customerId = CustomerId.NewUniqueV7();
// Create from existing GUID with validation
var result1 = CustomerId.TryCreate(existingGuid);
// Returns: Success(CustomerId) if guid != Guid.Empty
// Returns: Failure(ValidationError) if guid == Guid.Empty
// Create from string with validation
var result2 = CustomerId.TryCreate("550e8400-e29b-41d4-a716-446655440000");
// Returns: Success(CustomerId) if valid GUID format
// Returns: Failure(ValidationError) if invalid format or empty GUID
// With custom field name for validation errors
var result3 = CustomerId.TryCreate(input, "customer.id");
// Error field will be "customer.id" instead of default "customerId"
// In entity constructors
public class Customer : Entity<CustomerId>
{
public EmailAddress Email { get; }
private Customer(CustomerId id, EmailAddress email) : base(id)
{
Email = email;
}
public static Result<Customer> Create(EmailAddress email) =>
email.ToResult()
.Map(e => new Customer(CustomerId.NewUnique(), e));
}
ASP.NET Core automatic validation with route parameters:
// 1. Register automatic validation in Program.cs
builder.Services
.AddControllers()
.AddScalarValueObjectValidation(); // Enables automatic validation!
// 2. Use value objects directly in controller actions
[ApiController]
[Route("api/customers")]
public class CustomersController : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<Customer>> Get(CustomerId id) // Automatically validated!
{
// If we reach here, 'id' is a valid, non-empty CustomerId
// Model binding validated it automatically
var customer = await _repository.GetByIdAsync(id);
return Ok(customer);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(CustomerId id) // Also works here!
{
await _repository.DeleteAsync(id);
return NoContent();
}
}
// Invalid GUID is rejected automatically:
// GET /api/customers/00000000-0000-0000-0000-000000000000
// 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": {
// "id": ["Customer Id cannot be empty."]
// }
// }
Using in API endpoints with automatic JSON serialization (manual approach):
// Request DTO
public record GetCustomerRequest(CustomerId CustomerId);
// API endpoint
app.MapGet("/customers/{id}", (string id) =>
CustomerId.TryCreate(id)
.Bind(_customerRepository.GetAsync)
.Map(customer => new CustomerDto(customer))
.ToHttpResult());
// JSON request/response examples:
// Request: GET /customers/550e8400-e29b-41d4-a716-446655440000
// Response: { "customerId": "550e8400-e29b-41d4-a716-446655440000", "name": "John" }
//
// Invalid GUID: GET /customers/00000000-0000-0000-0000-000000000000
// Response: 400 Bad Request with ValidationError
Multiple strongly-typed IDs in the same domain:
public partial class CustomerId : RequiredGuid<CustomerId> { }
public partial class OrderId : RequiredGuid<OrderId> { }
public partial class ProductId : RequiredGuid<ProductId> { }
public class Order : Entity<OrderId>
{
public CustomerId CustomerId { get; }
private readonly List<OrderLine> _lines = [];
public Result<Order> AddLine(ProductId productId, int quantity) =>
this.ToResult()
.Ensure(_ => quantity > 0, Error.Validation("Quantity must be positive"))
.Tap(_ => _lines.Add(new OrderLine(productId, quantity)));
// Compiler prevents mixing IDs:
// AddLine(customerId, 5); // Won't compile - type safety!
}
Remarks
This class extends ScalarValueObject<TSelf, T> to provide a specialized base for GUID-based value objects
with automatic validation that prevents empty/default GUIDs. When used with the partial keyword,
the PrimitiveValueObjectGenerator source generator automatically creates:
IScalarValue<TSelf, Guid>implementation for ASP.NET Core automatic validationNewUniqueV4()- Factory method for generating new unique Version 4 (random) identifiersNewUniqueV7()- Factory method for generating new unique Version 7 (time-ordered) identifiersTryCreate(Guid)- Factory method for non-nullable GUIDs (required by IScalarValue)TryCreate(Guid?, string?)- Factory method with empty GUID validation and custom field nameTryCreate(string?, string?)- Factory method for parsing strings with validationIParsable<T>implementation (Parse,TryParse)- JSON serialization support via
ParsableJsonConverter<T> - Explicit cast operator from Guid
- OpenTelemetry activity tracing
Common use cases:
- Entity identifiers (CustomerId, OrderId, ProductId)
- Correlation IDs for distributed tracing
- Session or transaction identifiers
- Any domain concept requiring a globally unique, non-empty identifier
Benefits over plain GUIDs:
- Type safety: Cannot accidentally use CustomerId where OrderId is expected
- Validation: Prevents empty/default GUIDs at creation time
- Domain clarity: Makes code more self-documenting and expressive
- Serialization: Consistent JSON and database representation
- Factory methods: Clean API for creating new unique identifiers
Constructors
RequiredGuid(Guid)
Initializes a new instance of the RequiredGuid<TSelf> class with the specified GUID value.
protected RequiredGuid(Guid value)
Parameters
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.
Direct instantiation should be avoided. Instead, use the generated factory methods:
NewUnique()- Generate a new unique GUIDTryCreate(Guid?, string?)- Create from GUID with validationTryCreate(string?, string?)- Create from string with validation