Skip to main content

Command Palette

Search for a command to run...

Vogen vs StronglyTypedId in .NET: Which Should Your Team Use in 2026?

Updated
10 min readView as Markdown
Vogen vs StronglyTypedId in .NET: Which Should Your Team Use in 2026?

Primitive obsession is one of the subtler bugs in .NET APIs - int orderId, int customerId, int productId all look identical to the compiler, and nothing stops you from passing them in the wrong order. A strongly-typed ID for each entity turns that class of mistake into a compile error instead of a midnight incident.

The two dominant source-generator libraries for this in the .NET ecosystem are Vogen (by Steve Dunn) and StronglyTypedId (by Andrew Lock). Both solve the same core problem using compile-time source generation, but they have meaningfully different philosophies, feature scopes, and trade-offs. The patterns covered here go deeper on Patreon - with annotated, production-ready source code showing exactly how these libraries wire into EF Core, ASP.NET Core model binding, and JSON serialization in real APIs.

Getting strongly-typed IDs right is also closely tied to how you structure your domain layer and value objects - something Chapter 11 of the Zero to Production course walks through inside a full production ASP.NET Core API with clean architecture and CQRS already wired together.

ASP.NET Core Web API: Zero to Production

The Problem Both Libraries Solve

Consider a typical service method:

public async Task<Order> PlaceOrder(int customerId, int productId, int quantity) { ... }

The compiler accepts PlaceOrder(productId, customerId, 3) without complaint. The bug ships. In production, orders go to the wrong customers.

Strongly-typed IDs make the wrong argument order a compile error:

public async Task<Order> PlaceOrder(CustomerId customerId, ProductId productId, Quantity quantity) { ... }

Both Vogen and StronglyTypedId use .NET source generators to produce the boilerplate - the JSON converters, EF Core value converters, type conversions, and equality members - so you don't write it by hand.

Overview of Each Library

StronglyTypedId

StronglyTypedId was created by Andrew Lock (author of the well-known .NET book series) specifically to wrap primitive ID types. You annotate a struct with [StronglyTypedId] and the generator produces a struct that wraps Guid, int, long, string, or NullableString.

It is laser-focused: ID types only, no validation, no constraints. Its strength is simplicity - small surface area, easy mental model, easy EF Core and System.Text.Json integration via built-in converters.

Vogen

Vogen is broader in scope. It targets all value objects, not just IDs. You annotate with [ValueObject] and, optionally, provide a validation method. The generator produces the wrapper struct plus a Roslyn analyser that catches invalid construction at call sites.

Vogen treats invalid values as a design-time and code-review problem, not just a runtime check. If you try to create an invalid value object, the analyser fires a warning or error before you push the code. That's a materially different approach to the same primitive-obsession problem.

Side-by-Side Comparison

Feature StronglyTypedId Vogen
Primary focus Entity IDs only Any value object (IDs, money, email, quantity...)
Validation None Built-in - custom Validate() method
Roslyn analyser No Yes - catches invalid construction at compile time
EF Core integration Auto-generated value converters Yes, via Microsoft.EntityFrameworkCore package
System.Text.Json Built-in converters Yes, via Vogen.Serialization.SystemTextJson
Newtonsoft.Json Supported Supported
Primitive backing types Guid, int, long, string, NullableString Guid, int, long, string, decimal, float, double, bool, DateOnly, TimeOnly, DateTime
MediatR / ASP.NET route binding Works with generated converters Works with generated converters and TypeConverter
Source generator approach Incremental Roslyn generator Incremental Roslyn generator
Minimum .NET version .NET 7+ .NET 7+
Opinionatedness Low - simple attr, few options Higher - enforces construction patterns
Learning curve Low Low to medium
NSwag / Swagger support Via value converter registration Via value converter registration

When StronglyTypedId Is the Right Choice

StronglyTypedId is the right pick when your main goal is eliminating ID confusion across a codebase and you want zero ceremony. In production I've used it on codebases where the team was already comfortable with DDD tactically but didn't want Vogen's analyser noise during a migration phase.

Use StronglyTypedId when:

  • You only need to type your entity IDs, not domain concepts like Money, Email, or Temperature

  • You want simple adoption with minimal configuration

  • Your team is unfamiliar with value objects and you want a gradual on-ramp

  • You're working with Orleans or Marten and have seen source-generator conflicts with larger toolchains (this is a documented real-world pain point)

  • Binary footprint matters and you want a minimal dependency

When Vogen Is the Right Choice

Vogen is the right pick when you are applying DDD value objects properly and want compile-time protection against invalid state reaching your domain. The Roslyn analyser is the key differentiator - a trade-off that bit us early when we didn't have one was an OrderAmount being constructed with a negative value, and the only check happened at the controller layer.

Use Vogen when:

  • You want to wrap value concepts beyond IDs: money, quantities, emails, postal codes

  • You want the Roslyn analyser to catch invalid construction before code review

  • Your team is committed to a rich domain model and the DDD value-object discipline

  • You need the Validate() hook to enforce invariants at creation time

  • You're comfortable with a slightly more opinionated setup

EF Core Integration - What Actually Matters

Both libraries auto-generate EF Core value converters, but the registration detail matters.

With StronglyTypedId, the generated converter is straightforward - register it in OnModelCreating or use .HasConversion<GuidConverter>():

// EF Core auto-registers the generated converter if you configure it:
entity.Property(p => p.CustomerId)
      .HasConversion(new CustomerIdEfCoreValueConverter());

With Vogen, the EF Core integration package handles the plumbing similarly, and Vogen also generates a TypeConverter that ASP.NET Core route binding picks up automatically - meaning [Route("{customerId}")] just works with CustomerId as a parameter type.

Both require a one-time registration per type and both work with migrations and query translation.

JSON Serialization Trade-offs

In .NET 10 APIs, System.Text.Json is the default. Both libraries generate JSON converters.

StronglyTypedId generates converters registered via [JsonConverter] attributes on the generated struct - no additional wiring needed. Vogen's JSON converters are in a separate package and require explicit registration in AddJsonOptions or on the type.

In practice, the difference is minor. The trade-off that catches teams is polymorphic scenarios - if you're serializing a base type that contains either CustomerId or VendorId, you need to ensure both libraries' generated converters are registered, and Vogen's registration step is easy to miss in a new service.

Real-World Trade-off: Validation at the Boundary

One scenario where Vogen's approach changed how we shipped was input validation. With StronglyTypedId, an invalid ID (say, CustomerId constructed from 0 or a Guid.Empty) compiles fine and flows through the pipeline until you hit the database. With Vogen's Validate() hook, you can reject it at construction:

[ValueObject<int>]
public partial struct CustomerId
{
    private static Validation Validate(int value) =>
        value > 0 ? Validation.Ok : Validation.Invalid("CustomerId must be positive.");
}

That's a small snippet but it illustrates the philosophy: Vogen treats the value type itself as the validation layer. FluentValidation still handles request-level validation, but domain invariants live in the value object.

How Does This Fit Into a Real API?

In a Zero to Production-style clean architecture, strongly-typed IDs live in the Domain layer. Both libraries are zero-dependency - the generated structs have no runtime library overhead, only the source generator as a development dependency.

Internal link: if you're applying the repository pattern alongside EF Core, see how strongly-typed IDs interact with FindAsync and query translation in EF Core compiled queries vs raw SQL vs Dapper.

For authoritative documentation on the underlying C# feature that makes this possible, the Microsoft source generators documentation explains the incremental generator model both libraries use.

Recommendation

For most ASP.NET Core teams in 2026:

  • Starting with ID typing only? StronglyTypedId. Lower friction, easier onboarding, smaller blast radius.

  • Adopting DDD value objects seriously? Vogen. The Roslyn analyser ROI compounds over time, especially on large teams where value objects are used broadly across bounded contexts.

  • Using one library already? Don't mix. The mental model cost of having both in the same codebase outweighs any marginal benefit from picking the "better" tool per type.

There is no wrong answer here - both libraries are well-maintained, production-proven, and actively used by teams shipping real .NET APIs. The decision is primarily about scope and philosophy, not technical superiority.

FAQ

What is primitive obsession in .NET?

Primitive obsession is the anti-pattern of using built-in primitive types (int, Guid, string) to represent domain concepts that deserve their own types. In .NET APIs, the most common symptom is multiple int or Guid parameters that the compiler cannot distinguish - making argument-order bugs invisible until runtime.

Does Vogen have runtime overhead compared to StronglyTypedId?

Neither library has meaningful runtime overhead. Both generate plain structs with no runtime library dependency - the source generator is a build-time tool only. The structs wrap a primitive and are as cheap to allocate as the underlying type. Vogen's Validate() call is a single method invocation at construction time.

Do both libraries work with EF Core 10?

Yes. Both libraries generate EF Core value converters compatible with EF Core 8 through 10. EF Core 10 has no breaking changes affecting either library's value converter approach. Vogen also ships a dedicated Vogen.Integrations.EntityFrameworkCore package.

Can I use StronglyTypedId or Vogen with ASP.NET Core model binding?

Yes. Both generate TypeConverter implementations used by ASP.NET Core for route and query parameter binding. You can declare [HttpGet("{orderId}")] with OrderId orderId as the parameter and binding works with no additional configuration.

Which library is better for Minimal APIs vs Controllers?

Both work equally well with both styles. The generated TypeConverter handles route binding in Minimal APIs and Controllers identically. The only difference is that Minimal APIs' typed Results<T> may require explicit JSON converter registration for some edge cases - but this is a one-time setup.

Is Vogen v4 stable for production use in 2026?

Yes. Vogen 4.x (the incremental generator rewrite) is stable and in active production use. It addressed the incremental generator performance issues present in earlier versions and is compatible with .NET 7 through .NET 10.

What happens if I use both Vogen and StronglyTypedId in the same project?

Both use Roslyn incremental generators and should not conflict at the generator level. However, there are documented cases of generator conflicts when also using Orleans or Marten generators in the same project, particularly with StronglyTypedId. Vogen has been tested with Marten and is officially recommended by the Marten team. If you're in a complex multi-generator setup, Vogen is the safer choice.


About the Author

I'm Celin Daniel, Co-founder of Coding Droplets. I've been building .NET and ASP.NET Core systems in production for 13+ years - APIs, distributed backends, enterprise platforms. Everything I write here comes from real shipping experience: patterns that held up, trade-offs that bit us, and lessons learned the hard way.

More from this blog

C

Coding Droplets

280 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.