Search code examples
asp.net-coresdkswaggeropenapiswashbuckle.aspnetcore

How to Configure Swagger to Display Different Model Classes Based on Content Type in .NET API


I have a .NET API project with the following API endpoint, but the Swagger UI and the generated swagger.json file only show options for the PetJsonRequestInfo model class, regardless of the content type. I need to configure Swagger to correctly display different model classes based on the content type (multipart/form-data and application/json).

Here's the relevant code for my API endpoint:

public class PetRequestInfo
    {
    // File upload specific to multipart/form-data
    public IFormFile PetImage { get; set; }
    // Common properties
    public string PetName { get; set; }
    public int PetAge { get; set; }
    }

    public class PetJsonRequestInfo
    {
    // No file upload, JSON only
    // Common properties
    public string PetName { get; set; }
    public int PetAge { get; set; }
    }

    // POST: api/pets/add
    [HttpPost("add")]
    [Consumes("multipart/form-data", "application/json")]
     public async Task<ActionResult<PetAdded>> AddPet([FromForm] PetRequestInfo petRequestInfo,      [FromBody] PetJsonRequestInfo petJsonRequestInfo)
    {
     // Validate inputs
    if (petRequestInfo == null || petJsonRequestInfo == null)
    {
        return BadRequest("Invalid request data.");
    }
    // logic will be here...
    }

The Swagger file currently looks like this:

"/v1/Pet/add": {
    "post": {
        "summary": "Add a new pet.",
        "requestBody": {
            "description": "The pet request body.",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/PetJsonRequestInfo"
                    }
                },
                "multipart/form-data": {
                    "schema": {
                        "$ref": "#/components/schemas/PetJsonRequestInfo"
                    }
                },
                "application/x-www-form-urlencoded": {
                    "schema": {
                        "$ref": "#/components/schemas/PetJsonRequestInfo"
                    }
                }
            }
        },
        "responses": {
            "201": {
                "description": "Created",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/PetAdded"
                        }
                    }
                }
            },
            "401": {
                "description": "Unauthorized",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResult"
                        }
                    }
                }
            },
            "403": {
                "description": "Forbidden",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResult"
                        }
                    }
                }
            }
        }
    }
}

I want the Swagger documentation to reflect the correct model class based on the content type, like this. How can I achieve this? Any help would be greatly appreciated!

"requestBody": {
    "description": "The pet request body.",
    "content": {
        "application/json": {
            "schema": {
                "$ref": "#/components/schemas/PetJsonRequestInfo"
            }
        },
        "multipart/form-data": {
            "schema": {
                "$ref": "#/components/schemas/PetRequestInfo"
            }
        },
        "application/x-www-form-urlencoded": {
            "schema": {
                "$ref": "#/components/schemas/PetRequestInfo"
            }
        }
    }
}

Solution

  • You could implement CustomOperationFilter to achieve this. Such as following:

    Change operation when relavatePath is "api/pets/add"

        public class CustomOperationFilter : IOperationFilter
        {
            public void Apply(OpenApiOperation operation, OperationFilterContext context)
            {
                if (context.ApiDescription.RelativePath.Equals("api/pets/add", StringComparison.OrdinalIgnoreCase))
                {
                    operation.RequestBody.Content.Clear();
                    operation.RequestBody.Content.Add("application/json", new OpenApiMediaType
                    {
                        Schema = context.SchemaGenerator.GenerateSchema(typeof(PetJsonRequestInfo), context.SchemaRepository)
                    });
                    operation.RequestBody.Content.Add("multipart/form-data", new OpenApiMediaType
                    {
                        Schema = context.SchemaGenerator.GenerateSchema(typeof(PetRequestInfo), context.SchemaRepository)
                    });
                }
            }
        }
    

    Add the filter in program.cs

    builder.Services.AddSwaggerGen(c =>
    {
        c.OperationFilter<CustomOperationFilter>();
    });
    

    Create action using Dynamic type.

        [Route("api/[controller]")]
        [ApiController]
        public class ValuesController : ControllerBase
        {
            [HttpPost("test")]
    
            public void Post3(object requestBody)
            {
    
                if (JsonSerializer.Serialize(requestBody).Contains("petName"))
                {
                    PetJsonRequestInfo result = (PetJsonRequestInfo)requestBody;
                    ...
                }
                else{
    
                    PetRequestInfo result = (PetRequestInfo)requestBody;
                    ...
                }
            }
        }
    

    Test result
    enter image description here enter image description here