In this post, let's have a look at how Union types in C# 15 flow
through an ASP.NET Core minimal API and how they are described in
OpenAPI. This is a follow up to my previous post
C# 15: Brand New Union Types, so I will reuse the same Shape union.
Everything here runs on .NET 11 Preview 6 (latest as of today, it
will change) and the Microsoft.AspNetCore.OpenApi package for the built in OpenAPI
document generation.
Also using latest language features.
<LangVersion>preview</LangVersion>
Here is a single endpoint that returns a Shape.
using Microsoft.AspNetCore.Http.HttpResults; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); WebApplication app = builder.Build(); app.MapOpenApi(); app .MapGet("/shapes/{id}", Results<Ok<Shape>, NotFound> (int id) => { Dictionary<int, Shape> shapes = new() { { 1, new Circle(2) }, { 2, new Rectangle(3, 4) }, { 3, new Triangle(4, 5) } }; return shapes.TryGetValue(id, out var shape) ? TypedResults.Ok(shape) : TypedResults.NotFound(); }) .WithName("GetShape"); app.Run(); public record class Circle(double Radius); public record class Rectangle(double Width, double Height); public record class Triangle(double Base, double Height); public union Shape(Circle, Rectangle, Triangle);
With the union exposed, an endpoint that returns a union is described with
an anyOf schema listing each case type. Here is the OpenAPI document.
{
"paths": {
"/shapes/{id}": {
"get": {
"operationId": "GetShape",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Shape" }
}
}
},
"404": { "description": "Not Found" }
}
}
}
},
"components": {
"schemas": {
// The union is an "anyOf" over its case types.
"Shape": {
"type": "object",
"anyOf": [
{ "$ref": "#/components/schemas/Circle" },
{ "$ref": "#/components/schemas/Rectangle" },
{ "$ref": "#/components/schemas/Triangle" }
]
},
// Each case reuses its own standalone component, no "$type" discriminator.
"Circle": {
"type": "object",
"required": [ "radius" ],
"properties": {
"radius": { "type": "number", "format": "double" } }
},
"Rectangle": {
"type": "object",
"required": [ "width", "height" ],
"properties": {
"width": { "type": "number", "format": "double" },
"height": { "type": "number", "format": "double" }
}
},
"Triangle": {
"type": "object",
"required": [ "base", "height" ],
"properties": {
"base": { "type": "number", "format": "double" },
"height": { "type": "number", "format": "double" }
}
}
}
}
}
Note that unlike polymorphic types, union cases don't carry a
$type
discriminator.
A few limits apply in this preview. Only JSON request bodies and responses
are supported. Binding a union from the query string, route values, headers,
or form fields is not yet available (dotnet/aspnetcore #66648).
More read:
Hope this helps.
Happy Coding.
Regards,
Jaliya
