Search code examples
.netasp.net-coreswaggerasp.net-core-webapiswagger-ui

How to Implement Polymorphic Request Bodies in ASP.NET Core Web API with Swagger UI?


I am developing an ASP.NET Core Web API where I need to allow clients to choose between multiple derived types when sending a PUT request.

I have set up polymorphism in my code using a base class and derived classes, and I am using custom JsonConverter to handle serialization. However, the Swagger UI is only displaying the option to send the body for the First type, and I need to enable a functionality that allows the user to select which specific type they want to send.

Here's how I have defined my models and controller:

// Base class and derived classes
public abstract class Base
{
    public Type Type { get; set; }
}

public enum Type
{
    First,
    Second
}

[JsonConverter(typeof(FirstConverter))]
public class First : Base
{
    public string Value { get; set; }
}

[JsonConverter(typeof(SecondConverter))]
public class Second : Base
{
    public string Data { get; set; }
}

// TestController
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpPut("/base")]
    public IActionResult First([FromBody] Base parameters)
    {
        return Ok(parameters);
    }
}

// Custom JsonConverter for polymorphism
public class TestJsonConverter : JsonConverter<Base>
{
    // Custom converter logic
}

// Swagger Configuration in Startup.cs or Program.cs
builder.Services.AddSwaggerGen(options =>
{
    options.UseOneOfForPolymorphism();
    options.UseAllOfForInheritance();
    options.SchemaFilter<NotificationSettingsSchemaFilter>();
});

But in Swagger UI, it looks like this:

enter image description here

As you can see, the UI is not providing an option to select between First or Second types for the request body. What am I missing in my Swagger setup to enable this functionality? How can I modify my Swagger configuration to allow users to choose between different types in the PUT request body in the UI?

Any help or pointers would be greatly appreciated!


Solution

  • What you are trying to do is possible using discriminators, which are documented here: https://github.com/domaindrivendev/Swashbuckle.AspNetCore#enrich-polymorphic-base-classes-with-discriminator-metadata.

    Applied to your solution:

    [SwaggerDiscriminator("type")]
    [SwaggerSubType(typeof(First), DiscriminatorValue = "first")]
    [SwaggerSubType(typeof(Second), DiscriminatorValue = "second")]
    public abstract class Base
    {
        public Type Type { get; set; }
    }
    

    This will cause the OpenAPI specification generated from you code to use the oneOf directive and the discriminator property, as per these reference: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism.

    However, I would very strongly urge you to consider exposing simple models over your API, rather than those which exhibit polymorphism. Supporting polymorphic models over your API is unnecessarily complex, and worse - I have seen consumer-side code generators struggle to correctly interpret the oneOf and discriminator keywords in OpenAPI specifications, and generate client code which won't work.