I need to serialize objects with List property to XML to get XML code like this (I know it's not valid XML but my 3rd party application need this format):
<Filters>
<Criteria_0 Parameter="STATUS"
Operator="EQUAL"
Value="STARTED" />
<Criteria_1 Parameter="STATUS"
Operator="EQUAL"
Value="COMPLETED" />
</Filters>
I wrote code like this:
public class JobStatusListTask
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
}
public class JobFilterCriteria : IXmlSerializable
{
public static int Count = 0;
public string Parameter;
public CriteriaOperator Operator;
public string Value;
private JobFilterCriteria()
{
Parameter = string.Empty;
Value = string.Empty;
}
public JobFilterCriteria(string parameter, CriteriaOperator criteriaOperator, string value)
{
Parameter = parameter;
Operator = criteriaOperator;
Value = value;
}
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
writer.WriteStartElement(string.Format("Criteria_{0}", Count++));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
}
It works almost perfect. Serializer return XML code with unnecessary JobFilterCriteria element.
<?xml version="1.0" encoding="utf-8"?>
<Filters>
<JobFilterCriteria>
<Criteria_0
Parameter="STATUS" Operator="Equal" Value="ERROR" />
</JobFilterCriteria>
<JobFilterCriteria>
<Criteria_1
Parameter="STATUS" Operator="Equal" Value="STARTED" />
</JobFilterCriteria>
</Filters>
What I need to change to remove JobFilterCriteria from XML code?
I think, this code block will solve your problem, you need to serialize JobStatusListTask class.
public class JobStatusListTask : IXmlSerializable
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Filters");
foreach(var item in Filters)
{
writer.WriteStartElement("Criteria", string.Format("Criteria_{0}", Count++));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
writer.WriteEndElement();
}
}
I can't comment on answers, but I don't see how the above compiles as is, and it seems the namespace is being set accidentally, I'm sure you worked it out, but for posterity, a few changes that I believe are needed to make it work as intended:
public class JobStatusListTask : IXmlSerializable
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Filters");
for(int i = 0; i < Filters.Count; i++)
{
writer.WriteStartElement(string.Format("Criteria_{0}", i));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
writer.WriteEndElement();
}
}
Related
How can i remove the root xml namespace given that I am using asp.net core and the XmlDataContractSerializerOutputFormatter to format the response.
All of the returned xml docs have the following format
<?xml version="1.0" encoding="utf-8"?>
<response xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
some other stuff
</response>
I need to remove the xmlns:i="http://www.w3.org/2001/XMLSchema-instance" part.
Please try following code:
xmlDoc.Load(#"FILE_PATH");
XmlNodeList header_el = xmlDoc.GetElementsByTagName("response");
foreach (XmlNode child in header_el)
{
if (child.Attributes[0] != null)
child.Attributes.Remove(child.Attributes[0]);
}
Console.WriteLine(xmlDoc.OuterXml);
Output of this code is:
<?xml version="1.0" encoding="utf-8"?><response>
some other stuff
</response>
I ended up extending the existing XmlDataContractSerializerOutputFormatter, tapping into the method that creates the xmlwriter object and wrapping it in an adapter to edit the functionality, since editing the response wasn't really a practical nor feasible solution due to the structure of the response object as represented by the framework.
First the Formatter
public class CustomDataContractOutputFormatter : XmlDataContractSerializerOutputFormatter
{
public CustomDataContractOutputFormatter() : base(){}
public CustomDataContractOutputFormatter(XmlWriterSettings set) : base(set){}
public override XmlWriter CreateXmlWriter(TextWriter writer, XmlWriterSettings xmlWriterSettings)
{
var bas = base.CreateXmlWriter(writer, xmlWriterSettings);
var ret = new XmlWriterNoNamespace();
ret.AdaptedWriter = bas;
return ret;
}
}
Then the base adapter (part of it)
public class XmlWriterAdapter : XmlWriter
{
XmlWriter _adaptedWriter;
public XmlWriterAdapter():base(){}
public XmlWriter AdaptedWriter
{
get
{
return _adaptedWriter;
}
set
{
this._adaptedWriter = value;
}
}
public override WriteState WriteState { get{return _adaptedWriter.WriteState;} }
public override void Flush()
{
_adaptedWriter.Flush();
}
public override string LookupPrefix(string ns)
{
return _adaptedWriter.LookupPrefix(ns);
}
}
And finally the specific adapter with a hack to disable writing of the namespace
public class XmlWriterNoNamespace : XmlWriterAdapter
{
bool _skipAttribute;
public XmlWriterNoNamespace():base(){}
public override void WriteEndAttribute()
{
if(_skipAttribute)
{
_skipAttribute = false;
return;
}
base.WriteEndAttribute();
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
if(prefix.Equals("xmlns"))
{
_skipAttribute = true;
return;
}
base.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteString(string text)
{
if(_skipAttribute)
return;
base.WriteString(text);
}
}
I know that this topic has been covered in a number of different Stackoverflow articles, and I have read about 30 of them to make sure that what I am doing matches up with those. It is (even fro the .Net 2.0, 3.0, and 4.0 version of the answers)
I am attempting to create a very simple (at least in my mind) configuration file with custom attributes on the sections, and then optional items within the sections. So, now to the code:
<?xml version="1.0" encoding="utf-8" ?>
<CustomSiteConfiguration>
<Sites>
<Site siteRoot="/Site US" name="SiteUS_en">
</Site>
<Site siteRoot="/Site Canada" name="SiteCanada_en">
</Site>
<Site siteRoot="/Partner" name="Partner_en">
<siteSettings>
<setting name="" value="" />
</siteSettings>
<JavaScriptBundles>
<file name="" />
</JavaScriptBundles>
<CSSBundles>
<file name="" />
</CSSBundles>
</Site>
</Sites>
</CustomSiteConfiguration>
So, what you are looking at is a global Section of type Sites which contains multiple sections (CollectionElementCollections) of type Site. Site is defined by custom attributes on the item, as well as optional items within the section itself. So, siteSettings is optional, JavaScriptBundles is optional, and CSSBundles are also optional.
The C# Code is below:
For Sites
public class CustomGlobalSiteConfiguration : ConfigurationSection
{
public CustomGlobalSiteConfiguration() { }
[ConfigurationProperty("Sites")]
[ConfigurationCollection(typeof(SitesCollection), AddItemName="Site")]
public SitesCollection Sites
{
get
{
return (SitesCollection)base["Sites"];
}
}
}
For Site Collections
[ConfigurationCollection(typeof(SitesCollection), AddItemName="Site")]
public class SitesCollection : ConfigurationElementCollection
{
// Constructor
public SitesCollection() { }
/*
public CustomSiteConfiguration this[int index]
{
get { return (CustomSiteConfiguration)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public siteSetting this [int index]
* */
protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomSiteConfiguration)element).name;
}
protected override ConfigurationElement CreateNewElement()
{
return new SitesCollection();
}
}
For Site Definition
/**
* Overarching structure of the Site Item
**/
public class CustomSiteConfiguration : ConfigurationElement
{
[ConfigurationProperty("siteRoot")]
public String siteRoot
{
get
{
return (String)this["siteRoot"];
}
set
{
this["siteRoot"] = value;
}
}
[ConfigurationProperty("name")]
public String name
{
get
{
return (String)this["name"];
}
set
{
this["name"] = value;
}
}
[ConfigurationProperty("siteSettings", IsRequired=false)]
public CustomSiteSiteSettings siteSettings
{
get
{
return this["siteSettings"] as CustomSiteSiteSettings;
}
}
[ConfigurationProperty("JavaScriptBundles", IsRequired = false)]
public JavaScriptBundles javaSciptBundle
{
get
{
return this["JavaScriptBundles"] as JavaScriptBundles;
}
}
[ConfigurationProperty("CSSBundles", IsRequired = false)]
public CSSBundles cssBundle
{
get
{
return this["CSSBundles"] as CSSBundles;
}
}
} // end of public class CustomSiteConfiguration : ConfigurationSection
For SiteSettings Definition
/**
* Subsection - Site Settings
**/
public class CustomSiteSiteSettings : ConfigurationElementCollection
{
// Constructor
public CustomSiteSiteSettings() { }
public siteSetting this [int index]
{
get { return (siteSetting)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public siteSetting this [int index]
protected override object GetElementKey(ConfigurationElement element)
{
return ((siteSetting)element).name;
}
protected override ConfigurationElement CreateNewElement()
{
return new CustomSiteSiteSettings();
}
} // end of public class CustomSiteSiteSettings : ConfigurationSection
Site Setting Element
public class siteSetting : ConfigurationElement
{
[ConfigurationProperty("name")]
public String name
{
get
{
return (String)this["name"];
}
set
{
this["name"] = value;
}
} // end of public String name
[ConfigurationProperty("value")]
public String value
{
get
{
return (String)this["value"];
}
set
{
this["value"] = value;
}
} // end of public String value
} // end of public class siteSetting : ConfigurationElement
I am leaving out the other items for space, but the other parts look the same. Basically, what is happening is, I am getting
Unrecognized attribute 'siteRoot'. Note that attribute names are case-sensitive.
Looking at everything, it appears that I should be fine, however, I think I may be doing too much and missing things. Any help with this would be greatly appreciated.
Thanks
I have figured out what was wrong with my code. I am going to provide the information below. I used the following article for help on tracking down some of the pieces: How to implement a ConfigurationSection with a ConfigurationElementCollection
I took the entire code base back to nothing and built it up from scratch. The XML is still the same
<?xml version="1.0" encoding="utf-8" ?>
<CustomSiteConfiguration>
<Sites>
<Site siteRoot="/Site US" name="SiteUS_en">
</Site>
<Site siteRoot="/Site Canada" name="SiteCanada_en">
</Site>
<Site siteRoot="/Partner" name="Partner_en">
<siteSettings>
<setting name="" value="" />
</siteSettings>
<JavaScriptBundles>
<file name="" />
</JavaScriptBundles>
<CSSBundles>
<file name="" />
</CSSBundles>
</Site>
</Sites>
</CustomSiteConfiguration>
So, first I started with the Sites Container
public class CustomSiteSettingsSection : ConfigurationSection
{
[ConfigurationProperty("Sites")]
[ConfigurationCollection(typeof(SiteCollection), AddItemName="Site")]
public SiteCollection Sites
{
get
{
return (SiteCollection)base["Sites"];
}
} // end of public SiteCollection Site
} // end of public class CustomSiteSettings : ConfigurationSection {
And then I added the SiteCollection for the Collection of Site Elements
public class SiteCollection : ConfigurationElementCollection
{
// Constructor
public SiteCollection() { }
public SiteElement this[int index]
{
get { return (SiteElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public SiteElement this[int index]
protected override ConfigurationElement CreateNewElement()
{
return new SiteElement();
} // end of protected override ConfigurationElement CreateNewElement()
protected override object GetElementKey(ConfigurationElement element)
{
return ((SiteElement)element).name;
}
} // end of public class SiteCollection : ConfigurationElementCollection
Then I added the definition for the Site with optional values
public class SiteElement : ConfigurationElement
{
// Constructor
public SiteElement() { }
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public String name
{
get { return (String)this["name"]; }
set { this["name"] = value; }
} // end of public String name
[ConfigurationProperty("siteRoot", IsRequired = true)]
public String siteRoot
{
get { return (String)this["siteRoot"]; }
set { this["siteRoot"] = value; }
} // end of public String siteRoot
[ConfigurationProperty("siteSettings", IsRequired=false)]
[ConfigurationCollection(typeof(SiteSettingsElementCollection), AddItemName = "setting")]
public SiteSettingsElementCollection siteSettings
{
get
{
return (SiteSettingsElementCollection)base["siteSettings"];
}
} // end of public SiteCollection Site
} // end of public class SiteElement : ConfigurationElement
Next I added the SiteSettings Collection
public class SiteSettingsElementCollection : ConfigurationElementCollection
{
// Constructor
public SiteSettingsElementCollection() { }
public SiteSettingElement this[int index]
{
get { return (SiteSettingElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public SiteElement this[int index]
protected override ConfigurationElement CreateNewElement()
{
return new SiteSettingElement();
} // end of protected override ConfigurationElement CreateNewElement()
protected override object GetElementKey(ConfigurationElement element)
{
return ((SiteSettingElement)element).name;
}
} // end of public class SiteCollection : ConfigurationElementCollection
And finally, I added the Setting Element Definition
public class SiteSettingElement : ConfigurationElement
{
public SiteSettingElement() { }
[ConfigurationProperty("name", IsRequired=true, IsKey=true)]
public String name
{
get { return (String)this["name"]; }
set { this["name"] = value; }
} // end of public String name
[ConfigurationProperty("value", IsRequired = true)]
public String value
{
get { return (String)this["value"]; }
set { this["value"] = value; }
} // end of public String value
} // end of public class SiteSettingElement : ConfigurationElement
At this point, I just repeat the same for the two bundles. In the end this all works, and allows for optional settings and sections.
Example from MSDN on making a custom config section that should work as follows,
class RemoteServiceSection : ConfigurationSection
{
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
[ConfigurationCollection(typeof(RemoteServiceCollection), AddItemName="addService", ClearItemsName="clearServices",
RemoveItemName="removeService")]
public RemoteServiceCollection Services
{
get
{
return this["remoteServices"] as RemoteServiceCollection;
}
}
}
class RemoteServiceCollection : ConfigurationElementCollection, IList<RemoteServiceElement>
{
public RemoteServiceCollection()
{
RemoteServiceElement element = (RemoteServiceElement)CreateNewElement();
Add(element);
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.AddRemoveClearMap;
}
}
protected override ConfigurationElement CreateNewElement()
{
return new RemoteServiceElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((RemoteServiceElement)element).Hostname;
}
protected override string ElementName
{
get
{
return "remoteService";
}
}
public new IEnumerator<RemoteServiceElement> GetEnumerator()
{
foreach (RemoteServiceElement element in this)
{
yield return element;
}
}
public void Add(RemoteServiceElement element)
{
BaseAdd(element, true);
}
public void Clear()
{
BaseClear();
}
public bool Contains(RemoteServiceElement element)
{
return !(BaseIndexOf(element) < 0);
}
public void CopyTo(RemoteServiceElement[] array, int index)
{
base.CopyTo(array, index);
}
public bool Remove(RemoteServiceElement element)
{
BaseRemove(GetElementKey(element));
return true;
}
bool ICollection<RemoteServiceElement>.IsReadOnly
{
get { return IsReadOnly(); }
}
public int IndexOf(RemoteServiceElement element)
{
return BaseIndexOf(element);
}
public void Insert(int index, RemoteServiceElement element)
{
BaseAdd(index, element);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public RemoteServiceElement this[int index]
{
get
{
return (RemoteServiceElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
}
class RemoteServiceElement : ConfigurationElement
{
public RemoteServiceElement() { }
public RemoteServiceElement(string ip, string port)
{
this.IpAddress = ip;
this.Port = port;
}
[ConfigurationProperty("hostname", IsKey = true, IsRequired = true)]
public string Hostname
{
get
{
return (string)this["hostname"];
}
set
{
this["hostname"] = value;
}
}
[ConfigurationProperty("ipAddress", IsRequired = true)]
public string IpAddress
{
get
{
return (string)this["ipAddress"];
}
set
{
this["ipAddress"] = value;
}
}
[ConfigurationProperty("port", IsRequired = true)]
public string Port
{
get
{
return (string)this["port"];
}
set
{
this["port"] = value;
}
}
}
}
I am getting the error that says 'Unrecognized element 'addService'. I think I've followed the MSDN article exactly. It can be found here - http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx
Thanks in advance for your help. This is what I wrote in app.config (with brackets of course that dont show up here?):
<remoteServices>
<addService hostname="xxxxxxx" ipAddress="xxx.x.xxx.xx" port="xxxx" >
</remoteServices>
Here is app.config as requested, x'ing out the specific names just for privacy purposes, they are just strings:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
For future generations:
Your config should look like this:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
</remoteServices>
</configuration>
Why?
You add to node:
<configSections>
custom section named:
name="remoteServices"
with type
type="AqEntityTests.RemoteServiceSection
and then in code, you add property to your custom section:
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
Meaning you created node inside node with both having same name. Because of that you have received error "Unrecognized element 'addService'". Just compiler informing you that such element should not be in that node.
Two links for quick learning of custom configuration:
Custom Configuration Sections for Lazy Coders
How to create sections with collections
You might also look into using an unnamed default collection as I mention here
That allows you to add items in the manner you suggest.
I want to implement Craig Andera's custom XML configuration handler in a slightly different scenario. What I want to be able to do is to read in a list of arbitrary length of custom objects defined as:
public class TextFileInfo
{
public string Name { get; set; }
public string TextFilePath { get; set; }
public string XmlFilePath { get; set; }
}
I managed to replicate Craig's solution for one custom object but what if I want several?
Craig's deserialization code is:
public class XmlSerializerSectionHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
XPathNavigator nav = section.CreateNavigator();
string typename = (string)nav.Evaluate("string(#type)");
Type t = Type.GetType(typename);
XmlSerializer ser = new XmlSerializer(t);
return ser.Deserialize(new XmlNodeReader(section));
}
}
I think I could do this if I could get
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
to work but it throws
Could not load type 'System.Collections.Generic.List<Test1.TextFileInfo>' from assembly 'Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I don't think this would work in that scenario. Craig's solution works well for simple object graphs, but collections are a little trickier. Lists are serialised as arrays, so putting your example in, a serialised List is something like:
<ArrayOfTextFileInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlsns:xsd="http://www.w3.org/2001/XMLSchema>
<TextFileInfo>
<Name>My Text File</Name>
<TextFilePath>C:\MyTextFile.txt</TextFilePath>
<XmlFilePath>C:\MyXmlFile.xml</XmlFilePath>
</TextFileInfo>
</ArrayOfTextFileInfo>
Now, I'm guessing you could probably put that in a config, as long as the config section is named as "ArrayOfTextFileInfo". Not exactly that friendly. I think what you should probably do is use the standard configuration classes to build this:
public class TextFileConfigurationElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name {
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("textFilePath")]
public string TextFilePath {
get { return (string)this["textFilePath"]; }
set { this["textFilePath"] = value; }
}
[ConfigurationProperty("xmlFilePath")]
public string XmlFilePath {
get { return (string)this["xmlFilePath"]; }
set { this["xmlFilePath"] = value; }
}
}
[ConfigurationCollection(typeof(TextFileConfigurationElement))]
public class TextFileConfigurationElementCollection : ConfigurationElementCollection
{
protected override void CreateNewElement() {
return new TextFileConfigurationElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((TextFileConfigurationElement)element).Name;
}
}
public class TextFilesConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("files")]
public TextFileConfigurationElementCollection Files {
get { return (TextFileConfigurationElementCollection)this["files"]; }
set { this["files"] = value; }
}
public static TextFilesConfigurationSection GetInstance() {
return ConfigurationManager.GetSection("textFiles") as TextFilesConfigurationSection;
}
}
Once you've registered the config section:
<configSections>
<add name="textFiles" type="...{type here}..." />
</configSections>
You can add in the configs:
<textFiles>
<files>
<add name="File01" textFilePath="C:\File01.txt" xmlTextFile="C:\File01.xml" />
</files>
</textFiles>
Using that in code:
public List<TextFileInfo> GetFiles() {
var list = new List<TextFileInfo>();
var config = TextFileConfigurationSection.GetInstance();
if (config == null)
return list;
foreach (TextFileConfigurationElement fileConfig in config.Files) {
list.Add(new TextFileInfo
{
Name = fileConfig.Name,
TextFilePath = fileConfig.TextFilePath,
XmlFilePath = fileConfig.XmlFilePath
});
}
return list;
}
Also, this:
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
Won't work for a couple of reasons, you haven't fully qualified the TextFileInfo type (needs a namespace), and your definition of a generic type is wrong (I can see why you haven't specified it that way), it should look like:
Type t = Type.GetType("System.Collections.Generic.List`1[MyNamespace.TextFileInfo]");
Hope that helps!
The setup:
class Item
{
private int _value;
public Item()
{
_value = 0;
}
public int Value { get { return _value; } set { _value = value; } }
}
class ItemCollection : Collection<Item>
{
private string _name;
public ItemCollection()
{
_name = string.Empty;
}
public string Name { get {return _name;} set {_name = value;} }
}
Now, trying to serialize using the following code fragment:
ItemCollection items = new ItemCollection();
...
XmlSerializer serializer = new XmlSerializer(typeof(ItemCollection));
using (FileStream f = File.Create(fileName))
serializer.Serialize(f, items);
Upon looking at the resulting XML I see that the ItemCollection.Name value is not there!
I think what may be happening is that the serializer sees the ItemCollection type as a simple Collection thus ignoring any other added properties...
Is there anyone having encountered such a problem and found a solution?
Regards,
Stécy
This behavior is "By Design". When deriving from a collection class the Xml Seralizier will only serialize the collection elements. To work around this you should create a class that encapsulates the collection and the name and have that serialized.
class Wrapper
{
private Collection<Item> _items;
private string _name;
public Collection<Item> Items { get {return _items; } set { _items = value; } }
public string Name { get { return _name; } set { _name = value; } }
}
A detailed discussion is available here: http://blogs.vertigo.com/personal/chris/Blog/archive/2008/02/01/xml-serializing-a-derived-collection.aspx
XmlSerializer is evil. That said, any object that implements IEnumerable will be serialized as an simple collection, ignoring any extra properties you've added yourself.
You will need to create a new class that holds both your property and a property that returns the collection.
I am not sure if I am missing something, but do you want the resulting xml to be
<ItemCollection>
<Name>name val</Name>
<Item>
<Value>1</alue>
</Item
<Item>
<Value>2</alue>
</Item
</ItemCollection>
If so, just apply the XmlRoot attribute to the itemcollection class and set the element name...
[XmlRoot(ElementName="ItemCollection")]
public class ItemCollection : Collection<Item>
{
[XmlElement(ElementName="Name")]
public string Name {get;set;}
}
This will instruct the serializer to output the required name for you collection container.
You can also try to implelemnt your own serialization using IXmlSerializable interface
public class ItemCollection : Collection<Item>,IXmlSerializable
{
private string _name;
public ItemCollection()
{
_name = string.Empty;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("name", _name);
List<Item> coll = new List<Item>(this.Items);
XmlSerializer serializer = new XmlSerializer(coll.GetType());
serializer.Serialize(writer, coll);
}
#endregion
}
Above code will generate the serialized xml as
<?xml version="1.0"?>
<ItemCollection>
<name />
<ArrayOfItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Item>
<Value>1</Value>
</Item>
<Item>
<Value>2</Value>
</Item>
</ArrayOfItem>
</ItemCollection>
public class Animals : List<Animal>, IXmlSerializable
{
private static Type[] _animalTypes;//for IXmlSerializable
public Animals()
{
_animalTypes = GetAnimalTypes().ToArray();//for IXmlSerializable
}
// this static make you access to the same Animals instance in any other class.
private static Animals _animals = new Animals();
public static Animals animals
{
get {return _animals; }
set { _animals = value; }
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
reader.MoveToContent();
reader.ReadStartElement("Animals");
// you MUST deserialize with 'List<Animal>', if Animals class has no 'List<Animal>' fields but has been derived from 'List<Animal>'.
List<Animal> coll = GenericSerializer.Deserialize<List<Animal>>(reader, _animalTypes);
// And then, You can set 'Animals' to 'List<Animal>'.
_animals.AddRange(coll);
reader.ReadEndElement();
//Read Closing Element
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("Animals");
// You change 'List<Animal>' to 'Animals' at first.
List<Animal> coll = new List<Animal>(_animals);
// And then, You can serialize 'Animals' with 'List<Animal>'.
GenericSerializer.Serialize<List<Animal>>(coll, writer, _animalTypes);
writer.WriteEndElement();
}
#endregion
public static List<Type> GetAnimalTypes()
{
List<Type> types = new List<Type>();
Assembly asm = typeof(Animals).Assembly;
Type tAnimal = typeof(Animal);
//Query our types. We could also load any other assemblies and
//query them for any types that inherit from Animal
foreach (Type currType in asm.GetTypes())
{
if (!currType.IsAbstract
&& !currType.IsInterface
&& tAnimal.IsAssignableFrom(currType))
types.Add(currType);
}
return types;
}
}