Reputation: 1783
I have implemented Swagger/Swashbuckle in my AspNetCore 2.1 app and it's working great. However some of my API models are based on complex WCF XML services and use a few System.Xml.Serialization annotations for adding namespaces and changing the names of properties. When these models are viewed on the Swagger page they are missing the namespaces and ignore any attribute name changes. Therefore the swagger default requests won't deserialize when posted to my controller. On the other hand the JSON requests work fine.
Consider these two classes;
public class test
{
[System.Xml.Serialization.XmlElementAttribute(Namespace = "http://www.example.com/ns/v1")]
public test_c ct1 { get; set; }
public string t2 { get; set; }
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.example.com/ns/v1")]
public class test_c
{
[System.Xml.Serialization.XmlElementAttribute("my-new-name")]
public string tc1 { get; set; }
public string tc2 { get; set; }
}
When serialized as XML we get something like;
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ct1 xmlns="http://www.example.com/ns/v1">
<my-new-name>aaa</my-new-name>
<tc2>xxxxxx</tc2>
</ct1>
<t2>bbb</t2>
</test>
This is what is the xml that is expected as the request. However, the swagger sample request shows as;
<?xml version="1.0" encoding="UTF-8"?>
<test>
<ct1>
<tc1>string</tc1>
<tc2>string</tc2>
</ct1>
<t2>string</t2>
</test>
Which will not deserialize when posted.
So now to the point. All I need/hope to do is modify the swagger request xml schema -- while not affecting the JSON request schema (I don't even know if they are -or can be- separate). I thought this would be simple but I'm lost sea of swashbuckle options & setup. I was hoping that I could simply assign the aspnet xml serializer to deserialize a provided request object. Or implement an ISchemaFilter or IOperationsFilter?
If someone could point me in the right direction I'd be forever grateful.
Upvotes: 2
Views: 2697
Reputation: 1783
ok, well I'll answer this myself.
I did end up implementing ISchemaFilter. So to answer this question using the same models as in the question, I first created my own implementation of ISchemaFilter and just hardcoded in the checks for the required changes (in reality i'm going to have a big dictionary<> of class and property changes).
The "Schema" class allows us to add XML only meta to the model class or any of its properties.
public class MyRequestISchemaFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Type == "object"){
if (context.SystemType == typeof(test_c))
{
schema.Xml = new Xml()
{
Namespace = "http://www.example.com/ns/v1"
};
foreach (var prop in schema.Properties)
{
if (prop.Key == "tc1")
{
prop.Value.Xml = new Xml()
{
Name = "my-new-name"
};
}
}
}
}
}
}
Then we wire-up this filter in our AddSwaggerGen service configure call at startup.
services.AddSwaggerGen(c =>
{
c.SchemaFilter<MyRequestISchemaFilter>();
...
Here is the sample request XML that the filter will produce;
<?xml version="1.0" encoding="UTF-8"?>
<test>
<ct1 xmlns="http://www.example.com/ns/v1">
<my-new-name>string</my-new-name>
<tc2>string</tc2>
</ct1>
<t2>string</t2>
</test>
It's missing the root level XMLSchema namespaces but the Schema::Xml prop doesn't support multiple namespaces. In any case the XML deserializes fine as long as I don't use those namespaces. I might be able to add them if I add properties with a namespace then set them as Attributes of the root element (which the Xml prop does handle). Haven't tried that though.
Upvotes: 2