0
votes

It seems swashbuckle\swagger-ui (5.6 - using swagger-ui) does not generate example XML correctly when the model has a property that is a list.

To see this issue:

1 - Create an empty webapi project (I'm using asp.net)

2 - Add a couple of example models (I went with Customer + Order for testing)

public class Customer
{
    public string AccountNumber { get; set; }
    [XmlArray("Orders"),XmlArrayItem("Order")]
    public List<Order> Orders { get;set; }
}

public class Order
{
    public string OrderNumber { get;set; }
}

3 - Create a controller using FromBody to bind to model

public class CustomerController : ApiController
{
    public void Post([FromBody]Customer customer)
    {
        customer.ToString();
    }
}

4 - Change web api config to allow simple XML

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Formatters.XmlFormatter.UseXmlSerializer = true;  //ADD THIS
    }
}

5 - Run site and using /swagger ui change parameter content type to xml and select example model. You will find the example is as follows.

<?xml version="1.0"?>
<Customer>
  <AccountNumber>string</AccountNumber>
  <Orders>
    <OrderNumber>string</OrderNumber>
  </Orders>
</Customer>

6 - Submit this with a breakpoint on the customer.ToString() line in the controller and you will find the Orders collection is empty

7 - Modify the XML in swagger-ui to the following and submit:

<?xml version="1.0"?>
<Customer>
  <AccountNumber>string</AccountNumber>
  <Orders>
    <Order><OrderNumber>string</OrderNumber></Order>
  </Orders>
</Customer>

8 - The Customer.Orders collection is now correctly populated.

What is the best way to fix or workaround this in Swashbuckle?

(There are a few discussions around this and whether it's a bug in swagger-ui or Swashbuckle, but I'm specifically interested in working around it using Swashbuckle)

4
I think this is github.com/swagger-api/swagger-ui/issues/4274, though your write up matches the problem I'm having a lot more clearly than the github issue. - Abacus

4 Answers

1
votes

I have found the following works:

1 - Add an implementation of ISchemaFilter

internal class ApplySchemaVendorExtensions : ISchemaFilter
{
    public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
    {
        // Fix issues with xml array examples not generating correctly
        if (!type.IsValueType)
        {
            schema.xml = new Xml { name = type.Name };
            if(schema.properties != null)
            {
                foreach (var property in schema.properties)
                {
                    //Array property, which wraps its elements
                    if (property.Value.type == "array")
                    {
                        property.Value.xml = new Xml
                        {
                            name = $"{property.Key}",
                            wrapped = true
                        };
                    }
                }
            }
        }
    }
}

2 - Comment this line into SwaggerConfig.cs

c.SchemaFilter<ApplySchemaVendorExtensions>();

Repeat the test in the Question and the example XML now works directly. As always I'm curious if there's a better solution...

EDIT: Actually this oddly works in the original project I have this issue, but in the small reproduction project for this Quesion it behaves slightly differently! I will edit this answer when I find why...

1
votes

Thanks to @mutex, but I found that I needed to make another adjustment to it:

internal class SwaggerFixArraysInXmlFilter : Swashbuckle.Swagger.ISchemaFilter
{
    // this fixes a Swagger bug that wasn't generating correct XML elements around List<> or array[] types
    public void Apply(Swashbuckle.Swagger.Schema schema, Swashbuckle.Swagger.SchemaRegistry schemaRegistry, System.Type type)
    {
        // Fix issues with xml array examples not generating correctly
        if (!type.IsValueType)
        {
            schema.xml = new Swashbuckle.Swagger.Xml { name = type.Name };
            if (schema.properties != null)
            {
                foreach (var property in schema.properties)
                {
                    //Array property, which wraps its elements
                    if (property.Value.type == "array")
                    {
                        property.Value.xml = new Swashbuckle.Swagger.Xml
                        {
                            name = $"{property.Key}",
                            wrapped = true
                        };
                        property.Value.items.xml = new Swashbuckle.Swagger.Xml
                        {
                            name = $"{property.Value.items.type}",
                            wrapped = true
                        };
                    }
                }
            }
        }
    }
}
0
votes

If you are using .Net Core 2.2 with Swagger v5, you will need the below code set

internal class SwaggerFixArraysInXmlFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            Type type = context.Type;

            // Fix issues with xml array examples not generating correctly
            if (!type.IsValueType)
            {
                schema.Xml = new OpenApiXml { Name = type.Name };
                if (schema.Properties != null)
                {
                    foreach (var property in schema.Properties)
                    {
                        //Array property, which wraps its elements
                        if (property.Value.Type == "array")
                        {
                            property.Value.Xml = new OpenApiXml
                            {
                                Name = $"{property.Key}",
                                Wrapped = true
                            };
                            property.Value.Items.Xml = new OpenApiXml
                            {
                                Name = $"{property.Value.Items.Type}",
                                Wrapped = true
                            };
                        }
                    }
                }
            }
        }
    }
0
votes

Thanks to @Abacus, but I found that I needed to make another adjustment to it. (String is not a ValueType, so it was renaming any string value to <String>string</String>... May have something to do with .NET Core 3.1)

internal class SwaggerFixArraysInXmlFilter : Swashbuckle.Swagger.ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        Type type = context.Type;

        // Fix issues with xml array examples not generating correctly
        if (!type.IsValueType && type.Name != "String")
        {
            schema.Xml = new OpenApiXml { Name = type.Name };
            if (schema.Properties != null)
            {
                foreach (var property in schema.Properties)
                {
                    //Array property, which wraps its elements
                    if (property.Value.Type == "array")
                    {
                        property.Value.Xml = new OpenApiXml
                        {
                            Name = $"{property.Key}",
                            Wrapped = true
                        };
                        property.Value.Items.Xml = new OpenApiXml
                        {
                            Name = $"{property.Value.Items.Type}",
                            Wrapped = true
                        };
                    }
                }
            }
        }
    }