Skip to content

net10: a second IFormFile endpoint erases the Issue #216 file-part description document-wide #234

Description

@avgalex

Found while working on #232 (PR #233). Pre-existing, unrelated to that change.

Summary

On net10.0, adding a second endpoint that binds an IFormFile erases the Issue #216 file-part documentation (Allowed content types: …, Maximum file size: … bytes) from the whole document — including the first endpoint, which documented correctly when it was alone.

Affects MicroElements.AspNetCore.OpenApi.FluentValidation (native Microsoft.AspNetCore.OpenApi). net9.0 is not affected. Swashbuckle and NSwag are not affected.

Repro

Start from the package's test host, which has one upload endpoint:

app.MapPost("/api/upload", ([FromForm] UploadImageRequest request) => Results.Ok()).DisableAntiforgery();
// UploadImageRequest { IFormFile File }
// validator: RuleFor(x => x.File).NotNull().FileContentType("image/jpeg", "image/png").MaxFileSize(2 * 1024 * 1024);

/openapi/v1.json correctly contains:

"IFormFile": {
  "type": "string",
  "format": "binary",
  "description": "Allowed content types: image/jpeg, image/png. Maximum file size: 2097152 bytes."
}

Now add any second endpoint whose bound type contains an IFormFile — no validator, no [FromForm] attribute subtleties, a plain minimal API is enough:

public class SecondUploadRequest { public IFormFile Doc { get; set; } = default!; }

app.MapPost("/api/upload2", ([FromForm] SecondUploadRequest request) => Results.Ok()).DisableAntiforgery();

The component becomes:

"IFormFile": { "type": "string", "format": "binary" }

Both description notes are gone from the entire document. The existing test Issue216SpikeTests.FileContentType_And_MaxFileSize_Are_Documented_For_Upload_Endpoint fails.

Root cause

IFormFile is a single shared component per document. With one usage, the file property of UploadImageRequest is still an inline concrete OpenApiSchema when FluentValidationSchemaTransformer runs, so the FileContentType/MaxFileSize rules can write context.Property.Description, and the mutated instance is what later becomes the component.

With a second usage, Microsoft.AspNetCore.OpenApi promotes IFormFile to a component before the schema transformer reaches UploadImageRequest.file. The property is then an OpenApiSchemaReference, OpenApiSchemaCompatibility.GetProperty returns null, and OpenApiRuleContext.Property hands back a detached throwaway OpenApiSchema — the description is written to an object that is never serialized. This is the $ref contract from #176; the new part is that the number of usages elsewhere in the document decides whether a given endpoint gets documented.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions