Why does the OnDeserialization not fire for XML Deserialization? - c#

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.

Related

C# XmlSerializer conditionally serialialize List<T> items

i need to serialize and deserialize XML with C# XmlSerializer (or is there something better?).
[XmlElement]
public virtual List<Map> Maps { get; set; }
public class Map
{
[XmlAttribute("item")]
public string Item { get; set; }
[XmlAttribute("uri")]
public string Uri { get; set; }
}
Maps = new List<Map>{
new Map { Item="", Uri="" },
new Map { Item="something", Uri="foo" },
new Map { Item="", Uri="foo" },
}
The serializer should throw out every item with string.IsNullOrEmpty(map.Item) so that the resulting Xml only holds the map with "something".
How can I achieve this without a big hassle?:
<Maps>
<Map item="something" uri="foo" />
</Maps>
As far as I've understood, you want to filter your XML before you serialize it.
I suggest you use LINQ for this:
var filteredMaps = Maps.Where(map => !string.IsNullOrWhiteSpace(map.Item)).ToList();
Notice the .ToList() call at the end of the line. This is important, as your XmlSerializer is of type List<Map> I suppose. Put this line before you serialize your object and the result should look like this:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Map item="something" uri="foo" />
</ArrayOfMap>
Don't forget the using System.Linq;
Well you can try creating an XmlWriter that filters out all elements with an xsi:nil attribute or containing an empty string, and passes all other calls to the underlying standard XmlWriter to "clean up" serialized XML.

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; }
}

Deserialize XML Array Where Root is Array and Elements Dont Follow Conventions

The XML I am getting is provided by an outside source so I don't have the ability to easily reformat it. I would like to use xml attributes on my entities instead of having to write a linq query that knows how the XML and entity is formatted. Here is an example:
<?xml version="1.0"?>
<TERMS>
<TERM>
<ID>2013-2</ID>
<DESC>Spring 2013</DESC>
</TERM>
<TERM>
<ID>2013-3</ID>
<DESC>Summer 2013 Jun&Jul</DESC>
</TERM>
</TERMS>
I know the the XMLSerializer expects ArrayOfTerm instead of TERMS for example, but that I can tweak my entity to use a different element name with the xml attributes such as this:
public class TermData
{
[XmlArray("TERMS")]
[XmlArrayItem("TERM")]
public List<Term> terms;
}
public class Term
{
[XmlElement("ID")]
public string id;
[XmlElement("DESC")]
public string desc;
}
and I am deserializing the data like so:
TermData data;
XmlSerializer serializer = new XmlSerializer(typeof(TermData));
using (StringReader reader = new StringReader(xml))
{
data = (TermData)serializer.Deserialize(reader);
}
return View(data.terms);
The problem I am facing is that TERMS is the root and the array itself. If the XML were to have a root element that was not the array, I could edit my TermData class like so and it would deserialize correctly (already tested).
[XmlRoot("ROOT")]
public class TermData
{
[XmlArray("TERMS")]
[XmlArrayItem("TERM")]
public List<Term> terms;
}
Note that using TERMS as the XMLRoot does not work. Right now, my code is throwing
InvalidOperationException: There is an error in XML document (2,2).
InnerException: "<TERMS xmlns=" was not expected.
This would lead me to believe that the XML is not formatted correctly, but from my understanding the example I gave is perfectly valid XML.
This would all be trivial if I could edit the source xml, but there could be tons of other responses like this and I need to be able to flex for whatever I might get. What I'm trying to confirm is whether or not the XMLSerializer can support this type of XML structure. I've tested just about everything and can't get it deserialize without editing the XML. It would also be convenient if I didn't have to define a wrapper class (TermData) to hold the list, but this seems to only work if the xml follows the naming conventions for the serializer (ArrayOfTerm, etc).
Maybe you can try :
[XmlRoot("TERMS")]
public class TermData
{
public TermData()
{
terms = new List<Term>();
}
[XmlElement("TERM")]
public List<Term> terms{get;set;}
}
public class Term
{
[XmlElement("ID")]
public string id{get;set;}
[XmlElement("DESC")]
public string desc{get;set;}
}
Hope this will help,

XML Deserialization with C# .NET 3.5

I Have this XML File
<?xml version="1.0" standalone="yes"?>
<Root>
<Object>
<referenceName>People</referenceName>
<query>select * from people</query>
</Object>
<Object>
<referenceName>Countries</referenceName>
<query>select * from countries</query>
</Object>
</Root>
I need to convert into an object with C#.
I got confused how to do it.
Kindly note that I can have alot of objects in the xml file.
I know that i have to use an [XMLArray......
Thanks
The simplest trick here is at the VS command line:
xsd example.xml
xsd example.xsd /classes
Et voila; one example.cs file with example C# that shows how to get that xml from .NET objects via XmlSerializer.
In this case, I expect the following would work:
public class Root
{
[XmlElement("Object")]
public List<SomeOtherObject> Objects { get; set; }
}
public class SomeOtherObject
{
[XmlElement("referenceName")]
public string Name { get; set; }
[XmlElement("query")]
public string Query { get; set; }
}
update: validated it; yup, it works...
XmlSerializer ser = new XmlSerializer(typeof(Root));
using (XmlReader reader = XmlReader.Create(
new StringReader(xml)))
{
var obj = (Root)ser.Deserialize(reader);
// use obj
}
Use the xsd.exe tool to generate an initial set of classes to start with. Once you have those, tweak if needed (post the generated classes) and use System.Xml.Serialization.XmlSerializer to deserialize back into the runtime object.

Categories

Resources