Reputation: 109
I have two classes, "company" derived from Treenode, and "document".
[Serializable]
[XmlRoot("Company")]
public class Company : TreeNode, IXmlSerializable
{
private string _x;
private string _y;
public Company() { }
[XmlElement("X")]
public string X { get; set; }
[XmlElement("Y")]
public string Y { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Company")
{
x = reader["X"].ToString;
y = reader["Y"].ToString;
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("X", this.X.ToString());
writer.WriteElementString("Y", this.Y.ToString());
}
}
public class Document
{
private int _id;
private string _name;
private Company _company;
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company")]
public Company Comp { get; set; }
}
When I try to serialize document, then save to file, its work. But when I deserialize, reader parameter always null. Any solutions ?
This is my code for deserialize. "sr" is variable that hold xml text.
var sr = new StreamReader(ms);
var myStr = sr.ReadToEnd();
XmlSerializer serializer = new XmlSerializer(typeof(List<Document>));
using (TextReader tr = new StringReader(myStr))
{
List<Document> docu = (List<Document>)serializer.Deserialize(tr);
}
I try to implement ISerialization and debug but never fired, and try to overide serialize and deserialize method, and no luck.
I am using .NET Framework 3.5
Upvotes: 1
Views: 645
Reputation: 117275
As explained in this article, implementing IXmlSerializable
correctly is in fact quite tricky. Your implementation of ReadXml()
appears to violate the requirement that it consume the wrapper element itself as well as all the contents, like so:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.MoveToContent();
// Read attributes
Boolean isEmptyElement = reader.IsEmptyElement; // (1)
reader.ReadStartElement();
if (!isEmptyElement) // (1)
{
// Read Child elements X and Y
// Consume the end of the wrapper element
reader.ReadEndElement();
}
}
Furthermore, reader["X"]
returns the value of the XML attribute named "X"
, as explained in the docs. In your WriteXml()
you wrote the values of X
and Y
as nested XML elements. This explains the NullReferenceException
. You need to fix your read and write methods to be consistent.
However, I'd suggest an alternative to implementing IXmlSerializable
, which is to introduce a surrogate type for your Company
. First, extract all the non-TreeNode
properties of Company
into an interface:
public interface ICompany
{
string X { get; set; }
string Y { get; set; }
}
public class Company : TreeNode, ICompany
{
public Company() { }
public string X { get; set; }
public string Y { get; set; }
}
This is optional but makes the code clearer. Next, introduce a surrogate POCO that implements the same interface but does not inherit from TreeNode
:
public class CompanySurrogate : ICompany
{
public string X { get; set; }
public string Y { get; set; }
public static implicit operator CompanySurrogate(Company company)
{
if (company == null)
return null;
// For more complex types, use AutoMapper
return new CompanySurrogate { X = company.X, Y = company.Y };
}
public static implicit operator Company(CompanySurrogate surrogate)
{
if (surrogate == null)
return null;
// For more complex types, use AutoMapper
return new Company { X = surrogate.X, Y = surrogate.Y };
}
}
Notice that the surrogate can be implicitly converted to your original Company
type? Now you can use the surrogate in XML serialization by setting the XmlElementAttribute.Type
attribute property to be that of the surrogate:
public class Document
{
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company", Type = typeof(CompanySurrogate))]
public Company Comp { get; set; }
}
This avoids all possibilities of error in implementing IXmlSerializable
. Given the following input list:
var list = new List<Document>
{
new Document { Name = "my name", ID = 101, Comp = new Company { X = "foo", Y = "bar", NodeFont = new System.Drawing.Font("Arial", 10) } },
new Document { Name = "2nd name", ID = 222, Comp = new Company { X = "tlon", Y = "ukbar" } },
};
The following XML can will be generated, and can be deserialized successfully:
<ArrayOfDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Document>
<ID>101</ID>
<Name>my name</Name>
<Company>
<X>foo</X>
<Y>bar</Y>
</Company>
</Document>
<Document>
<ID>222</ID>
<Name>2nd name</Name>
<Company>
<X>tlon</X>
<Y>ukbar</Y>
</Company>
</Document>
</ArrayOfDocument>
That being said, I don't really recommend this design. Your UI should present your data model, it should not be your data model. See for instance How does one implement UI independent applications?. Replacing Company
with ICompany
whenever possible could be a first step to changing your design; you may find it becomes easier to replace your existing architecture with a TreeNode
that looks like this:
public class CompanyNode : TreeNode
{
public ICompany Company { get; set; }
}
Upvotes: 3