I am developing a WCF Service that can accept and return SOAP 1.1 messages. However, I am having problems deserializing the SOAP body to my object. At the moment I have;
namespace MyAPI
{
[ServiceContract]
public interface IMyService
{
[OperationContract(IsOneWay = false, Action = "*", ReplyAction = "*")]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml)]
Message ProcessMessgae(Message message);
}
}
public class MyService : IMyService
{
public Message ProcessMessgae(Message message)
{
MessageBuffer buffer = message.CreateBufferedCopy(8192);
// Get a copy of the original message.
Message msgCopy = buffer.CreateMessage();
// Take another copy of the same message.
Message returnMsg = buffer.CreateMessage();
// Use the msgCopy to get an XML and extract the body contents. Once this message has been read and consumed,
System.Xml.XmlDictionaryReader xrdr = msgCopy.GetReaderAtBodyContents();
string bodyData = xrdr.ReadOuterXml();
var reader = new StringReader(bodyData);
var serializer = new XmlSerializer(typeof(OTA_HotelAvailRQ));
var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader);
return returnMsg;
}
}
This is my auto-generated object
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2046.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.opentravel.org/OTA/2003/05")]
public partial class OTA_HotelAvailRQ : object, System.ComponentModel.INotifyPropertyChanged
{
private string echoTokenField;
private System.DateTime timeStampField;
private bool timeStampFieldSpecified;
private bool targetFieldSpecified;
private decimal versionField;
private bool availRatesOnlyField;
private bool availRatesOnlyFieldSpecified;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string EchoToken
{
get
{
return this.echoTokenField;
}
set
{
this.echoTokenField = value;
this.RaisePropertyChanged("EchoToken");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public System.DateTime TimeStamp
{
get
{
return this.timeStampField;
}
set
{
this.timeStampField = value;
this.RaisePropertyChanged("TimeStamp");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool TimeStampSpecified
{
get
{
return this.timeStampFieldSpecified;
}
set
{
this.timeStampFieldSpecified = value;
this.RaisePropertyChanged("TimeStampSpecified");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public decimal Version
{
get
{
return this.versionField;
}
set
{
this.versionField = value;
this.RaisePropertyChanged("Version");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public bool AvailRatesOnly
{
get
{
return this.availRatesOnlyField;
}
set
{
this.availRatesOnlyField = value;
this.RaisePropertyChanged("AvailRatesOnly");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool AvailRatesOnlySpecified
{
get
{
return this.availRatesOnlyFieldSpecified;
}
set
{
this.availRatesOnlyFieldSpecified = value;
this.RaisePropertyChanged("AvailRatesOnlySpecified");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
However, when I run the code I'm getting an exception on the following line var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader);
The SOAP XML I'm posting is;
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:UsernameToken>
<wsse:Username>test</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">thebest</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<OTA_HotelAvailRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="1.0" TimeStamp="2005-08-01T09:30:47+02:00" EchoToken="fb57388d" AvailRatesOnly="true">
</OTA_HotelAvailRQ>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The actual exception is;
What happens if you add your namespace to the xmlserializer?
var instance = (OTA_HotelAvailRQ)serializer.Deserialize(reader, "http://www.opentravel.org/OTA/2003/05");
Another option would be do something like the below, you may have to ReadOuterXml() instead of ReadInnerXml, the namespace may be able to be removed from this method too:
System.Xml.Linq.XElement body = System.Xml.Linq.XElement.Parse(msgCopy.GetReaderAtBodyContents().ReadInnerXml());
var instance = Deserialize<OTA_HotelAvailRQ>(body, "http://www.opentravel.org/OTA/2003/05");
public static T Deserialize<T>(XElement xElement, string nameSpace)
{
using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(xElement.ToString())))
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T), nameSpace);
return (T)xmlSerializer.Deserialize(memoryStream);
}
}
If you are still having problems, there is also a way using the DataContractSerializer or a SoapreflectionImporter, but you don't have a very complex object there those may be overkill.
Is there a way to add model information, like valid values, default values, summary, and other remarks into the swagger output?
For instance in c# how would I add the following comments and attributes into swagger?
/// <summary>
/// A clear summary
/// </summary>
/// <remarks>
/// Some remarks
/// </remarks>
public class A
{
public A()
{
_Field_A = 0;
_Field_B = string.Empty;
}
private int _Field_A { get; set; }
[Range(0, 150)]
public int Field_A
{
get
{
return _Field_A;
}
set
{
if (value != null) { _Field_A = value; }
}
}
private string _Field_B { get; set; }
/// <summary>
/// Field_B summary
/// </summary>
public string Field_B
{
get
{
return _Field_B;
}
set
{
if (value != null) { _Field_B = value; }
}
}
}
You will need to enable XML documentation file creation in your project properties:
Project Properties > Build > Check the XML Documentation File box
Then you can uncomment or add the following line to your SwaggerConfig.cs file:
c.IncludeXmlComments(GetXmlCommentsPath());
According to the Swashbuckle github, you can enable XML comments which will allow you to add the metadata accordingly.
httpConfiguration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "A title for your API");
c.IncludeXmlComments(GetXmlCommentsPathForControllers());
c.IncludeXmlComments(GetXmlCommentsPathForModels());
});
I'm using Fluent NHibernate. I have an object at runtime with lazy collections/properties that may or may not have been populated. I plan on serializing that object and need all the collections/properties to be populated before I do so. How can I "eager-load" my object at runtime?
If you already have the relationships set in you mappings you do not need to specify how to join in your query, you could simply use Fetch (even deep fetch) to specify the path to be loaded eaderly:
session.QueryOver<MasterEnt>()
.Where(x => x.Id == 2)
.Fetch(x => x.DetailEntList)
.Eager().List();
You can use ICriteria and manipulate the load through NHibernate.ICriteria.SetFetchMode(string, NHibernate.FetchMode).
Example:
DetailEnt.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace FetchTest
{
public class DetailEnt
{
private Int32? id;
/// <summary>
/// Entity key
/// </summary>
public virtual Int32? Id
{
get { return id; }
set { id = value; }
}
private String description;
/// <summary>
/// Description
/// </summary>
public virtual String Description
{
get { return description; }
set { description = value; }
}
private MasterEnt rIMaster;
/// <summary>
/// Gets or sets the RI master.
/// </summary>
/// <value>
/// The RI master.
/// </value>
public virtual MasterEnt RIMaster
{
get { return rIMaster; }
set { rIMaster = value; }
}
}
}
MasterEnt.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace FetchTest
{
public class MasterEnt
{
private Int32? id;
/// <summary>
/// Entity key
/// </summary>
public virtual Int32? Id
{
get { return id; }
set { id = value; }
}
private String description;
/// <summary>
/// Description
/// </summary>
public virtual String Description
{
get { return description; }
set { description = value; }
}
private ICollection<DetailEnt> detailEntList;
/// <summary>
/// <see cref="RIDetailEnt"/> one-to-many relationship.
/// </summary>
public virtual ICollection<DetailEnt> DetailEntList
{
get { return detailEntList; }
set { detailEntList = value; }
}
}
}
Forcing eager load at runtime:
NHibernate.ISession ss = GetSessionFromSomeWhere();
NHibernate.ICriteria crt = ss.CreateCriteria<MasterEnt>();
crt
.Add(NHibernate.Criterion.Expression.IdEq(17))
//here is "force eager load at runtime"
.SetFetchMode("DetailEntList", NHibernate.FetchMode.Join);
MasterEnt mEnt = crt.UniqueResult<MasterEnt>();
In this case I used "hbm". But the logic should be the same.
EDITED:
With "NHibernate 2.1.2" and "NHibernate.Linq"
INHibernateQueryable<MasterEnt> nhq = null;
IList<MasterEnt> masterList = null;
nhq = (INHibernateQueryable<MasterEnt>)(
from master in session.Linq<MasterEnt>()
where master.Id == 2
select master);
nhq.Expand("DetailEntList");
masterList = nhq.ToList<MasterEnt>();
with QueryOver<T>Left.JoinQueryOver from NHibernate 3:
IQueryOver<MasterEnt> query = session.QueryOver<MasterEnt>()
.Left.JoinQueryOver<DetailEnt>(m => m.DetailEntList)
.Where(m => m.Id == 2);
masterList = query.List<MasterEnt>();
These queries work this way independently if using "FluentNHibernate" or "hbm".
I made some code for it, soon I'll post the links to the files.
EDITED 2:
I've posted the code on q_10303345_1350308.7z (runnable by NUnit). There are explanations about the dependencies in the "dependencies \ readme.txt". The dll dependencies are loaded by NuGet.
I'm generating >100 classes from a lot of XSD's to build our test harness application for internal testing. I'm using XSD.exe to generate these classes, as doing it by bring in too much risk and it would take way too long.
I'm having an issue that is causing me a bit of pain. I'm trying to get this enumeration (for now just this one, then I'll do the same technique on the remaining ones...)
What I would like is for this to generate something like this(where the receiver element has a typeCode attribute):
<receiver typeCode="RCV">
<device classCode="DEV" determinerCode="INSTANCE">
<id root="..." extension="..." />
<asAgent classCode="AGNT">
<representedOrganization classCode="ORG" determinerCode="INSTANCE">
<id root="..." extension="..." />
</representedOrganization>
</asAgent>
</device>
</receiver>
however, receiver never gets an attribute, even though I have explicetly set in the test harness startup object:
this.receiver = new MCCI_MT000100BCReceiver();
this.receiver.typeCode = CommunicationFunctionType.RSP;
this.receiver.device = new MCCI_MT000100BCDevice();
I have an enumeration (I've added the XMLEnums and the [Flags] attribute as XSD.exe didn't bother)
[Flags]
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:hl7-org:v3")]
public enum CommunicationFunctionType
{
/// <remarks/>
[System.Xml.Serialization.XmlEnum("RCV")]
RCV = 1,
/// <remarks/>
[System.Xml.Serialization.XmlEnum("RSP")]
RSP = 2,
/// <remarks/>
[System.Xml.Serialization.XmlEnum("SND")]
SND = 3,
}
And this is one of the classes that use that enum:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(TypeName = "MCCI_MT000100BC.Receiver", Namespace = "urn:hl7-org:v3")]
public partial class MCCI_MT000100BCReceiver
{
private CS[] realmCodeField;
private II typeIdField;
private II[] templateIdField;
private MCCI_MT000100BCDevice deviceField;
private CommunicationFunctionType typeCodeField;
private bool typeCodeFieldSpecified;
public MCCI_MT000100BCReceiver()
{
this.typeCodeField = CommunicationFunctionType.RCV;
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("realmCode")]
public CS[] realmCode
{
get
{
return this.realmCodeField;
}
set
{
this.realmCodeField = value;
}
}
/// <remarks/>
public II typeId
{
get
{
return this.typeIdField;
}
set
{
this.typeIdField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("templateId")]
public II[] templateId
{
get
{
return this.templateIdField;
}
set
{
this.templateIdField = value;
}
}
/// <remarks/>
public MCCI_MT000100BCDevice device
{
get
{
return this.deviceField;
}
set
{
this.deviceField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public CommunicationFunctionType typeCode
{
get
{
return this.typeCodeField;
}
set
{
this.typeCodeField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool typeCodeSpecified
{
get
{
return this.typeCodeFieldSpecified;
}
set
{
this.typeCodeFieldSpecified = value;
}
}
}
I understand that serializing enumerations isn't something .NET likes to do at times, but any help would be appreciated.
There is no problem with enums in .net/XML; the problem is simply that you (or xsd) have added a "typeCodeSpecified" member. This is a pattern that is used to conditionally include values - specifically, for a member called "Foo", the engine checks for either a "FooSpecified" property, or a "ShouldSerializeFoo()" method. Since you never set "typeCodeSpecified" to true, it will return false, and the member will be omitted during serialization.
If you didn't want that, remove this member.
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!