XML&WCF POST request: some parameters are read, other not - c#

I have a wcf restful service on a IIS server.
I have made some API, which can be called sending both xml or json.
I've made my C# classes and then, I'm testing it. With JSON is perfect, but I have still some issues with XML request.
I want to send the xml with a post and this is the xml I send:
<?xml version="1.0" encoding="utf-8" ?>
<SetClientiXML
xmlns="http://tempuri.org/">
<dati>
<ArrayOfWrapClienti
xmlns="http://schemas.datacontract.org/2004/07/MultipayOnline"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<WrapClienti>
<CODRETE>0018</CODRETE>
<CODICE>20685</CODICE>
<NOME>A.T.E.R. Azienda Territoriale</NOME>
<INDIRIZZO>PIAZZA POZZA</INDIRIZZO>
<CITTA>Verona</CITTA>
<CAP>37123</CAP>
<PROV>VR</PROV>
<CODICEFISCALE>00223640236</CODICEFISCALE>
<PIVA>223640236</PIVA>
<EMAIL/>
<ESPOSIZ_CONTABILE>937,02</ESPOSIZ_CONTABILE>
<STATO>FALSE</STATO>
</WrapClienti>
</ArrayOfWrapClienti>
</dati>
<retista>3303903</retista>
<hashedString>oklkokokokok</hashedString>
</SetClientiXML>
the wcf read well "retista" and "hashedString", but "dati" is empty (0 elements), while I expect it has got the "wrapClienti" object I sent.
This is the prototype of my API:
[OperationContract]
[WebInvoke(UriTemplate = "SetClienti.xml", Method = "POST", BodyStyle=WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Xml)]
GenericResponse SetClientiXML(List<WrapClienti> dati, string retista, string hashedString);
So, the problem is that the List is empty.. why? How can I write the xml to make readable the list?
Ask me if I can give to you more details.
UPDATE: More weird!!
With this xml:
<?xml version="1.0" encoding="utf-8" ?><SetClientiXML xmlns="http://tempuri.org/">
<dati>
<WrapClienti xmlns="http://schemas.datacontract.org/2004/07/MultipayOnline" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<CODRETE>0018</CODRETE>
<CODICE>20685</CODICE>
<NOME>A.T.E.R. Azienda Territoriale</NOME>
<INDIRIZZO>PIAZZA POZZA</INDIRIZZO>
<CITTA>Verona</CITTA>
<CAP>37123</CAP>
<PROV>VR</PROV>
<CODICEFISCALE>00223640236</CODICEFISCALE>
<PIVA>223640236</PIVA>
<EMAIL/>
<ESPOSIZ_CONTABILE>937,02</ESPOSIZ_CONTABILE>
<STATO>FALSE</STATO>
</WrapClienti>
</dati>
<retista>3303903</retista>
<hashedString>oklkokokokok</hashedString>
</SetClientiXML>
the wcf read some attributes of the List, and other.. are nul!!!
I my WrapClienti I have a lof of attributes. Two of them are:
private string p_CAP { get; set; }
public string CAP
{
get
{
if (model == null)
return p_CAP.ToSafeString();
else
return this.model.CAP.ToSafeString();
}
set { p_CAP = value; }
}
private string p_PROV { get; set; }
public string PROV
{
get
{
if (model == null)
return p_PROV.ToSafeString();
else
return this.model.PROV.ToSafeString();
}
set { p_PROV = value; }
}
the problem is, with the xml above and with two breakpoint on the two set methods, only the set of PROV is called and, the one of CAP, not!!! Why? Now I'm really getting crazy... why this behavior??
Solution here.

This has to do with the ordering of the fields in your XML. It sounds very strange, but WCF DataContractSerializer is really fussy about the order in which the fields are encountered in the XML, but even worse, also in comparison to how they are defined in the code.
You see, the serializer wants the fields to be defined in alphabetical order, and if you serialize an instance of your class, you will find that the resulting XML fields are in alphabetical order. However, on deserialization, the serializer finds that the type you want to deserialize to has the fields defined in the "wrong" order. In this situation the behavior can seem random, but I think it has something to do with the fact that CAP should be the first field encountered, whereas PROV should be the last field, alphabetically.
So you have two options:
Reorder your XML and the fields in your class to be in alphabetical order, or
Decorate your class members with the DataMemeber property, and define the order of serialization.
You can do 2 like this:
[DataContract]
public class WrapClienti
{
[DataMember(Order=1)]
public string CAP { get; set; }
[DataMember(Order=2)]
public string PROV { get; set; }
...etc
}

Related

AspNetCore Swagger/Swashbuckle how to modify xml schema requests

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.
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.

web api list serialization

I want to serialize a list to xml (from a web-api method).
public class Result
{
public List<string> Users { get; set; }
}
So I get for example:
<result>
<user>Paul</user>
<user>David</user>
<user>Joan</user>
</result>
So far, I get:
<result>
<users>
<user>Paul</user>
<user>David</user>
<user>Joan</user>
</users>
</result>
How do I tell the serialization not to wrap the user list in a "users" tag?
Thanks.
You could either derive from XmlObjectSerializer and implement your own XML Serializer (see here FMI) or else manipulate your type so it works with the default formatter. Which isn't a great solution, but may work for a simple example, like so:
public class Result : List<User>
{
//Any user added to Result will be nested directly within Result in the XML
}
Further reading:
MSDN: XmlObjectSerializer Class:
"Extend the XmlObjectSerializer to create your own serializer to serialize and deserialize objects."
Insights on WCF: CustomXmlObjectSerializer: Real-world example with source code.
You need to replace default DataContractSerializer with XmlSerializer in Application_Start method.
For whole project:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
For specific type:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.SetSerializer<Result>(new XmlSerializer(typeof(Result)));
After this you can use attributes to format your xml output:
public class Result
{
[XmlElement("user")]
public List<string> Users { get; set; }
}

Deserializing xml that contains an invalid data type

I have an XML file that I need to deserialize into an object similar to this one:
public class TestObject
{
public string Name { get; set; }
public int Size { get; set; }
public TestObject()
{
Name = string.Empty;
Size = 0;
}
}
My deserialize method looks like this:
private TestObject DeserializeConfiguration(string xmlFileName)
{
XmlSerializer deserializer = new XmlSerializer(typeof(TestObject));
TextReader textReader = new StreamReader(xmlFileName);
TestObject testObj = (TestObject)deserializer.Deserialize(textReader);
textReader.Close();
return testObj;
}
This is working well enough for me but on occasion, I get an XML file that may contain an invalid data type (by "invalid", I mean with respect to the type of the object property that it should map to). For example, if my XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<TestObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Orion</Name>
<Size>abc</Size>
</TestObject>
Obviously I can't convert "abc" into the integer Size property of my object. When I attempt to deserialize this, I see an InvalidOperationException and, not surprisingly, the InnerException is "Input string was not in a correct format". Is it possible to catch this error, use a default value for that property of my object and continue deserializing the remainder of the XML file? If not, can anyone tell me if there's a generally regarded "best practice" for handling invalid data during deserialization?
What you would need to do is to validate the incoming XML before deserializing. Basically you want to avoid having to process badly-formed XML. After validation the deserializer can at least be sure that all incoming XML will be deserializable.
You can create an XML Schema that contains the definition of valid XML in your case, and then validate the incoming XML with the XSD (XML Schema Definition) first (see also http://www.codeguru.com/csharp/csharp/cs_data/xml/article.php/c6737/Validation-of-XML-with-XSD.htm for more details).
Good luck!

Problem with C# XmlSerialization

I have xml file:
<?xml version="1.0" encoding="utf-8"?>
<LabelTypesCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance="xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LabelTypes>
<LabelType>
<Name>LabelTypeProduct</Name>
</LabelType>
<LabelType>
<Name>LabelTypeClient</Name>
</LabelType>
</LabelTypes>
</LabelTypesCollection>
And 2 c# classes:
[Serializable]
[XmlRoot("LabelTypesCollection")]
public class LabelTypesCollection
{
private static string _labelTypesCollectionPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.Combine(Program.ProgramName, "LabelTypesCollection.xml"));
[XmlArray("LabelTypes", ElementName="LabelType")]
public List<LabelType> LabelTypes { get; set; }
public static LabelTypesCollection LoadAllLabelTypes()
{
FileInfo fi = new FileInfo(_labelTypesCollectionPath);
if (!fi.Exists)
{
Logger.WriteLog("Could not find size_types_collection.xml file.", new Exception("Could not find size_types_collection.xml file."));
return new LabelTypesCollection();
}
try
{
using (FileStream fs = fi.OpenRead())
{
XmlSerializer serializer = new XmlSerializer(typeof(LabelTypesCollection));
LabelTypesCollection labelTypesCollection = (LabelTypesCollection)serializer.Deserialize(fs);
return labelTypesCollection;
}
}
catch (Exception ex)
{
Logger.WriteLog("Error during loading LabelTypesCollection", ex);
return null;
}
}
}
[Serializable]
public class LabelType
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlIgnore]
public string TranslatedName
{
get
{
string translated = Common.Resources.GetValue(Name);
return (translated == null) ? Name : translated;
}
}
}
And when I call:
LabelTypesCollection.LoadAllLabelTypes();
I get LabelTypeCollection object with empty LabelTypes list. There is no error or anything. Could anyone point me to the problem?
Change this
[XmlArray("LabelTypes", ElementName="LabelType")]
to this
[XmlArray]
The ElementName of an XmlArrayAttribute specifies the element name of the container, and is actually what you specify in the first parameter to the ctor! So the ctor you have says "this class serializes as a container named LabelTypes; no wait actually I want the container to be named LabelType". The named parameter is overwriting what the first unnamed parameter says.
And in fact, since you want the container element to be named LabelTypes, which is what the member is actually called, you don't need to specify it at all.
You may have been thinking of XmlArrayItemAttribute, which controls what the individual members of a serialized collection are named - but you don't need that here either.
My usual approach for working out xml serializer stuff is to build objects manually then look at the xml they serialize to. In this case, using the code you currently have produces xml like this:
<?xml version="1.0" encoding="utf-16"?>
<LabelTypesCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LabelType>
<LabelType>
<Name>one</Name>
</LabelType>
<LabelType>
<Name>two</Name>
</LabelType>
</LabelType>
</LabelTypesCollection>
which is what tipped me off to the incorrect LabelType specifier.
Note that you also don't need the XmlRoot on LabelTypesCollection, or the XmlElement on Name, since you are just specifying what the xml serializer will come up with anyway.
Here's a suggestion.
Write a small test program that creates an instance of LabelTypesCollection, and adds some LabelType objects into it.
Then use an XmlSerializer to write the object to a file, and look at the Xml you get, to ensure that your input Xml is in the correct schema.
Maybe there's something wrong with one of your Xml elements.
I really think you get an empty list because your code can't find the xml file. Also try instantiating your list. If you have the xml path correctly.
public List<LabelType> LabelTypes = new List<LabelType>();

Why does the OnDeserialization not fire for XML Deserialization?

I have a problem which I have been bashing my head against for the better part of three hours. I am almost certain that I've missed something blindingly obvious...
I have a simple XML file:
<?xml version="1.0" encoding="utf-8"?>
<WeightStore xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Records>
<Record actual="150" date="2010-05-01T00:00:00" />
<Record actual="155" date="2010-05-02T00:00:00" />
</Records>
</WeightStore>
I have a simple class structure:
[Serializable]
public class Record
{
[XmlAttribute("actual")] public double weight { get; set; }
[XmlAttribute("date")] public DateTime date { get; set; }
[XmlIgnore] public double trend { get; set; }
}
[Serializable]
[XmlRoot("WeightStore")]
public class SimpleWeightStore
{
[XmlArrayAttribute("Records")]
private List<Record> records = new List<Record>();
public List<Record> Records { get { return records; } }
[OnDeserialized()]
public void OnDeserialized_Method(StreamingContext context)
{
// This code never gets called
Console.WriteLine("OnDeserialized");
}
}
I am using these in both calling code and in the class files:
using System.Xml.Serialization;
using System.Runtime.Serialization;
I have some calling code:
SimpleWeightStore weight_store_reload = new SimpleWeightStore();
TextReader reader = new StringReader(xml);
XmlSerializer deserializer = new XmlSerializer(weight_store.GetType());
weight_store_reload = (SimpleWeightStore)deserializer.Deserialize(reader);
The problem is that I am expecting OnDeserialized_Method to get called, and it isn't.
I suspect it might have something to do with the fact that it's XML deserialization rather than Runtime deserialization, and perhaps I am using the wrong attribute name, but I can't find out what it might be.
Any ideas, folks?
There's no equivalent of OnDeserialized for XML deserialization.
See this post for workarounds: How do you find out when you've been loaded via XML Serialization?
The only way you could do that in a graceful way is to manually implement IXmlSerializable, which is not fun. Simply; XmlSerializer doesn't support serialization callbacks.
Sometimes, though, you can switch to DataContractSerializer, which still offers xml capabilities but which does support serialization callbacks. Unfortunately the xml options are limited - it won't work for you xml structure, since that uses attributes (DataContractSerializer only supports elements).
You might also look at the comments on this answer, which discusses the points from this.

Categories

Resources