The ValidationsGenerator cannot discover validatable types when:
- DTOs live in a referenced assembly (not the host)
- Endpoint mapping uses generic extension methods (e.g.,
app.MapCommand<TRequest>())
This forces users into three compounding workarounds that shouldn't be necessary.
Assembly A — DTOs:
// MyApp.Models/CreateItemRequest.cs
public sealed record CreateItemRequest(
[Required] [StringLength(200, MinimumLength = 1)] string Name,
[Required] [EmailAddress] string Email);Assembly B — Endpoints:
// MyApp.Api/ItemApi.cs
public static class ItemApi
{
public static void Map(IEndpointRouteBuilder app)
{
app.MapPost("/items", (CreateItemRequest request) => Results.Ok());
}
}Host:
builder.Services.AddValidation();
app.MapItemEndpoints();Result: Validation does not run. Invalid requests return 200 OK.
Workaround 1 — Fake trigger method in the DTO assembly (forces generator to run):
// MyApp.Models/ValidationCodegenTrigger.cs
internal static class ValidationCodegenTrigger
{
// Never called. Exists solely to make the source generator emit a resolver for this assembly.
public static IServiceCollection Trigger(this IServiceCollection services)
=> services.AddValidation();
}Workaround 2 — [ValidatableType] on every DTO (forces type discovery):
[ValidatableType]
public sealed record CreateItemRequest(...);Workaround 3 — Reflection-based resolver aggregation in the host:
#pragma warning disable ASP0029
builder.Services.AddValidation(options =>
{
foreach (var resolver in assembly.GetTypes()
.Where(t => typeof(IValidatableInfoResolver).IsAssignableFrom(t)
&& !t.IsAbstract
&& t.GetConstructor(Type.EmptyTypes) != null)
.Select(t => (IValidatableInfoResolver)Activator.CreateInstance(t)!))
{
options.Resolvers.Add(resolver);
}
});
#pragma warning restore ASP0029All three are required. Missing any one means validation silently does nothing.
Two independent failures in the generator pipeline:
TypesParser.cs resolves types from the handler delegate's formal parameters. When the delegate is in a different
assembly, or when the mapping goes through a generic extension method like MapCommand<TRequest>(), the generator sees
ITypeParameterSymbol (not the concrete type) and skips it because type parameters have NotApplicable accessibility:
// TypesParser.cs:78-81
if (typeSymbol.DeclaredAccessibility is not Accessibility.Public)
{
return false; // Type parameters hit this path — silently skipped
}The ValidationsGenerator only emits an IValidatableInfoResolver for assemblies that contain an AddValidation()
invocation. Feature assemblies that only define DTOs and endpoints never trigger generation — there is no resolver to
discover, even with reflection.
The ErrorOrX source generator handles this exact scenario without any workarounds. Its architecture:
-
Discovers types from user method signatures, not from endpoint delegate resolution. When a user writes
Create(CreateOrderRequest request, ...), the generator seesCreateOrderRequestas a concrete type — regardless of which assembly it lives in. -
Emits a single resolver per compilation. No duplicate hint names, no per-assembly activation requirement.
-
Cross-assembly just works:
MyApp.Models/ ← DTOs with [Required], [Range], etc.
CreateOrderRequest.cs
MyApp.Api/ ← Endpoints (different assembly)
OrderApi.cs ← Create(CreateOrderRequest request, ...)
Program.cs ← AddValidation() + MapErrorOrEndpoints()
No fake trigger methods. No [ValidatableType]. No reflection scanning.
Resolve generic type arguments at invocation sites before the accessibility check:
if (typeSymbol is ITypeParameterSymbol typeParam)
{
var concreteType = ResolveTypeArgument(operation, typeParam);
if (concreteType is not null)
typeSymbol = concreteType;
else
return false;
}Consider one of:
- Option A: Run the generator for any assembly containing types with
[ValidatableType]orDataAnnotationsattributes, without requiring anAddValidation()call - Option B: Have the host's
AddValidation()interceptor automatically discover and register resolvers from referenced assemblies
- Stack Overflow: Built-in validation support for Minimal APIs (291 views)
- Stack Overflow: Validation stops working when endpoint mapping is in different assembly ( 159 views)
- #61971 — Duplicate hint name (fixed, but symptom of multi-emission)
- #61388 — Generic type validation failures (fixed for some cases)
- #62757 — Inaccessibility errors (fixed by skipping private types)
The closed issues fixed crashes but not the underlying discovery gap. Cross-assembly validation still requires all three workarounds.
- .NET 10.0
Microsoft.Extensions.Validationsource generator- Multi-project solution with shared DTO assemblies