Strongly-typed IDs for .NET — eliminate primitive obsession with source-generated value types that include equality, comparison, parsing, JSON serialization, and type conversion out of the box.
dotnet add package Demarbit.StrongIdsThe source generator is bundled — no additional packages or configuration needed.
[StrongId] attribute — decorate a partial struct and the source generator produces a complete value type with equality, comparison, parsing, ToString(), and explicit/implicit conversions.
Backing types — Guid (default), Int, Long, and String. Choose the one that matches your persistence layer.
JSON serialization — a JsonConverter<T> is generated for each ID, so System.Text.Json serialization works without any configuration.
Type conversion — a TypeConverter is generated for ASP.NET Core model binding, route parameters, and query strings.
using Demarbit.StrongIds;
[StrongId]
public partial struct OrderId { }That's it. The generator produces a readonly partial struct with a Guid backing type. You get:
OrderId.New()— creates a new ID with a randomGuidOrderId.From(guid)— wraps an existingGuidOrderId.Empty— the default/empty valueOrderId.Parse(string)andOrderId.TryParse(string, out OrderId)—IParsable<T>support- Full
IEquatable<OrderId>andIComparable<OrderId>implementations ==,!=operators- Implicit conversion to
Guid, explicit conversion fromGuid - Built-in
JsonConverterandTypeConverter
[StrongId(BackingType.Guid)] // default
public partial struct OrderId { }
[StrongId(BackingType.Int)]
public partial struct LineNumber { }
[StrongId(BackingType.Long)]
public partial struct EventSequence { }
[StrongId(BackingType.String)]
public partial struct Slug { }Strong IDs pair naturally with Demarbit.Shared.Domain generic entities:
[StrongId]
public partial struct ProductId { }
public class Product : AggregateRoot<ProductId>
{
public string Name { get; private set; }
public Product(ProductId id, string name) : base(id)
{
Name = Guard.Against.NullOrWhiteSpace(name, nameof(name));
}
}
// Usage
var id = ProductId.New();
var product = new Product(id, "Widget");The generated TypeConverter means ASP.NET Core binds route parameters automatically:
[HttpGet("{id}")]
public async Task<IActionResult> Get(OrderId id)
{
// id is parsed from the route string — no manual conversion needed
}Works out of the box with System.Text.Json:
var order = new { Id = OrderId.New(), Name = "Test" };
var json = JsonSerializer.Serialize(order);
// {"Id":"d4f5a1b2-...","Name":"Test"}This package follows a few deliberate constraints:
- Zero runtime dependencies — the attribute library has no dependencies. The source generator runs at compile time only and does not ship with your application.
- Compile-time generation — no reflection, no runtime code generation. The generated code is plain C# that you can inspect in your IDE.
- Small surface area — one attribute, one enum, four backing types. Everything else is generated.
- Framework integration —
IEquatable<T>,IComparable<T>,IParsable<T>,JsonConverter<T>, andTypeConverterare implemented so strong IDs work everywhere primitive types do.
This package complements the other Demarbit shared libraries:
Demarbit.StrongIds ← this package (zero runtime deps)
Demarbit.Shared.Domain ← EntityBase<TId> accepts any IEquatable<TId>
Demarbit.Shared.Application ← CQRS commands/queries use strong IDs as parameters
Demarbit.Shared.Infrastructure ← EF Core value converters for strong IDs
Each package can be used independently. A project using strong IDs without the shared domain library works fine — the generated structs implement IEquatable<T> which satisfies any generic constraint.