Summary
During a documentation build for a high-traffic API, we encountered a recurring issue where OpenAPI/Swagger UI failed to generate XML examples for collection-based responses. The error "XML example cannot be generated; root element name is undefined" surfaced because the schema generator could not determine the appropriate root node for IEnumerable<T> or Collection<T> types when switching between media types. While we attempted to intercept this using IOpenApiSchemaTransformer, we discovered that the transformer lifecycle triggers multiple times per type, making it difficult to pinpoint exactly when the XML media type is being processed versus JSON.
Root Cause
The failure stems from three interconnected architectural behaviors in the .NET 10 OpenAPI implementation:
- Schema Polymorphism vs. Media Type Specificity: The OpenAPI generator creates schemas based on the requested content type. When a request specifies
application/vnd.example.weatherforecast+xml, the transformer is invoked to refine the schema, but the context often lacks the explicit “root element” metadata required for valid XML serialization. - Redundant Transformer Invocations:
IOpenApiSchemaTransformer.TransformAsyncis not called once per type; it is called per media type combination defined in the endpoint. This causes a “multi-pass” execution where the state ofOpenApiSchemaTransformerContextshifts depending on whether the engine is resolving the JSON schema or the XML schema. - Collection Type Erasure: For collections, the underlying
JsonTypeInfo.Typeprovides the generic collection type, but the XML serializer requirements demand a specific named root element which is not natively present in the standard OpenAPI schema object for collections.
Why This Happens in Real Systems
In complex enterprise environments, we rarely serve a single application/json endpoint. Real systems use Content Negotiation to support:
- Versioned Media Types: Using custom vendor types (e.g.,
application/vnd.api.v1+xml) to manage breaking changes. - Legacy Integration: Providing XML support for older SOAP-adjacent clients while moving to JSON for modern web frontends.
- Contract-First Requirements: Where the documentation must strictly match the XML serialization boundaries of the actual response payload.
Because the OpenAPI engine treats each media type as a distinct “view” of the same data model, the transformation logic must be context-aware, which adds significant complexity to the middleware pipeline.
Real-World Impact
- Broken Client SDKs: Automatically generated clients (via NSwag or Kiota) fail to instantiate the root collection object because the schema lacks the required XML element name.
- Developer Friction: API consumers see “undefined” errors in Swagger UI, leading to incorrect assumptions about the API’s capability to return XML.
- Integration Delays: Downstream teams attempting to implement XML parsing logic fail during integration testing because the contract documentation is structurally invalid.
Example or Code
public class XmlCollectionSchemaTransformer : IOpenApiSchemaTransformer
{
public async Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
// Check if we are dealing with a collection type
bool isCollection = typeof(IEnumerable).IsAssignableFrom(context.JsonTypeInfo.Type)
&& context.JsonTypeInfo.Type != typeof(string);
if (isCollection)
{
// We must detect if the current transformation context is for XML
// In .NET 10, we look at the context to see if we need to inject
// the missing XML root element metadata.
var rootElementName = context.JsonTypeInfo.Type.Name; // Simplified logic
if (schema.Example == null)
{
schema.Description += $" (XML Root: {rootElementName})";
// Manually injecting example logic for XML media types
// would happen here based on context-specific detection.
}
}
await Task.CompletedTask;
}
}
How Senior Engineers Fix It
To resolve this, a senior engineer moves away from “guessing” the media type and moves toward explicit context inspection:
- Media Type Detection: Instead of just looking at
JsonTypeInfo.Type, inspect thecontextto identify the specific Content-Type being processed. If the context indicates an XML-specific pass, inject theXmlRootattribute metadata into theOpenApiSchema. - Idempotency Guards: Implement a caching mechanism or a “visited” flag within the transformation lifecycle to prevent the transformer from performing expensive computations multiple times for the same type across different media types.
- Schema Decorators: Instead of a generic transformer, use specialized Schema Decorators that specifically target
Collection<T>types and manually populate theOpenApiSchema.Xmlproperties (if available in the specific implementation) or thedescriptionfield to guide the UI.
Why Juniors Miss It
- Assuming Single-Pass Execution: Juniors often assume that if a type is defined once, the transformer runs once. They fail to realize that one type + three media types = three transformations.
- Focusing Only on the Data Model: They focus on the
TinsideCollection<T>but overlook the container requirements of the XML format itself. - Ignoring the Serialization Context: They attempt to solve the problem by changing the class definition (adding
[XmlRoot]) rather than understanding how the OpenAPI Generator maps those attributes to the documentation schema.