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 anyOf directive and the discriminator property, as per these reference: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism.

    However, I would urge you to define simple models for your API, rather than those which exhibit polymorphism. Supporting polymorphic models causes code generators to struggle to correctly interpret the anyOf and discriminator keywords in OpenAPI specifications, and generate client code which doesn't work.