Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Add initial validations generator for minimal APIs #59795

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

captainsafia
Copy link
Member

This PR introduces support for parameter validation based on System.ComponentModel annotations to minimal APIs. This support is implemented via compile-time source generation and takes a dependency on invocation interceptors and endpoint filters.

Enabling and disabling validation support

Parameter validation can be enabled at the global level via a WithValidation invocation on an IEndpointConventionBuilder instance. Behind the scenes, this invocation serves as the source of an intercepted WithValidation call that will be generated by the source generator at compile-time.

var app = WebApplication.Create();

app.MapGet("/hello/{name}", ([MinLength(1)] string name) => $"Hello, {name}!")
	.WithValidation();

app.Run();

Since invoking WithValidation on every endpoint that requires validation can be tedious, it is also possible to enable parameter validation globally for all endpoints in the application by building on-top of the global conventions API proposed in #59755.

var app = WebApplication.Create();

app.Conventions.WithValidation();

app.MapGet("/hello/{name}", ([MinLength(1)] string name) => $"Hello, {name}!");
app.MapGet("/age/{birthDate}", (DateOnly birthDate) => )

app.Run();

When validation is enabled globally in an application, it is possible to disable it on a per-endpoint basis via the DisableValidation extension method.

var app = WebApplication.Create();

app.Conventions.WithValidation();

app.MapGet("/hello/{name}", ([MinLength(1)] string name) => $"Hello, {name}!")
	.DisableValidation();
app.MapGet("/age/{birthDate}", (DateOnly birthDate) => )

app.Run();

Discovering types that require validation

The implementation discovers types that are validatable by analyzing the parameter list for all handlers in the Map invocations of a given application. Types are considered validatable if they:

  • Implement the IValidateObject interface
  • Contain properties that include ValidationAttributes on them
  • Are enumerable that contain types that can be validated

Parameters are considered validatable if they contain ValidationAttributes on them.

Validation on polymorphic and inherited types
The implementation builds on top of the [JsonDerived] type attributes in System.Text.Json namespace to discover polymorphic variants of a type. The implementation accounts for base types that are validatable when generating Validate invocations.

Recursion depth checks
The implementation currently re-uses the JsonSerializerOptions.MaxDepth property for setting the max depth that should be respected when recurring in the validation hierarchy. For most cases, this might end up being moot because the JsonSerializer will kick on the MaxDepth being violated when serializing before the validation has the chance to kick in. This will have the biggest impact for types with custom binding sources that don't go through the serializer.

This choice is largely taken to avoid introduced new API, but we can consider an explicit and distinct max depth value specifically for the validation logic. This will require the introduction of a new ValidationOptions object somewhere in the Http namespace.

Areas to improvement

  • The implementation doesn't currently effectively cache parameter-level validation attributes and can be more aggressive about caching for property-level attributes with the same config. We can consider this as an optimization in the future.
  • The implementation currently applies on endpoints and types that can be discovered statically. We can consider fallback logic for endpoints that need to be discovered dynamically by including a fallback implementation in the WithValidation call.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Jan 9, 2025
@captainsafia captainsafia added area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant