I am trying to deserialize an xml list to a generic List in C#. I do not want to use XmlAttributes as the solution needs to be generic. I must be getting close as I am getting 3 Item objects but they are all populated with default values, ie: null or 0.
This is not a duplicate of how to deserialize an xml node with a value and an attribute using asp.net serialization which uses XmlAttributes and is not an option. This is clearly stated in the question.
There are a number of SO answers out there whicha I have tried and are similar to what I need, including the other list of SO responses in the [Duplicate]. The code below is modified from one of those answers but does not quite work.
<?xml version="1.0" encoding="utf-8"?>
<root>
---snip---
<Items>
<Item ItemId="1" ItemName="TestName1" Number="100" Created=""></Item>
<Item ItemId="2" ItemName="TestName2" Number="200" Created=""></Item>
<Item ItemId="3" ItemName="TestName3" Number="300" Created=""></Item>
</Items>
</root>
========================================================
var sourceData = XDocument.Parse(xml);
public List<T> GetObjectList<T>(string identifier) where T : class, new()
{
if (sourceData != null && !identifier.isNullOrEmpty())
{
var list = sourceData.Descendants(identifier)
.Select(p => DeserializeObject<T>(p.ToString()))
.ToList();
return list;
}
return new List<T>();
}
public static T DeserializeObject<T>(string xml) where T : class, new()
{
if (string.IsNullOrEmpty(xml))
{
return default(T);
}
try
{
using (var stringReader = new StringReader(xml))
{
var serializer = new XmlSerializer(typeof(T));
return (T) serializer.Deserialize(stringReader);
}
}
catch
{
return default(T);
}
}
public class Item
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public int? Number { get; set;}
public DateTime? Created { get; set; }
}
// RESULT
// Item ItemId="0" ItemName="null" Number="null" Created="null"
// Item ItemId="0" ItemName="null" Number="null" Created="null"
// Item ItemId="0" ItemName="null" Number="null" Created="null"
Check out the XmlSerialzer constructor, you can specify attribute overrides there, if you do not want to modify the class -- you'll need some data defining what you serialize at some point; the XmlSerializer was never written to work without these Attributes, or types that are known to be serializable (numbers, strings, ...).
If you want to be generic, you'll have to write your own serializer, which uses reflection to decide what to serialize, how, and in which order, and what to ignore. You can use the XmlSerializer in it when serializing types that have the necessary attributes. Search for tutorials explaining how to implement IXmlSerializable, they will show you how to use XmlWriter / XmlReader to avoid handling the XML syntax yourself.
A guess why it is like this: Circular references make (de-)serialization really hard, and a generic solution would probably try to serialize this without coming to an end, causing weird bugs with the call hanging forever, when some developer changes one of those classes without being able to see (due to no attribute being there) that they have to write the class in a way avoiding serialization problems.
I asked another similar question (A Better XElement to Object Without XMLAttributes in C#) when this one got marked as a duplicate (for a while) and ended up creating a simple custom de-serializer that met my needs. See the answers there for the code if interested.
Related
I have to work on a application when sends XML back. Because the root is always the "Reply", but the property's differ, deserializing can't be based on 1 object type.
I've now written code which loads the Xml first in a new XmlDocument, reads a Name attribute and based on the attribute, I try to deserialize it. Are there
better ways?
Example xml I can expect:
<Reply Name="GetModulesList" Result="yes"><ModuleName="xxxxxx.exe" Path="\Debug\xxxxxxx.exe" Order="1"/></Reply>
<Reply Name="OpenRecipe" Result="yes"/>
How whould you solve this?
XmlDocument doc = new XmlDocument();
doc.LoadXml(trimmedPart);
if (doc.DocumentElement?.Attributes != null)
{
XmlAttribute name = doc.DocumentElement.Attributes.Cast<XmlAttribute>().SingleOrDefault(a => String.Compare(a.Name, "name", StringComparison.OrdinalIgnoreCase) == 0);
replyName = name?.Value;
}
if (!string.IsNullOrEmpty(replyName))
{
XmlSerializer serializer = new XmlSerializer(GetSerializerObjectType(replyName));
using (StringReader reader = new StringReader(trimmedPart))
{
object obj = serializer.Deserialize(reader);
if (obj != null) returnList.Add(obj);
}
}
I recently dealt with a similar scenario. Here's a very rough outline, adapted from what I was working with to your case as specifically as I can.
My assumption is that you're going to do different things with different types. If it's an OpenRecipe you're going to do one thing, if it's GetModuleList you're going to do something else.
The biggest difference is that each "handler" I worked with for different types might handle completely different types of data - XML, JSON, even Excel, so the handlers received content in the form of a byte array and were responsible for deserializing it. (It wasn't my idea to write an in-house version of Biztalk.)
In this case, if it's always XML, you can
Determine the type of the inner XML from the Name attribute
Deserialize the inner XML to that type
Pass it off to a strongly-typed class
This class is concerned with deserializing the Reply (except for the unknown inner content) and passing it to something that will handle that inner content in a more strongly typed way:
public class XmlReplyRouter
{
private readonly IReplyTypeMapper _replyTypeMapper;
private readonly IHandlerFactory _handlerFactory;
private readonly XmlSerializer _serializer = new XmlSerializer(typeof(Reply));
public XmlReplyRouter(
IReplyTypeMapper replyTypeMapper,
IHandlerFactory handlerFactory)
{
_replyTypeMapper = replyTypeMapper;
_handlerFactory = handlerFactory;
}
public void RouteReply(string replyXml)
{
using (var reader = new StringReader(replyXml))
{
var reply = (Reply)_serializer.Deserialize(reader);
var replyType = _replyTypeMapper.GetReplyType(reply.Name);
var handler = _handlerFactory.GetHandler(replyType);
handler.HandleReply(reply);
}
}
}
public class Reply
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Result { get; set; }
[XmlAnyElement]
public XmlNode InnerXml { get; set; }
public string XmlContent => InnerXml?.OuterXml;
}
That's where all the ugliness goes.
The XmlAnyElement attribute allows us to deserialize without knowing what to make of that inner content. Once we know what type to use we can separately deserialize that.
The implementations of IReplyTypeMapper and IHandlerFactory could be anything. In my case I couldn't use a DI container so it contained a Dictionary<string, IReplyHandler> and selected the correct one based on the name. (The terminology I worked with was different, but same concept.)
The first determines the type (from the "name" attribute) and the second returns a handler for that type.
public interface IReplyTypeMapper
{
Type GetReplyType(string replyName);
}
public interface IHandlerFactory
{
IReplyHandler GetHandler(Type contentType);
}
Finally, here's the interface and a base class for the reply handlers.
As you can see, the base class doesn't really do anything. It just bridges the gap between object and generic types so that we can write strongly-typed handlers.
public interface IReplyHandler
{
void HandleReply(object content);
}
public abstract class BaseHandler<T> : IReplyHandler
{
private readonly XmlSerializer _serializer = new XmlSerializer(typeof(T));
public void HandleReply(object content)
{
HandleReplyContent((T)content);
}
protected abstract void HandleReplyContent(T content);
}
As you can see it's very similar to what you're doing. The objective, though, is that once I've gotten this minimal amount of initial code out of the way, everything else is a strongly-typed class like this:
public class SomeSpecificReplyHandler : BaseHandler<SomeSpecificReply>
{
// Maybe these have dependencies of their own. That's easiest if
// everything gets resolved from an IoC container.
protected override void HandleReplyContent(SomeSpecificReply content)
{
// do whatever
}
}
I don't know if this might be overkill for your needs. My intent was to start from an entry point where my data could be just about anything and quickly get out of there to a place where all of my code was strongly-typed and easy to test. All of these classes are testable (I wrote a few while typing this up) the individual handlers would also be easy to test.
This post contains some other details about implementing the factories without an IoC container. In my case I couldn't work with an IoC container, so I had to "manually" compose all of my classes. The inner implementation of the factory was just a dictionary. That way if I had the option of switching to an IoC container later I could replace the whole thing with DI registrations.
In the database, we have an xml field that contains 2 validation schemas; the old one does not have a namespace, the new one does. The reason for this is that we had to version one of the properties. Here is an example of the differences:
Version 1
<PropertyA>
<PropertyA1>false</PropertyA1>
<PropertyA2>3.23</PropertyA2>
</PropertyA>
Version 2
<ts:PropertyA xmlns:ts="http://www.example.com/v2">
<ts:PropertyA1>false</ts:PropertyA2>
<ts:PropertyA2>
<ts:PropertyA2a>
<ts:PropertyA2a1>City 1</ts:PropertyA2a1>
<ts:PropertyA2a2>3.23</ts:PropertyA2a2>
</ts:PropertyA2a>
<ts:PropertyA2b>
<ts:PropertyA2b1>City 2</ts:PropertyA2b1>
<ts:PropertyA2b2>1.21</ts:PropertyA2b2>
</ts:PropertyA2b>
</ts:PropertyA2>
</ts:PropertyA>
Basically, we just create multiple options for PropertyA2...
So now the isue is deserialization. This object needs to be deserialized into the same data object in the app code and the problem is that the element name is the same so the serializer is obviously having trouble figuring out which object to deserialize into since sometimes the database will return Version 1 and sometimes it will return Version 2.
Here is an example of the data class being used for serialization and my current approach that isn't quite working:
[Serializable]
public class MyDataClass
{
// ... other stuff
[XmlElement(Name = "PropertyA", typeof(V1.PropertyA), Namespace = "")]
public V1.PropertyA PropertyAV1 { get ;set; }
[XmlElement(Name = "PropertyA", typeof(V2.PropertyA), Namespace = "http://www.example.com/v2")]
public V2.PropertyA PropertyAV2 { get; set; }
}
[Serializable]
public class V1.PropertyA
{
public bool PropertyA1 { get; set; }
public decimal PropertyA2 { get; set; }
}
[Serializable]
public class V2.PropertyA
{
public bool PropertyA1 { get; set; }
public List<SomeOtherThing> PropertyA2 { get; set; }
}
When I go to deserialize V1, it works fine. When I go to deserialize V2, i get an error Did not expect <ts:PropertyA xmlns:ts="http://www.example.com/v2"> so I'm thinking there's a parameter I'm missing in the deserialize method:
public MyDataClass Deserialize(string xml)
{
var s = new XmlSerializer(typeof (MyDataClass));
MyDataClass info = null;
using (var r = new StringReader(xml))
{
info = (MyDataClass) s.Deserialize(r);
}
return info;
}
I believe you can set the expected namespace in the serializer, but since I don't know what the namespace is going to be until I actually inspect the xml document, I'm not sure how to proceed.
So my question is this: Is what I'm trying to do even possible? Am I on the right track? Is there a better solution that is maybe less contrived? How can I have the serializer deal with the new namespace and deserialize to the correct properties?
You can't.
The problem here is that you have to hardcode MyDataClass according to a single XMLSchema. If the XMLSchema alters, MyDataClass is no longer a valid target for the XMLSerializer's deserialize method, which is why you're getting the 'Did not expect ...' error message. In this case, when reading the V2 xml data stream, the deserialize method tries to fill MyDataClass#PropertyAV1 with the content of <ts:PropertyA2> and there is no way of telling it to instead fill MyDataClass#PropertyAV2. Even if there was a way to achieve this, you'd be stuck with an undefined value for MyDataClass#PropertyAV1 in the object of type MyDataClass.
So there are two solutions to the problem at hand :
a) Stick with XMLSerializer and define class MyDataClass like so
public class MyDataClass
{
// only one Property here, as there's only one root element in the xml
// and this single Property is not bound to a specific XML element
[XmlAnyElement]
public PropertyA PropertyA { get ;set; }
}
You then have to analyze the contents of PropertyA yourself and build some logic around it, see here for more details :
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlanyelementattribute.aspx
b) Dispense the XMLSerializer, read the XML data stream with XMLReader and do the all the parsing of the xml yourself, also add logic to create the according C# objects, depending on the the type of xml you've read.
Obviously, both solutions require more coding on the C# side, but with solution b) you'll have the chance of gaining a performance benefit, as XMLSerializer#deserialize most probably builds a DOM tree to create the C# object from, which the XMLReader doesn't do.
It seems that what I was trying to do was either unachievable or no one with the right level of xml fu saw this thread :(.
So anyway, what I ended up doing was adding an extra column to the database with the version number of the xml contract. Since everything in there was the same, I just called it V1.
I then read that info out into app code and used the version number to drive a factory. Basically, if v1, then deserialize to this, if v2, deserialize to this other thing.
And of course, to support that, I simply created a new data object that had the appropriate structure to support v2. I'm not happy with it, but it works and is flexible enough :/
Background: This question is about logging the change tracking of a POCO class in C# .NET 4.0.
Let's say I have a Person class with a Name (string) property. That Name property has a custom Attribute called [IsDirty(true/false)] that is set dynamically by a property-auditing class.
[IsDirty(true)]
public string Name { get; set; }
After the changes are detected and the attributes are set, I'm storing the object via normal XML Serialization in a MS SQL Database (XML column type).
What I can't figure out is if it's possible to somehow serialize my custom attribute IsDirty along with it's current value - preferably as an XML attribute on the serialized XML element (Name) so that the final xml is like:
<Name IsDirty="true">John</Name>
Any ideas/info would be appreciated-
I think you're going to have to write your own XML serialization for this and mix in some reflection to check attribute values on properties.
There's a good guide to implementing the IXMLSerializable interface here. Unfortunately you will have to implement serialization of all properties in the class, but on the bright side, if you implement IXmlSerializable correctly, you can still use the XmlSerializer class.
In your serialization code, you can check the attribute value using something like this:
public class YourClass : IXmlSerializable
{
[IsDirty(true)]
public string Name { get; set; }
// skipped ReadXml and GetSchema interface methods for brevity
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("YourClass");
var myType = typeof(YourClass);
foreach(var propInfo in myType.GetProperties())
{
writer.WriteStartElement(propInfo.Name);
foreach(var attr in propInfo.GetCustomAttributes(typeof(IsDirtyAttribute), false))
{
var myAttr = attr as IsDirtyAttribute;
writer.WriteAttributeString("Dirty", attr.Value ? "true" : "false");
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
}
This code is untested and written from memory, so there are probably some bugs lurking around, but hopefully it'll get you on the right track.
It would be possible by manually reading your Attribute and its value. You could do this by wrapping the serialization and deserialization in methods that appended/read the to/from the xml and the attribute..
However I would inherit these classes from a base class that had the property IsDirty then you neednt worry!
I'm playing with my favorite thing, xml (have the decency to kill me please) and the ultimate goal is save it in-house and use the data in a different manner (this is an export from another system). I've got goo that works, but meh, I think it can be a lot better.
public Position(XElement element)
{
Id = element.GetElementByName("id");
Title = element.GetElementByName("title");
}
I'm thinking of making it more automated (hacky) by adding data annotations for the xml element it represents. Something like this for instance.
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("title")]
public string Title { get; set; }
then writing some reflection/mapper code but both ways feels ... dirty. Should I care? Is there a better way? Maybe deserialize is the right approach? I just have a feeling there's a mo' bettah way to do this.
You can use the XmlSerializer class and markup your class properties with attributes to control the serialization and deserialization of the objects.
Here's a simple method you can use to deserialize your XDocument into your object:
public static T DeserializeXml<T>(XDocument document)
{
using (var reader = document.CreateReader())
{
var serializer = new XmlSerializer(typeof (T));
return (T) serializer.Deserialize(reader);
}
}
and a simple serializer method:
public static String ToXml<T>(T instance)
{
using (var output = new StringWriter(new StringBuilder()))
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(output, instance);
return output.ToString();
}
}
The mechanism that you are suggesting already exists in the form of the XmlSerializer class and a set of custom attributes that you can use to control the serialisation process.
In fact the .Net framework comes with a tool xsd.exe which will generate these class files for you from a schema definition (.xsd) file.
Also, just to make things really easy, Visual Studio even has the ability to generate you an .xsd schema for an xml snippet (its hidden away in the Xml menu somewhere).
I have a class that I need to do some custom XML output from, thus I implement the IXmlSerializable interface. However, some of the fields I want to output with the default serialization except I want to change the xml tag names. When I call serializer.Serialize, I get default tag names in the XML. Can I change these somehow?
Here is my code:
public class myClass: IXmlSerializable
{
//Some fields here that I do the custom serializing on
...
// These fields I want the default serialization on except for tag names
public string[] BatchId { get; set; }
...
... ReadXml and GetSchema methods are here ...
public void WriteXml(XmlWriter writer)
{
XmlSerializer serializer = new XmlSerializer(typeof(string[]));
serializer.Serialize(writer, BatchId);
... same for the other fields ...
// This method does my custom xml stuff
writeCustomXml(writer);
}
// My custom xml method is here and works fine
...
}
Here is my Xml output:
<MyClass>
<ArrayOfString>
<string>2643-15-17</string>
<string>2642-15-17</string>
...
</ArrayOfString>
... My custom Xml that is correct ..
</MyClass>
What I want to end up with is:
<MyClass>
<BatchId>
<id>2643-15-17</id>
<id>2642-15-17</id>
...
</BatchId>
... My custom Xml that is correct ..
</MyClass>
In many cases, you can use the XmlSerializer constructor-overload that accepts a XmlAttributeOverrides to specify this extra name information (for example, passing a new XmlRootAttribute) - however, this doesn't work for arrays AFAIK. I expect for the string[] example it would be simpler to just write it manually. In most cases, IXmlSerializable is a lot of extra work - I avoid it as far as possible for reasons like this. Sorry.
You can tag your fields with attributes to control the serialized XML. For example, adding the following attributes:
[XmlArray("BatchId")]
[XmlArrayItem("id")]
public string[] BatchId { get; set; }
will probably get you there.
If anyone is still looking for this you can definitely use the XmlArrayItem however this needs to be a property in a class.
For readability you should use the plural and singular of the same word.
/// <summary>
/// Gets or sets the groups to which the computer is a member.
/// </summary>
[XmlArrayItem("Group")]
public SerializableStringCollection Groups
{
get { return _Groups; }
set { _Groups = value; }
}
private SerializableStringCollection _Groups = new SerializableStringCollection();
<Groups>
<Group>Test</Group>
<Group>Test2</Group>
</Groups>
David