Related
I am a student and not an advance developer. I faced an issue regarding to construct a program. I have tried a few things, but still could not solved it. The question came from an exam.
Apologize for this post, because my previous two posts were not accepted well. This time I tried, but got no success still for my posted issue.
Question as follows
a) Write a class named Document to represent a document node in a print queue. It should contain a method name that returns the subject of the document and an abstract method called type that returns the document type. Derive from a document class two concrete classes named word document and pdf document.
b) Another class named Print Queue that is implemented as a linked list of Document objects. Print Queue should have a method named Push for adding a document to the end of the list, a method named pop for removing the first document from the list, and a method named DisplayContents for listing all of the documents in the Print Queue, reporting both the Name and Type of each element Print Queue should not use any standard library classes it should be own implementation of linked list.
Here I have coded it that way:
class PrintQueue
{
private Document head;
private int size;
public int Count
{
get
{
return size;
}
}
public void Push(Object data)
{
Document toAdd = new Document();
toAdd.data = data;
Document current = head;
while (current.Next != null)
{
current = current.Next;
}
current.Next = toAdd;
size++;
}
public bool Pop()
{
Document tempNode = head;
Document lastNode = null;
int count = 0;
if (size > 0)
{
while (tempNode != null)
{
if (count == size - 1)
{
lastNode.Next = tempNode.Next;
return true;
}
count++;
lastNode = tempNode;
tempNode = tempNode.Next;
}
}
return false;
}
}
public abstract class Document
{
public Document Next;
public Object data;
public string Subject { get; set; }
public abstract string type();
}
public class WordDocument : Document
{
public override string type()
{
return "docx";
}
}
public class PdfDocument : Document
{
public override string type()
{
return "pdf";
}
}
This line causes the problem Document toAdd = new Document();
But I still could not find a way for DisplayContents function which will list all of the documents in the Print Queue, reporting both the Name and Type of each element Print Queue.
Could anyone review my code and help me to get it rectified as per the question. Highlight those areas where I have made mistakes.
Thanks
UPDATE
the 2 questions i was trying to solve as follows
Question 1 : Write a class named Document to represent a document node in a print queue. it should contain a method name that returns the subject of the document and an abstract method called type that returns the document type . from document class derive two concrete classes named word document and pdf document.
Question 2 : Another class named Print Queue that is implemented as a linked list of Document objects. Print Queue should have a method named Push for adding a document to the end of the list, a method named pop for removing the first document from the list , and a method named DisplayContents for listing all of the documents in the Print Queue, reporting both the Name and Type of each element Print Queue should not use any standard library classes it should be own implementation of linked list.
here i like to post whatever at last i achieved. so please see the code and question let me know is it correct as per the 2 question pasted here.
see the latest code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrintQueueDemo
{
class PrintQueue
{
Node head;
Node tail;
/// <summary>
/// add a document to the print queue
/// </summary>
/// <param name="strType">document type</param>
/// <param name="strName">docunent name</param>
public void Push(string strType, string strName)
{
if (head == null)
{
head = new Node(strType, strName);
tail = head;
}
else
{
Node node = new Node(strType, strName);
tail.setNext(node);
tail = node;
}
}
/// <summary>
/// pop a document from the queue
/// </summary>
/// <returns>null if printqueue is empty, else document</returns>
public Node.Document Pop()
{
if (head == null)
return null;
Node.Document doc = new Node.Document(head.document.Type, head.document.Name);
head = head.nextnode;
if (head == null)
tail = null;
return doc;
}
/// <summary>
/// get the current content of the queue
/// </summary>
/// <returns>string with current content (line per entry)</returns>
public string DisplayContents()
{
string content = "";
Node node = head;
if (node == null)
return "PrintQueue is empty";
content = node.document.Name + ": " + node.document.Type;
while (node.nextnode != null)
{
node = node.nextnode;
content += "\r\n" + node.document.Name + ": " + node.document.Type;
}
return content;
}
public class Node
{
public Document document { get; private set; }
public Node nextnode { get; private set; }
public Node(string strType, string strName)
{
document = new Document(strType, strName);
}
public void setNext(Node node)
{
nextnode = node;
}
public class Document
{
public string Type { get; private set; }
public string Name { get; private set; }
public Document(string strType, string strName)
{
Name = strName;
Type = strType;
}
}
}
}
}
PrintQueue pq = new PrintQueue();
pq.Push("cpp", "main.cpp");
pq.Push("c#", "main.cs");
pq.Push("c", "main.c");
pq.Push("h", "myinclude.h");
Console.WriteLine(pq.DisplayContents());
Console.WriteLine("===");
PrintQueue.Node.Document doc;
doc = pq.Pop();
Console.WriteLine("{0}: {1}", doc.Name, doc.Type);
doc = pq.Pop();
Console.WriteLine("{0}: {1}", doc.Name, doc.Type);
doc = pq.Pop();
Console.WriteLine("{0}: {1}", doc.Name, doc.Type);
doc = pq.Pop();
Console.WriteLine("{0}: {1}", doc.Name, doc.Type);
Console.WriteLine("===");
Console.WriteLine(pq.DisplayContents());
Console.WriteLine("===");
pq.Push("xls", "workbook.xls");
Console.WriteLine(pq.DisplayContents());
just tell me the above code will be accepted as per 2 question which i pasted at top. thanks
I have got the following code:
BaseContent.cs
public class BaseContent
{
// Some auto properties
}
News.cs
public class News : BaseContent
{
// Some more auto properties
}
Events.cs
public class Event : BaseContent
{
// Some more auto properites
}
GenericResponse.cs
public class GenericResponse<T>
{
[XmlArray("Content")]
[XmlArrayItem("NewsObject", typeof(News)]
[XmlArrayItem("EventObject", typeof(Event)]
public List<T> ContentItems { get; set; }
}
NewsResponse.cs
public class NewsResponse : GenericResponse<News> {}
EventResponse.cs
public class EventResponse : GenericResponse<Event> {}
As you can see, I have a base class BaseContent and two classes deriving from it. Next I have a generic response class, since the structure of the xml-files is always the same, but they differ in some properties.
I thought I can specify with the [XmlArrayItem] which name to use for a specific class. But now I get the error:
System.InvalidOperationException: Unable to generate a temporary class (result=1).
error CS0012: The type 'System.Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
I can not add this reference, because I'm working on a Windows 8 App.
If I comment out one of the [XmlArrayItem] it is working well.
Anyone got an idea to solve this problem?
Update
I can not use DataContractSerializer, because I have to use XmlAttributes
Edit: Feel free to download the demo project
You didn't provide all the properties for the objects, so allow me to add some - just as an example:
public class BaseContent
{
[XmlAttribute("Name")]
public string Name { get; set; }
}
[XmlType(TypeName = "EventObject")]
public class Event : BaseContent
{
[XmlAttribute("EventId")]
public int EventId { get; set; }
}
[XmlType(TypeName = "NewsObject")]
public class News : BaseContent
{
[XmlAttribute("NewsId")]
public int NewsId { get; set; }
}
GenericResponse.cs could be defined this way - no need to specify the typeof for the array items:
public class GenericResponse<T>
{
[XmlArray("Content")]
public List<T> ContentItems { get; set; }
public GenericResponse()
{
this.ContentItems = new List<T>();
}
}
And then you have the response classes:
public class EventResponse : GenericResponse<Event>
{
}
public class NewsResponse : GenericResponse<News>
{
}
Example 1: Serialize an EventResponse object
var response = new EventResponse
{
ContentItems = new List<Event>
{
new Event {
EventId = 1,
Name = "Event 1"
},
new Event {
EventId = 2,
Name = "Event 2"
}
}
};
string xml = XmlSerializer<EventResponse>.Serialize(response);
Output XML:
<?xml version="1.0" encoding="utf-8"?>
<EventResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Content>
<EventObject Name="Event 1" EventId="1" />
<EventObject Name="Event 2" EventId="2" />
</Content>
</EventResponse>
If you try the same with NewsResponse it will work fine. BTW I'm using my generic XmlSerializer, click on the link to know more about it.
XmlSerializer.cs:
/// <summary>
/// XML serializer helper class. Serializes and deserializes objects from/to XML
/// </summary>
/// <typeparam name="T">The type of the object to serialize/deserialize.
/// Must have a parameterless constructor and implement <see cref="Serializable"/></typeparam>
public class XmlSerializer<T> where T: class, new()
{
/// <summary>
/// Deserializes a XML string into an object
/// Default encoding: <c>UTF8</c>
/// </summary>
/// <param name="xml">The XML string to deserialize</param>
/// <returns>An object of type <c>T</c></returns>
public static T Deserialize(string xml)
{
return Deserialize(xml, Encoding.UTF8, null);
}
/// <summary>
/// Deserializes a XML string into an object
/// Default encoding: <c>UTF8</c>
/// </summary>
/// <param name="xml">The XML string to deserialize</param>
/// <param name="encoding">The encoding</param>
/// <returns>An object of type <c>T</c></returns>
public static T Deserialize(string xml, Encoding encoding)
{
return Deserialize(xml, encoding, null);
}
/// <summary>
/// Deserializes a XML string into an object
/// </summary>
/// <param name="xml">The XML string to deserialize</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
/// <returns>An object of type <c>T</c></returns>
public static T Deserialize(string xml, XmlReaderSettings settings)
{
return Deserialize(xml, Encoding.UTF8, settings);
}
/// <summary>
/// Deserializes a XML string into an object
/// </summary>
/// <param name="xml">The XML string to deserialize</param>
/// <param name="encoding">The encoding</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
/// <returns>An object of type <c>T</c></returns>
public static T Deserialize(string xml, Encoding encoding, XmlReaderSettings settings)
{
if (string.IsNullOrEmpty(xml))
throw new ArgumentException("XML cannot be null or empty", "xml");
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (MemoryStream memoryStream = new MemoryStream(encoding.GetBytes(xml)))
{
using (XmlReader xmlReader = XmlReader.Create(memoryStream, settings))
{
return (T) xmlSerializer.Deserialize(xmlReader);
}
}
}
/// <summary>
/// Deserializes a XML file.
/// </summary>
/// <param name="filename">The filename of the XML file to deserialize</param>
/// <returns>An object of type <c>T</c></returns>
public static T DeserializeFromFile(string filename)
{
return DeserializeFromFile(filename, new XmlReaderSettings());
}
/// <summary>
/// Deserializes a XML file.
/// </summary>
/// <param name="filename">The filename of the XML file to deserialize</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
/// <returns>An object of type <c>T</c></returns>
public static T DeserializeFromFile(string filename, XmlReaderSettings settings)
{
if (string.IsNullOrEmpty(filename))
throw new ArgumentException("filename", "XML filename cannot be null or empty");
if (! File.Exists(filename))
throw new FileNotFoundException("Cannot find XML file to deserialize", filename);
// Create the stream writer with the specified encoding
using (XmlReader reader = XmlReader.Create(filename, settings))
{
System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
return (T) xmlSerializer.Deserialize(reader);
}
}
/// <summary>
/// Serialize an object
/// </summary>
/// <param name="source">The object to serialize</param>
/// <returns>A XML string that represents the object to be serialized</returns>
public static string Serialize(T source)
{
// indented XML by default
return Serialize(source, null, GetIndentedSettings());
}
/// <summary>
/// Serialize an object
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="namespaces">Namespaces to include in serialization</param>
/// <returns>A XML string that represents the object to be serialized</returns>
public static string Serialize(T source, XmlSerializerNamespaces namespaces)
{
// indented XML by default
return Serialize(source, namespaces, GetIndentedSettings());
}
/// <summary>
/// Serialize an object
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
/// <returns>A XML string that represents the object to be serialized</returns>
public static string Serialize(T source, XmlWriterSettings settings)
{
return Serialize(source, null, settings);
}
/// <summary>
/// Serialize an object
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="namespaces">Namespaces to include in serialization</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
/// <returns>A XML string that represents the object to be serialized</returns>
public static string Serialize(T source, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
{
if (source == null)
throw new ArgumentNullException("source", "Object to serialize cannot be null");
string xml = null;
XmlSerializer serializer = new XmlSerializer(source.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
x.Serialize(xmlWriter, source, namespaces);
memoryStream.Position = 0; // rewind the stream before reading back.
using (StreamReader sr = new StreamReader(memoryStream))
{
xml = sr.ReadToEnd();
}
}
}
return xml;
}
/// <summary>
/// Serialize an object to a XML file
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="filename">The file to generate</param>
public static void SerializeToFile(T source, string filename)
{
// indented XML by default
SerializeToFile(source, filename, null, GetIndentedSettings());
}
/// <summary>
/// Serialize an object to a XML file
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="filename">The file to generate</param>
/// <param name="namespaces">Namespaces to include in serialization</param>
public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces)
{
// indented XML by default
SerializeToFile(source, filename, namespaces, GetIndentedSettings());
}
/// <summary>
/// Serialize an object to a XML file
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="filename">The file to generate</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
public static void SerializeToFile(T source, string filename, XmlWriterSettings settings)
{
SerializeToFile(source, filename, null, settings);
}
/// <summary>
/// Serialize an object to a XML file
/// </summary>
/// <param name="source">The object to serialize</param>
/// <param name="filename">The file to generate</param>
/// <param name="namespaces">Namespaces to include in serialization</param>
/// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
{
if (source == null)
throw new ArgumentNullException("source", "Object to serialize cannot be null");
XmlSerializer serializer = new XmlSerializer(source.GetType());
using (XmlWriter xmlWriter = XmlWriter.Create(filename, settings))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
x.Serialize(xmlWriter, source, namespaces);
}
}
#region Private methods
private static XmlWriterSettings GetIndentedSettings()
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Indent = true;
xmlWriterSettings.IndentChars = "\t";
return xmlWriterSettings;
}
#endregion
}
I found my solution. No the best, but it is working.
I impleted IXmlSerializable and handeled the stuff by myself:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
reader.MoveToContent();
if (reader.LocalName == "AnotherNode")
{
var innerXml = Serializer<AnotherClass>.CreateSerializer();
Remove = (AnotherClass) innerXml.Deserialize(reader);
reader.MoveToContent();
}
reader.Read();
// Here is the trick
if (reader.IsStartElement())
{
do
{
var innerXml = Serializer<T>.CreateSerializer();
var obj = (T) innerXml.Deserialize(reader);
Updates.Add(obj);
} while (reader.MoveToContent() == XmlNodeType.Element);
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
var removeSerializer = Serializer<RemoveElement>.CreateSerializer();
removeSerializer.Serialize(writer, Remove);
if (Updates.Any())
{
var innerXml = Serializer<T>.CreateSerializer();
writer.WriteStartElement("ContentUpdates");
foreach (var update in Updates)
{
innerXml.Serialize(writer, update);
}
writer.WriteEndElement();
}
}
Consider using DataContractSerializer instead of XmlSerializer.
XmlSerializer is generating at runtime a temp assembly, in order to speed up
serialization - deserialization process later on. Thus it requires code compilation.
DataContractSerializer on the other hand does not. In case you use the DataContractSerializer
approach you have to play around with appropriate attributes, so that you control "inheritence"
issues.
That sounds like a bug in the code-emitter for XmlSerializer for the Windows Store platform specifically, but it is a bit moot, because it still fails on regular .NET, but with a different message:
Unable to generate a temporary class (result=1).
error CS0030: Cannot convert type 'News' to 'Event'
error CS1502: The best overloaded method match for 'System.Collections.Generic.List.Add(News)' has some invalid arguments
error CS1503: Argument 1: cannot convert from 'Event' to 'News'
So basically, it looks like an unsupported scenario that manifests differently in the two scenarios. Fundamentally, the issue is that you're annotations are saying (for T=News) "if this News is a News, call the element 'NewsObject'; if this News is an Event, call the element 'EventObject'" - which is odd because a News is never an Event, etc.
However, the good news is that what you want to do can be done just using [XmlType(...)]:
[XmlType("NewsObject")]
public class News : BaseContent
{
// Some more auto properties
public int B { get; set; }
}
[XmlType("EventObject")]
public class Event : BaseContent
{
// Some more auto properites
public int C { get; set; }
}
...
public class GenericResponse<T>
{
[XmlArray("Content")]
public List<T> ContentItems { get; set; }
}
note: no [XmlArrayItem(...)] in the above.
which then outputs (after formatting, cleaning, etc):
<NewsResponse>
<Content>
<NewsObject><A>1</A><B>2</B></NewsObject>
</Content>
</NewsResponse>
via the test code:
var obj = new NewsResponse { ContentItems = new List<News> {
new News { A = 1, B = 2 } } };
var sw = new StringWriter();
using (var xw = System.Xml.XmlWriter.Create(sw))
{
var ser = new XmlSerializer(obj.GetType());
ser.Serialize(xw, obj);
}
string xml = sw.ToString();
If you need more control than that, then XmlAttributeOverrides is the thing to look at; but you must cache serializers created with XmlAttributeOverrides, to avoid hemorrhaging assemblies and getting a memory leak.
I'm trying to cleans the Xml element with the attribut "nil=true" inside a document.
I come up with this algo but I don't like how it's look.
Do anyone know a linq version of this algo?
/// <summary>
/// Cleans the Xml element with the attribut "nil=true".
/// </summary>
/// <param name="value">The value.</param>
public static void CleanNil(this XElement value)
{
List<XElement> toDelete = new List<XElement>();
foreach (var element in value.DescendantsAndSelf())
{
if (element != null)
{
bool blnDeleteIt = false;
foreach (var attribut in element.Attributes())
{
if (attribut.Name.LocalName == "nil" && attribut.Value == "true")
{
blnDeleteIt = true;
}
}
if (blnDeleteIt)
{
toDelete.Add(element);
}
}
}
while (toDelete.Count > 0)
{
toDelete[0].Remove();
toDelete.RemoveAt(0);
}
}
What's the namespace of the nil attribute? Put that inside { } like this:
public static void CleanNil(this XElement value)
{
value.Descendants().Where(x=> (bool?)x.Attribute("{http://www.w3.org/2001/XMLSchema-instance}nil") == true).Remove();
}
This should work..
public static void CleanNil(this XElement value)
{
var todelete = value.DescendantsAndSelf().Where(x => (bool?) x.Attribute("nil") == true);
if(todelete.Any())
{
todelete.Remove();
}
}
The extension method:
public static class Extensions
{
public static void CleanNil(this XElement value)
{
value.DescendantsAndSelf().Where(x => x.Attribute("nil") != null && x.Attribute("nil").Value == "true").Remove();
}
}
Sample usage:
File.WriteAllText("test.xml", #"
<Root nil=""false"">
<a nil=""true""></a>
<b>2</b>
<c nil=""false"">
<d nil=""true""></d>
<e nil=""false"">4</e>
</c>
</Root>");
var root = XElement.Load("test.xml");
root.CleanNil();
Console.WriteLine(root);
output:
<Root nil="false">
<b>2</b>
<c nil="false">
<e nil="false">4</e>
</c>
</Root>
as you can see, nodes <a> and <d> where removed as expected. The only thing to note is that you cannot call this method on the <Root> node because the root node cannot be removed, and you will get this run-time error:
The parent is missing.
I change my way to solve that issue and avoid to create nil on my nullable type
I use the following
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.90%29.aspx
public class OptionalOrder
{
// This field should not be serialized
// if it is uninitialized.
public string FirstOrder;
// Use the XmlIgnoreAttribute to ignore the
// special field named "FirstOrderSpecified".
[System.Xml.Serialization.XmlIgnoreAttribute]
public bool FirstOrderSpecified;
}
OK, so here's the story so far.
I could already deserialize individual objects using XmlSerializer, but deserializing lists was proving to be a real headache. I started out by trying to serialize List<Foo> and the serializer serialized multiple <Foo> XML structures inside a root <ArrayOfFoo> element. That proved to be problematic to deserialize, so it looks like I needed to have defined the 'ArrayOfFoo' element myself. So, I've got a class working that is a 'wrapper' for the list, as shown in this program:
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace XmlTester2
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("XML tester...");
string xml =
"<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
"<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
"<Field4>field4Val</Field4>" + "</Person>" +
"<Account i:type=\"AccountI2\">" + "<Field1>field1Val</Field1>" +
"<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
"<Field4>field4Val</Field4>" + "</Account>" +
"<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
"<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
"<Field4>field4Val</Field4>" + "</Person>" + "</ItemList>";
XmlSerializer ser = new XmlSerializer(typeof(ItemList));
using (var reader = new StringReader(xml))
{
ItemList result = (ItemList)ser.Deserialize(reader);
}
Console.WriteLine("Break here and check 'result' in Quickwatch...");
Console.ReadKey();
}
}
[XmlRootAttribute(IsNullable = false)]
public class ItemList
{
[XmlElementAttribute("Person")]
public List<Person> Persons { get; set; }
[XmlElementAttribute("Account")]
public List<Account> Accounts { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
[XmlInclude(typeof(PersonI2))]
public class Person
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
public class PersonI2 : Person
{
public string Field4 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
[XmlInclude(typeof(AccountI2))]
public class Account
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
public class AccountI2 : Account
{
public string Field4 { get; set; }
}
}
However, this 'wrapper', ItemList, still has to have manually defined in it all the elements that might be contained (in the example, Person and Account). What would be really ideal would be to have a generic list wrapper class. I know this is a bit hopeful, but would there be a way to do this? I'm thinking of something along these lines (this does not work, but is just to give you the general idea):
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace XmlTester3
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("XML tester...");
string xml =
"<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
"<Person i:type=\"PersonI2\">" +
"<Field1>field1Val</Field1>" +
"<Field2>field2Val</Field2>" +
"<Field3>field3Val</Field3>" +
"<Field4>field4Val</Field4>" +
"</Person>" +
"<Person i:type=\"PersonI2\">" +
"<Field1>field1Val</Field1>" +
"<Field2>field2Val</Field2>" +
"<Field3>field3Val</Field3>" +
"<Field4>field4Val</Field4>" +
"</Person>" +
"</ItemList>";
XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
using (var reader = new StringReader(xml))
{
ItemList<Person> result = (ItemList<Person>)ser.Deserialize(reader);
}
Console.WriteLine("Break here and check 'result' in Quickwatch...");
Console.ReadKey();
}
}
[XmlRootAttribute(IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonI2))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountI2))]
public class ItemList<T>
{
[XmlElementAttribute]
public List<T> Items { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
[XmlInclude(typeof(PersonI2))]
public class Person
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
public class PersonI2 : Person
{
public string Field4 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
[XmlInclude(typeof(AccountI2))]
public class Account
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
[XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
public class AccountI2 : Account
{
public string Field4 { get; set; }
}
}
So, the XML structures passed inside the ItemList would only be able to be of one type, say Person in this example, and I could define an ItemList<Person> that would allow me to deserialize a list containing multiple Person objects? Any ideas? If necessary, I wouldn't mind having to tag the ItemList class with an [XmlInclude...] for every type that ItemList might contain a collection of.
I'm guessing this is possible, I just haven't figured out quite how? :-) Or is the default XmlSerializer too fussy?
You can do this easily enough, just implement the System.Xml.Serialization.IXmlSerializable interface. If I were doing this, I might even reflect the possible derived types of T in the assembly that defines T and completely omit the [XmlInclude] declarations. The real down side with this approach is the creation of the XmlSerializers. You might consider caching them. Anyway just use this in your second example and it should work.
BTW, that is an interesting thing your doing with the "i:type=\"PersonI2\""; props for figuring that one out ;)
[XmlRootAttribute("ItemList", IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonI2))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountI2))]
public class ItemList<T> : System.Xml.Serialization.IXmlSerializable
{
class Map : Dictionary<String, XmlSerializer>
{ public Map() : base(StringComparer.Ordinal) { } }
public List<T> Items { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
private string TypeName(Type t)
{
String typeName = t.Name;
foreach (XmlTypeAttribute a in t.GetCustomAttributes(typeof(XmlTypeAttribute), true))
if (!String.IsNullOrEmpty(a.TypeName))
typeName = a.TypeName;
return typeName;
}
private Map LoadSchema()
{
Map map = new Map();
foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true))
{
Type t = inc.Type;
if (typeof(T).IsAssignableFrom(t))
map.Add(TypeName(t), new XmlSerializer(t));
}
return map;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Map map = LoadSchema();
int depth = reader.Depth;
List<T> items = new List<T>();
if (!reader.IsEmptyElement && reader.Read())
{
while (reader.Depth > depth)
{
items.Add((T)map[reader.LocalName].Deserialize(reader));
}
}
this.Items = items;
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Map map = LoadSchema();
foreach (T item in this.Items)
{
map[TypeName(item.GetType())].Serialize(writer, item);
}
}
}
I'm not sure I understand your question but do you know there's a XmlArrayItemAttribute.
[XmlArray("foos"), XmlArrayItem(typeof(Foo), ElementName = "foo")]
Under .NET 3.5 SP1 (Specificly SP1) you can use the Serializers from WCF to deserialize objects without specificly marking the class up with DataContract or Serializable attributes.
Almost any class should be able to be deserialized this way - as long as the Property names match the element names.
If you're getting deserializer errors - then it's possibly because of some misnamed property or an incorrect type. To check the input that the Serializer is looking for, you can populate an object once, and then serialize it down to XML to compare.
I wrote myself a helper class for using this a while back.
The way to use the helper is:
string serialized = "some xml";
MyType foo = Helpers.Deserialize<MyType>(serialized, SerializerType.Xml);
The actual helper class:
using System.IO;
using System.Runtime.Serialization; // System.Runtime.Serialization.dll (.NET 3.0)
using System.Runtime.Serialization.Json; // System.ServiceModel.Web.dll (.NET 3.5)
using System.Text;
namespace Serialization
{
public static class Helpers
{
/// <summary>
/// Declare the Serializer Type you want to use.
/// </summary>
public enum SerializerType
{
Xml, // Use DataContractSerializer
Json // Use DataContractJsonSerializer
}
public static T Deserialize<T>(string SerializedString, SerializerType UseSerializer)
{
// Get a Stream representation of the string.
using (Stream s = new MemoryStream(UTF8Encoding.UTF8.GetBytes(SerializedString)))
{
T item;
switch (UseSerializer)
{
case SerializerType.Json:
// Declare Serializer with the Type we're dealing with.
var serJson = new DataContractJsonSerializer(typeof(T));
// Read(Deserialize) with Serializer and cast
item = (T)serJson.ReadObject(s);
break;
case SerializerType.Xml:
default:
var serXml = new DataContractSerializer(typeof(T));
item = (T)serXml.ReadObject(s);
break;
}
return item;
}
}
public static string Serialize<T>(T ObjectToSerialize, SerializerType UseSerializer)
{
using (MemoryStream serialiserStream = new MemoryStream())
{
string serialisedString = null;
switch (UseSerializer)
{
case SerializerType.Json:
// init the Serializer with the Type to Serialize
DataContractJsonSerializer serJson = new DataContractJsonSerializer(typeof(T));
// The serializer fills the Stream with the Object's Serialized Representation.
serJson.WriteObject(serialiserStream, ObjectToSerialize);
break;
case SerializerType.Xml:
default:
DataContractSerializer serXml = new DataContractSerializer(typeof(T));
serXml.WriteObject(serialiserStream, ObjectToSerialize);
break;
}
// Rewind the stream to the start so we can now read it.
serialiserStream.Position = 0;
using (StreamReader sr = new StreamReader(serialiserStream))
{
// Use the StreamReader to get the serialized text out
serialisedString = sr.ReadToEnd();
sr.Close();
}
return serialisedString;
}
}
}
}
There are 2 main techniques for (de)serializing objects:
Implement an interface together with its Serialize() and Deserialize() methods for each class you want to (de)serialize - fast but requires a lot of maintenance.
Use a reflection based serizlier/deserializer that analizes the public fields and properties in your classes - slower but does not require maintaining (de)serialize() methods in each class.
Personally, in many cases, I prefer the 2nd technique.
.NET's built in XmlSerializer supports the 2nd technique, but has many limitations:
1 . Multi-deminsional arrays.
2 . Deserializing objects of unexpected types:
public MyClass
{
public IMyInterface MyProperty1
{
get;
set;
}
public MyBaseType MyProperty2
{
get;
set;
}
}
The types of the actual objects in MyProperty1, MyProperty2 is unknown during deserialization.
3 . (De)serializing complex collections.
4 . No good way to handle case where fields/properties were added/remove to/from class between serialization and deserialization.
5 . No support for serializing graphs with cycles.
The solution I came up with was to write a custom reflection based serializer/deserializer,
at the time I could not find any existing serializer, so I wrote a new one from scratch.
I can not publish it, since it is proprietary, however I noticed that afterwards simular serializers were published:
http://www.codeproject.com/KB/XML/GR_CustomXmlSerializer.aspx
XML Serialization and Inherited Types
http://www.codeproject.com/KB/XML/deepserializer.aspx
!THIS IS THE BEST SOLUTION I'VE FOUND!
OK, sorry for the answer-spam here, people, but I've found an even more elegant way of doing this that avoids the need for ItemList to have its items accessed using an 'Items' property; make the ItemList a List itself! This way, you just directly access ItemList as a list. Here's the amended example program:
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace XmlTester
{
public class Program {
static void Main(string[] args) {
Console.WriteLine("XML tester...");
// Valid XML for an ItemList of Person's
XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
string xmlIn =
#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<PersonBilingual>
<FullName>John Smith</FullName>
<Age>21</Age>
<Language>French</Language>
<SecondLanguage>German</SecondLanguage>
</PersonBilingual>
<Person>
<FullName>Joe Bloggs</FullName>
<Age>26</Age>
<Language>English</Language>
</Person>
<Person i:type=""PersonBilingual"">
<FullName>Jane Doe</FullName>
<Age>78</Age>
<Language>Italian</Language>
<SecondLanguage>English</SecondLanguage>
</Person>
</ItemList>";
//// Valid XML for an ItemList of Account's
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
//string xmlIn =
//#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
// <AccountBank>
// <AcctName>Deposit account</AcctName>
// <WithCompany>Bank of Switzerland</WithCompany>
// <BalanceInEuros>300</BalanceInEuros>
// </AccountBank>
// <Account>
// <AcctName>Book buying account</AcctName>
// <WithCompany>Amazon</WithCompany>
// </Account>
// <Account i:type=""AccountBank"">
// <AcctName>Savings account</AcctName>
// <WithCompany>Bank of America</WithCompany>
// <BalanceInEuros>2500</BalanceInEuros>
// </Account>
//</ItemList>";
//// Invalid XML, as we have mixed incompatible types
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
//string xmlIn =
//#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
// <PersonBilingual>
// <FullName>John Smith</FullName>
// <Age>21</Age>
// <Language>French</Language>
// <SecondLanguage>German</SecondLanguage>
// </PersonBilingual>
// <Account>
// <AcctName>Book buying account</AcctName>
// <WithCompany>Amazon</WithCompany>
// </Account>
// <Person i:type=""PersonBilingual"">
// <FullName>Jane Doe</FullName>
// <Age>78</Age>
// <Language>Italian</Language>
// <SecondLanguage>English</SecondLanguage>
// </Person>
//</ItemList>";
// Deserialize...
ItemList<Person> result;
using (var reader = new StringReader(xmlIn)) {
result = (ItemList<Person>)ser.Deserialize(reader);
}
Console.WriteLine("Break here and check 'result' in Quickwatch...");
Console.ReadKey();
// Serialize...
StringBuilder xmlOut = new StringBuilder();
ser.Serialize(new StringWriter(xmlOut), result);
Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
Console.ReadKey();
}
}
[XmlRoot(ElementName = "ItemList", IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonBilingual))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountBank))]
public class ItemList<T> : List<T>, IXmlSerializable {
#region Private vars
/// <summary>
/// The class that will store our serializers for the various classes that may be (de)serialized, given
/// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
/// </summary>
private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }
#endregion
#region Private methods
/// <summary>
/// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
/// associated with it. For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
/// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
/// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
/// corresponding to that type to be (de)serialized by this ItemList.
/// </summary>
/// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
private Map loadSchema() {
Map map = new Map();
foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
Type t = inc.Type;
if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
}
return map;
}
/// <summary>
/// As the XML type name can be different to our internal class name for that XML type, we need to be able
/// to expect an XML element name that is different to our internal class name for that XML type. Hence,
/// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
/// internal class name for that XML type. This method returns the XML type name given our internal
/// class we're using to (de)serialize that XML type. If no XML TypeName is specified in our internal
/// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
/// </summary>
/// <param name="t">Our internal class used to (de)serialize an XML type.</param>
/// <returns>The XML type name corresponding to the given internal class.</returns>
private string xmlTypeName(Type t) {
string typeName = t.Name;
foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
}
return typeName;
}
#endregion
#region IXmlSerializable Members
/// <summary>
/// Reserved and should not be used.
/// </summary>
/// <returns>Must return null.</returns>
public XmlSchema GetSchema() {
return null;
}
/// <summary>
/// Generates a list of type T objects from their XML representation; stores them in this ItemList.
/// </summary>
/// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
public void ReadXml(XmlReader reader) {
Map map = loadSchema();
int depth = reader.Depth;
List<T> items = new List<T>();
if (!reader.IsEmptyElement && reader.Read()) {
// While the reader is at a greater depth that the initial depth, ie. at one of the elements
// inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
while (reader.Depth > depth) {
try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
catch (InvalidOperationException iopEx) {
if (
iopEx.InnerException != null &&
iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized. Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
}
catch (KeyNotFoundException knfEx) {
throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type. Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
}
catch (Exception ex) {
throw ex;
}
}
}
this.AddRange(items);
}
/// <summary>
/// Converts a list of type T objects into their XML representation; writes them to the specified writer.
/// </summary>
/// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
public void WriteXml(XmlWriter writer) {
Map map = loadSchema();
foreach (T item in this) {
map[xmlTypeName(item.GetType())].Serialize(writer, item);
}
}
#endregion
}
/// <summary>
/// A regular person.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
[XmlInclude(typeof(PersonBilingual))]
public class Person {
public string FullName { get; set; }
public int Age { get; set; }
public string Language { get; set; }
}
/// <summary>
/// A person who can speak a second language.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
public class PersonBilingual : Person {
public string SecondLanguage { get; set; }
}
/// <summary>
/// Some kind of account.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
[XmlInclude(typeof(AccountBank))]
public class Account {
public string AcctName { get; set; }
public string WithCompany { get; set; }
}
/// <summary>
/// A bank account.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
public class AccountBank : Account {
public int BalanceInEuros { get; set; }
}
}
UPDATE: Please see the answer beginning !THIS IS THE BEST SOLUTION I'VE FOUND! - it's a better solution than this one.
...
Heavily inspired by csharptest.net's comment, I've created a class that pretty much does the job I wanted. :-) You access the deserialized items by checking ItemList.Items, and serialize stuff by inserting the items into ItemList.Items and then serializing it using an appropriate XmlSerializer. The only slight annoyance is that you must ensure that the ItemList class is tagged with an XmlIncludeAttribute for every class type that may need to be (de)serialized, or the XmlSerializer won't be able to deal with it.
Here's the example program, containing the generic ItemList class:
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace XmlTester
{
public class Program {
static void Main(string[] args) {
Console.WriteLine("XML tester...");
// Valid XML for an ItemList of Person's
XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
string xmlIn =
#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<PersonBilingual>
<FullName>John Smith</FullName>
<Age>21</Age>
<Language>French</Language>
<SecondLanguage>German</SecondLanguage>
</PersonBilingual>
<Person>
<FullName>Joe Bloggs</FullName>
<Age>26</Age>
<Language>English</Language>
</Person>
<Person i:type=""PersonBilingual"">
<FullName>Jane Doe</FullName>
<Age>78</Age>
<Language>Italian</Language>
<SecondLanguage>English</SecondLanguage>
</Person>
</ItemList>";
//// Valid XML for an ItemList of Account's
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
//string xmlIn =
//#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
// <AccountBank>
// <AcctName>Deposit account</AcctName>
// <WithCompany>Bank of Switzerland</WithCompany>
// <BalanceInEuros>300</BalanceInEuros>
// </AccountBank>
// <Account>
// <AcctName>Book buying account</AcctName>
// <WithCompany>Amazon</WithCompany>
// </Account>
// <Account i:type=""AccountBank"">
// <AcctName>Savings account</AcctName>
// <WithCompany>Bank of America</WithCompany>
// <BalanceInEuros>2500</BalanceInEuros>
// </Account>
//</ItemList>";
//// Invalid XML, as we have mixed incompatible types
//XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
//string xmlIn =
//#"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
// <PersonBilingual>
// <FullName>John Smith</FullName>
// <Age>21</Age>
// <Language>French</Language>
// <SecondLanguage>German</SecondLanguage>
// </PersonBilingual>
// <Account>
// <AcctName>Book buying account</AcctName>
// <WithCompany>Amazon</WithCompany>
// </Account>
// <Person i:type=""PersonBilingual"">
// <FullName>Jane Doe</FullName>
// <Age>78</Age>
// <Language>Italian</Language>
// <SecondLanguage>English</SecondLanguage>
// </Person>
//</ItemList>";
// Deserialize...
ItemList<Person> result;
using (var reader = new StringReader(xmlIn)) {
result = (ItemList<Person>)ser.Deserialize(reader);
}
Console.WriteLine("Break here and check 'result' in Quickwatch...");
Console.ReadKey();
// Serialize...
StringBuilder xmlOut = new StringBuilder();
ser.Serialize(new StringWriter(xmlOut), result);
Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
Console.ReadKey();
}
}
[XmlRoot(ElementName = "ItemList", IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonBilingual))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountBank))]
public class ItemList<T> : IXmlSerializable {
#region Private vars
/// <summary>
/// The class that will store our serializers for the various classes that may be (de)serialized, given
/// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
/// </summary>
private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }
#endregion
#region Private methods
/// <summary>
/// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
/// associated with it. For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
/// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
/// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
/// corresponding to that type to be (de)serialized by this ItemList.
/// </summary>
/// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
private Map loadSchema() {
Map map = new Map();
foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
Type t = inc.Type;
if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
}
return map;
}
/// <summary>
/// As the XML type name can be different to our internal class name for that XML type, we need to be able
/// to expect an XML element name that is different to our internal class name for that XML type. Hence,
/// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
/// internal class name for that XML type. This method returns the XML type name given our internal
/// class we're using to (de)serialize that XML type. If no XML TypeName is specified in our internal
/// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
/// </summary>
/// <param name="t">Our internal class used to (de)serialize an XML type.</param>
/// <returns>The XML type name corresponding to the given internal class.</returns>
private string xmlTypeName(Type t) {
string typeName = t.Name;
foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
}
return typeName;
}
#endregion
#region IXmlSerializable Members
/// <summary>
/// Reserved and should not be used.
/// </summary>
/// <returns>Must return null.</returns>
public XmlSchema GetSchema() {
return null;
}
/// <summary>
/// Generates a list of type T objects from their XML representation; stores them in this.Items.
/// </summary>
/// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
public void ReadXml(XmlReader reader) {
Map map = loadSchema();
int depth = reader.Depth;
List<T> items = new List<T>();
if (!reader.IsEmptyElement && reader.Read()) {
// While the reader is at a greater depth that the initial depth, ie. at one of the elements
// inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
while (reader.Depth > depth) {
try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
catch (InvalidOperationException iopEx) {
if (
iopEx.InnerException != null &&
iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized. Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
}
catch (KeyNotFoundException knfEx) {
throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type. Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
}
catch (Exception ex) {
throw ex;
}
}
}
this.Items = items;
}
/// <summary>
/// Converts a list of type T objects into their XML representation; writes them to the specified writer.
/// </summary>
/// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
public void WriteXml(XmlWriter writer) {
Map map = loadSchema();
foreach (T item in this.Items) {
map[xmlTypeName(item.GetType())].Serialize(writer, item);
}
}
#endregion
#region Public properties
public List<T> Items { get; set; }
#endregion
}
/// <summary>
/// A regular person.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
[XmlInclude(typeof(PersonBilingual))]
public class Person {
public string FullName { get; set; }
public int Age { get; set; }
public string Language { get; set; }
}
/// <summary>
/// A person who can speak a second language.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
public class PersonBilingual : Person {
public string SecondLanguage { get; set; }
}
/// <summary>
/// Some kind of account.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
[XmlInclude(typeof(AccountBank))]
public class Account {
public string AcctName { get; set; }
public string WithCompany { get; set; }
}
/// <summary>
/// A bank account.
/// </summary>
[XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
public class AccountBank : Account {
public int BalanceInEuros { get; set; }
}
}
Thanks everyone for your help!
I've got a requirement to pass parameters as Xml to my stored procedures.
I have a WCF service in the middle tier that makes calls to my data layer which in turn forwards the request to the appropriate stored procedure.
The design is that the WCF service is responsible for building the Xml to pass to the Repository.
I'm just wondering whether to keep control of what parameters are contained within the Xml in the middle tier or use a Dictionary built up by the client which I then convert to Xml in the middle tier?
At the moment I've gone for the latter - for example:
public TestQueryResponseMessage TestQuery(TestQueryRequestMessage message)
{
var result = Repository.ExecuteQuery("TestQuery", ParamsToXml(message.Body.Params));
return new TestQueryResponseMessage
{
Body = new TestQueryResponse
{
TopicItems = result;
}
}
}
private string ParamsToXml(Dictionary<string, string> nvc)
{
//TODO: Refactor
StringBuilder sb = new StringBuilder();
sb.Append("<params>");
foreach (KeyValuePair<string, string> param in nvc)
{
sb.Append("<param>");
sb.Append("<" + param.Key + ">");
sb.Append(param.Value);
sb.Append("</" + param.Key + ">");
sb.Append("</param>");
}
sb.Append("</params>");
return sb.ToString();
}
However I might need to do it the first way. E.g.
public TestQueryResponseMessage TestQuery(TestQueryRequestMessage message)
{
string xml = string.Format("<params><TestParameter>{0}</TestParameter></params>",message.Body.TestParameter)
var result = Repository.ExecuteQuery("TestQuery", xml);
return new TestQueryResponseMessage
{
Body = new TestQueryResponse
{
TopicItems = result;
}
}
}
What does the hivemind recommend?
If you must use xml; then rather than passing around a dictionary, I'd use a class that represents that data, and use XmlSerializer to fetch it as xml:
[Serializable, XmlRoot("args")]
public class SomeArgs {
[XmlElement("foo")] public string Foo { get; set; }
[XmlAttribute("bar")] public int Bar { get; set; }
}
...
SomeArgs args = new SomeArgs { Foo = "abc", Bar = 123 };
XmlSerializer ser = new XmlSerializer(typeof(SomeArgs));
StringWriter sw = new StringWriter();
ser.Serialize(sw, args);
string xml = sw.ToString();
This makes it much easier to manage which arguments apply to which queries, in an object-oriented way. It also means you don't have to do your own xml escaping...
Once you use Bob The Janitor's solution and you have your XML.
Create your stored procedure with a XML parameter. Then depending on how much XML you have and what your doing with it you can use Xquery or OpenXML to shred the XML document. Extract the data and perform the right action. This example is basic and pseudocode like but you should get the idea.
CREATE PROCEDURE [usp_Customer_INS_By_XML]
#Customer_XML XML
AS
BEGIN
EXEC sp_xml_preparedocument #xmldoc OUTPUT, #Customer_XML
--OPEN XML example of inserting multiple customers into a Table.
INSERT INTO CUSTOMER
(
First_Name
Middle_Name
Last_Name
)
SELECT
First_Name
,Middle_Name
,Last_Name
FROM OPENXML (#xmldoc, '/ArrayOfCustomers[1]/Customer',2)
WITH(
First_Name VARCHAR(50)
,Middle_Name VARCHR(50)
,Last_Name VARCHAR(50)
)
EXEC sp_xml_removedocument #xmldoc
END
You could just use an object Serialization class like this
public class Serialization
{
/// <summary>
/// Serializes the object.
/// </summary>
/// <param name="myObject">My object.</param>
/// <returns></returns>
public static XmlDocument SerializeObject(Object myObject)
{
XmlDocument XmlObject = new XmlDocument();
String XmlizedString = string.Empty;
try
{
MemoryStream memoryStream = new MemoryStream();
XmlSerializer xs = new XmlSerializer(myObject.GetType());
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
xs.Serialize(xmlTextWriter, myObject);
memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());
}
catch (Exception e)
{
System.Console.WriteLine(e);
}
XmlObject.LoadXml(XmlizedString);
return XmlObject;
}
/// <summary>
/// Deserializes the object.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="XmlizedString">The p xmlized string.</param>
/// <returns></returns>
public static T DeserializeObject<T>(String XmlizedString)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(XmlizedString));
//XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
Object myObject = xs.Deserialize(memoryStream);
return (T)myObject;
}
/// <summary>
/// To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.
/// </summary>
/// <param name="characters">Unicode Byte Array to be converted to String</param>
/// <returns>String converted from Unicode Byte Array</returns>
private static String UTF8ByteArrayToString(Byte[] characters)
{
UTF8Encoding encoding = new UTF8Encoding();
String constructedString = encoding.GetString(characters);
return (constructedString);
}
/// <summary>
/// Converts the String to UTF8 Byte array and is used in De serialization
/// </summary>
/// <param name="pXmlString"></param>
/// <returns></returns>
private static Byte[] StringToUTF8ByteArray(String pXmlString)
{
UTF8Encoding encoding = new UTF8Encoding();
Byte[] byteArray = encoding.GetBytes(pXmlString);
return byteArray;
}
}
then you don't have to build the XML by hand, plus you can use this with any item to transform it using XSLT
I would put the xml construction code inside the domain object. That way, you can just call obj.GetXML() from Web Service or data layer.