A modern, lightweight, AOT-compatible type-safe implementation of TypeId. TypeIds are globally unique, K-sortable identifiers that combine a human-readable prefix with a UUIDv7 encoded in Crockford Base32.
$ dotnet add package TypeSafeIdA modern, type-safe implementation of TypeID version 0.3 for .NET. TypeIds are globally unique, K-sortable identifiers that combine a human-readable prefix with a UUIDv7 encoded in Crockford Base32.
Here's an example of a TypeID of type user:
user_01h455vb4pex5vsknk084sn02q
└──┘ └────────────────────────┘
type uuid suffix (base32)
It's corresponding type-safe id type could be TypeId<User>.
TypeId<TEntity> provides compile-time safetyuser_01h455vb4pex5vsknk084sn02q)# Core library
dotnet add package TypeSafeId
# ASP.NET Core extensions
dotnet add package TypeSafeId.AspNetCore
# Entity Framework Core extensions
dotnet add package TypeSafeId.EntityFrameworkCore
This is the preferred id type. Use it when you know the type of id in advance.
// Define your entity types
record User(TypeId<User> Id, string Name);
// By default, the prefix is derived from the type name (lowercase, "_" separated)
var user = new User(
Id: TypeId.Create<User>(),
Name: "John Doe"
);
Console.WriteLine(user.Id); // user_01h455vb4pex5vsknk084sn02q
// Customize the prefix with the TypeId attribute
[TypeId("prd")]
record Product(TypeId<Product> Id, string Name);
var product = new Product(
Id: TypeId.Create<Product>(),
Name: "Widget"
);
Console.WriteLine(product.Id); // prd_01h455vb4pex5vsknk084sn02q
Use non-typed TypeId if you do not know the type of id.
using TypeSafeId;
// Create a TypeId with a prefix
var userId = TypeId.Create("user");
Console.WriteLine(userId); // user_01h455vb4pex5vsknk084sn02q
// Parse from string
var parsed = TypeId.Parse("user_01h455vb4pex5vsknk084sn02q");
Add TypeId routing support in your application:
using TypeSafeId;
var builder = WebApplication.CreateBuilder(args);
// Register TypeId route constraints
builder.Services.AddTypeIdRouting();
var app = builder.Build();
// Use TypeId in route parameters
app.MapGet("/users/{userId:typeid}", (TypeId<User> userId) =>
{
return Results.Ok(new { UserId = userId });
});
app.MapGet("/products/{productId:typeid}", (TypeId<Product> productId) =>
{
return Results.Ok(new { ProductId = productId });
});
app.Run();
record User(TypeId<User> Id, string Name);
[TypeId("prd")]
record Product(TypeId<Product> Id, string Name);
Use TypeId value converters for database storage:
using Microsoft.EntityFrameworkCore;
using TypeSafeId.EntityFrameworkCore.Storage.ValueConversion;
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Store as string
modelBuilder.Entity<User>()
.Property(u => u.Id)
.HasConversion(new TypeIdToStringConverter<User>());
// Or store as bytes for better performance
modelBuilder.Entity<User>()
.Property(u => u.Id)
.HasConversion(new TypeIdToBytesConverter<User>());
}
}
record User
{
public TypeId<User> Id { get; init; }
public string Name { get; set; } = string.Empty;
}
TypeIds serialize naturally with System.Text.Json:
using System.Text.Json;
using TypeSafeId;
var user = new User(
Id: TypeId.Create<User>(),
Name: "Jane Doe"
);
var json = JsonSerializer.Serialize(user);
// {"Id":"user_01h455vb4pex5vsknk084sn02q","Name":"Jane Doe"}
var deserialized = JsonSerializer.Deserialize<User>(json);
TypeIds follow the official specification:
[prefix_]<base32-encoded-uuid>_ (when prefix is present)user_01h455vb4pex5vsknk084sn02q # With prefix "user"
order_01h455vb4pex5vsknk084sn02r # With prefix "order"
01h455vb4pex5vsknk084sn02s # Without prefix
This implementation is inspired by and builds upon:
MIT License - See LICENSE file for details
Contributions are welcome! Please feel free to submit a Pull Request.