Sunday, August 9, 2026

Support for C# 15 Union Types in ASP.NET Core

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.

When you run this, you can see something like this:
Union Types in ASP.NET Core
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

No comments:

Post a Comment