Skip to content

Preserve non-service parameters and harden POST binding source handling in OpenAPI metadata generation#730

Merged
KrzysztofPajak merged 7 commits into
developfrom
copilot/fix-frontend-api-parameters-ignored
Jul 5, 2026
Merged

Preserve non-service parameters and harden POST binding source handling in OpenAPI metadata generation#730
KrzysztofPajak merged 7 commits into
developfrom
copilot/fix-frontend-api-parameters-ignored

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Actions that include a [FromServices] parameter were losing request parameter metadata in generated OpenAPI descriptions (e.g., checkout save and account register endpoints). This change ensures service-injected parameters are excluded from API docs while request parameters remain correctly documented, and prevents invalid runtime/non-request parameter metadata from causing OpenAPI generation failures.

  • API description generation

    • Updated MetadataApiDescriptionProvider to skip service-bound parameters when building parameter descriptions for both GET and POST.
    • Added a dedicated service-parameter check that handles:
      • BindingSource.Services
      • explicit [FromServices] on controller parameters
    • Corrected POST parameter source mapping:
      • preserve explicit binding source when available
      • infer unbound simple parameters as query
      • infer unbound complex parameters as body
    • Hardened POST parameter source resolution to:
      • fall back to model metadata binding source when BindingInfo is missing
      • skip non-request binding sources
      • exclude runtime CancellationToken parameters from API parameter descriptions
  • Regression coverage

    • Added focused tests for MetadataApiDescriptionProvider:
      • POST action with [FromServices] + body model keeps only the body model parameter.
      • GET action with [FromServices] + query parameter keeps only the query parameter.
      • POST action with missing BindingSource on [FromServices] still excludes the DI parameter.
      • POST action with complex model + simple parameter maps to body + query sources correctly.
      • POST action with missing binding source on CancellationToken excludes the runtime parameter and keeps the request model.
apiDescription.ParameterDescriptions.Add(new ApiParameterDescription {
    Name = param.Name,
    ModelMetadata = GetModel(param),
    Source = parameterBindingSource,
    Type = param.ParameterType
});

Copilot AI linked an issue Jul 5, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits July 5, 2026 16:26
Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ignored action parameters in OpenApi documents Preserve non-service action parameters in OpenAPI metadata generation Jul 5, 2026
Copilot AI requested a review from KrzysztofPajak July 5, 2026 16:27
@KrzysztofPajak KrzysztofPajak marked this pull request as ready for review July 5, 2026 16:30
Copilot AI review requested due to automatic review settings July 5, 2026 16:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes OpenAPI/ApiExplorer metadata generation so that controller action parameters injected from DI ([FromServices] / BindingSource.Services) are excluded from generated API parameter documentation, while normal request parameters (body/query) remain documented.

Changes:

  • Updated MetadataApiDescriptionProvider to skip service-injected parameters when building parameter descriptions for both GET and POST.
  • Added IsServiceParameter helper to detect service parameters via BindingSource.Services and explicit [FromServices] attributes.
  • Added regression tests covering GET and POST actions that include a DI parameter plus a normal request parameter.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Modules/Grand.Module.Api/ApiExplorer/MetadataApiDescriptionProvider.cs Skips DI/service parameters when generating ApiExplorer/OpenAPI parameter descriptions.
src/Tests/Grand.Module.Api.Tests/ApiExplorer/MetadataApiDescriptionProviderTests.cs Adds regression tests ensuring [FromServices] does not suppress normal body/query parameters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sonarqubecloud

sonarqubecloud Bot commented Jul 5, 2026

Copy link
Copy Markdown

@KrzysztofPajak

Copy link
Copy Markdown
Member

@copilot why endpoint "/Account/Register" looks like this in
"post": {
"tags": [
"Account"
],
"parameters": [
{
"name": "X-CSRF-TOKEN",
"in": "header",
"required": true,
"schema": {
"type": "string",
"description": "Antiforgery token"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
v2.json open api
Where is the input model?

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot why endpoint "/Account/Register" looks like this in
"post": {
"tags": [
...

It was missing because our metadata provider treated all POST action parameters as body parameters; for Register(RegisterModel model, string returnUrl) that produced invalid metadata and the request model was dropped from the generated spec. I fixed this in 6d1db8c by preserving explicit binding sources and inferring unbound simple POST params (like returnUrl) as query while keeping complex params (the register model) as body.

Copilot AI changed the title Preserve non-service action parameters in OpenAPI metadata generation Preserve non-service parameters and correct POST binding sources in OpenAPI metadata generation Jul 5, 2026
@KrzysztofPajak

Copy link
Copy Markdown
Member

@copilot Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
 An unhandled exception has occurred while executing the request.
 System.NullReferenceException: Object reference not set to an instance of an object.
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<>c.b__33_0(ApiParameterDescription parameter)
 at System.Linq.Lookup2.Create(IEnumerable1 source, Func2 keySelector, IEqualityComparer1 comparer)
 at System.Linq.Enumerable.GroupByIterator2.GetCount(Boolean onlyIfCheap)  at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetFormRequestBody(OpenApiDocument document, IList1 supportedRequestFormats, IEnumerable1 formParameters, IList1 endpointMetadata, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetRequestBodyAsync(OpenApiDocument document, ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, HttpRequest httpRequest, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<b__0>d.MoveNext()

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
 An unhandled exception has occurred while executing the request.
 System.NullReferenceException: Objec...

Fixed in 398cf46. I hardened POST parameter source handling to avoid emitting unsupported binding sources into OpenAPI metadata and excluded BindingSource.Special parameters (non-request/runtime values) from generated API parameter descriptions. I also added regression tests for model-binding source fallback and special-source exclusion.

@KrzysztofPajak

Copy link
Copy Markdown
Member

@copilot problem still exist
Executed endpoint 'HTTP: GET /openapi/{documentName}.json'
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
 An unhandled exception has occurred while executing the request.
 System.NullReferenceException: Object reference not set to an instance of an object.
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<>c.b__33_0(ApiParameterDescription parameter)
 at System.Linq.Lookup2.Create(IEnumerable1 source, Func2 keySelector, IEqualityComparer1 comparer)
 at System.Linq.Enumerable.GroupByIterator2.GetCount(Boolean onlyIfCheap)  at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetFormRequestBody(OpenApiDocument document, IList1 supportedRequestFormats, IEnumerable1 formParameters, IList1 endpointMetadata, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetRequestBodyAsync(OpenApiDocument document, ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, HttpRequest httpRequest, CancellationToken cancellationToken)
 at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<b__0>d.MoveNext()
 --- End of stack trace from previous location ---
 at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F0399B8B15BE443123F4BECEE84B112C1912EE3F44D9DDD7BFA38E371FB917343__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<g__RequestHandler|5>d.MoveNext()
 --- End of stack trace from previous location ---
 at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
 at Grand.Web.Common.Middleware.ContextLoggingMiddleware.InvokeAsync(HttpContext context, IContextAccessor contextAccessor) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Middleware\ContextLoggingMiddleware.cs:line 32
 at Grand.Web.Common.Middleware.CultureSettingMiddleware.InvokeAsync(HttpContext context, IContextAccessor contextAccessor) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Middleware\CultureSettingMiddleware.cs:line 32
 at Grand.Web.Common.Middleware.ContextMiddleware.InvokeAsync(HttpContext context, IContextAccessor contextAccessor) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Middleware\ContextMiddleware.cs:line 50
 at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
 at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
 at Grand.Web.Common.Middleware.PoweredByMiddleware.InvokeAsync(HttpContext context, IPoweredByMiddlewareOptions options) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Middleware\PoweredByMiddleware.cs:line 23
 at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
 at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
 at Grand.Web.Common.Middleware.InstallUrlMiddleware.InvokeAsync(HttpContext context) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Middleware\InstallUrlMiddleware.cs:line 72
 at Microsoft.FeatureManagement.UseForFeatureExtensions.<>c__DisplayClass0_1.<b__1>d.MoveNext()
 --- End of stack trace from previous location ---
 at Grand.Web.Common.Infrastructure.ApplicationBuilderExtensions.<>c.<b__4_0>d.MoveNext() in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Common\Infrastructure\ApplicationBuilderExtensions.cs:line 83
 --- End of stack trace from previous location ---
 at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
 at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
 at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
 Request finished HTTP/2 GET https://localhost:44350/openapi/v2.json - 500 - application/problem+json 205.9424ms

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot problem still exist
Executed endpoint 'HTTP: GET /openapi/{documentName}.json'
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
...

Fixed in 9e7840b. I hardened MetadataApiDescriptionProvider so POST parameter binding now falls back to model metadata, skips any non-request binding sources, and excludes runtime CancellationToken parameters when binding info is missing. I also added a regression test for that missing-binding-source case.

Copilot AI changed the title Preserve non-service parameters and correct POST binding sources in OpenAPI metadata generation Preserve non-service parameters and harden POST binding source handling in OpenAPI metadata generation Jul 5, 2026
MetadataApiDescriptionProvider emitted ApiParameterDescription objects
without a ParameterDescriptor, which OpenApiDocumentService.GetFormRequestBody
dereferences when grouping form parameters, crashing /openapi/v2.json with
a NullReferenceException. Populate ParameterDescriptor on every emitted
description and add regression tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KrzysztofPajak KrzysztofPajak merged commit 746dd9f into develop Jul 5, 2026
4 of 6 checks passed
@KrzysztofPajak KrzysztofPajak deleted the copilot/fix-frontend-api-parameters-ignored branch July 5, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend Api action parameters ignored

3 participants