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
Found while working on #232 (PR #233). Pre-existing, unrelated to that change.
Summary
On net10.0, adding a second endpoint that binds an
IFormFileerases 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(nativeMicrosoft.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:
/openapi/v1.jsoncorrectly contains:Now add any second endpoint whose bound type contains an
IFormFile— no validator, no[FromForm]attribute subtleties, a plain minimal API is enough:The component becomes:
Both description notes are gone from the entire document. The existing test
Issue216SpikeTests.FileContentType_And_MaxFileSize_Are_Documented_For_Upload_Endpointfails.Root cause
IFormFileis a single shared component per document. With one usage, thefileproperty ofUploadImageRequestis still an inline concreteOpenApiSchemawhenFluentValidationSchemaTransformerruns, so theFileContentType/MaxFileSizerules can writecontext.Property.Description, and the mutated instance is what later becomes the component.With a second usage,
Microsoft.AspNetCore.OpenApipromotesIFormFileto a component before the schema transformer reachesUploadImageRequest.file. The property is then anOpenApiSchemaReference,OpenApiSchemaCompatibility.GetPropertyreturnsnull, andOpenApiRuleContext.Propertyhands back a detached throwawayOpenApiSchema— the description is written to an object that is never serialized. This is the$refcontract from #176; the new part is that the number of usages elsewhere in the document decides whether a given endpoint gets documented.Notes
descriptionsibling next to the$ref(legal in OpenAPI 3.1) keeps it per-usage and is probably the right direction.encoding.contentType(also from Add support for media types #216) is not affected — it lives on the media type, not the schema, and stays correct in this scenario.