Xmlserialize a Custom Collection with an Attribute

XmlSerialize a custom collection with an Attribute

Collections generally don't make good places for extra properties. Both during serialization and in data-binding, they will be ignored if the item looks like a collection (IList, IEnumerable, etc - depending on the scenario).

If it was me, I would encapsulate the collection - i.e.

[Serializable]
public class MyCollectionWrapper {
[XmlAttribute]
public string SomeProp {get;set;} // custom props etc
[XmlAttribute]
public int SomeOtherProp {get;set;} // custom props etc
public Collection<string> Items {get;set;} // the items
}

The other option is to implement IXmlSerializable (quite a lot of work), but that still won't work for data-binding etc. Basically, this isn't the expected usage.

Xml Serialization of a Collection with Properties

If you find no solution you can try to use a different xml-serializer like sharpserializer or DataContractSerializer

How to add attributes for C# XML Serialization

Where do you have the type stored?

Normally you could have something like:

class Document {
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}

public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Document")]
public Document Document;
}

XmlSerializer doesn't serialize everything in my class

Following msdn:

Q: Why aren't all properties of collection classes serialized?

A: The XmlSerializer only serializes the elements in the collection when it detects either the IEnumerable or the ICollection interface. This behavior is by design. The only work around is to re-factor the custom collection into two classes, one of which exposes the properties including one of the pure collection types.

How to change the name of a collection of a custom class upon XML serialization?

Just use the proper constructor:

var xs = new XmlSerializer(typeof(List<Foo>), new XmlRootAttribute("foos"));

Also you could safely remove the [Serializable] attribute from your Foo class. This is for binary serialization and XmlSerializer ignores.

Customize XML Serialize With new Tags And Attributes And Root

It appears that you are trying to call a web service, with a custom security header. Usually, the easiest way to do this would be to generate a set of proxy classes from the WSDL of the target webservice.

Either

  • Right click on the use Add Service Reference / Add Web Reference from the Visual Studio
  • Or, if you have the WSDL and xsd files of the service, then use wsdl.exe command line tool (e.g. wsdl.exe *.wsdl *.xsd //language:c#)
  • See here on how to set security information on the ws:security header

However, if you are 100% sure that you need to obtain the exact soapEnv Xml above, I would suggest you keep your code 'as is' (i.e. just serialize MyObject in its default format using XmlSerializer or DataContractSerializer), and then use a XslCompiledTransform.

This XSLT will do exactly this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/MyObject">
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:smag="http://targetaddress.com/">
<soapenv:Header>
<Account>
<username><xsl:value-of select="Account/username"/></username>
<password><xsl:value-of select="Account/password"/></password>
</Account>
</soapenv:Header>
<soapenv:Body>
<smag:myobjinfos>
<destAdd><xsl:value-of select="destAdd"/></destAdd>
<Time><xsl:value-of select="Time"/></Time>
<maxNumb><xsl:value-of select="maxNumb"/></maxNumb>
</smag:myobjinfos>
</soapenv:Body>
</soapenv:Envelope> </xsl:template>
</xsl:stylesheet>

Converts

<?xml version="1.0"?>
<MyObject>
<destAdd>Destination</destAdd>
<Time>128</Time>
<maxNumb>99</maxNumb>
<Account>
<username>user</username>
<password>pass</password>
</Account>
</MyObject>

To this:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:smag="http://targetaddress.com/">
<soapenv:Header>
<Account>
<username>user</username>
<password>pass</password>
</Account>
</soapenv:Header>
<soapenv:Body>
<smag:myobjinfos>
<destAdd>Destination</destAdd>
<Time>128</Time>
<maxNumb>99</maxNumb>
</smag:myobjinfos>
</soapenv:Body>
</soapenv:Envelope>

XmlSerializer add attribute

Yes, you can do this using the XmlAttribute attribute. In order to do this, you need to define your custom attribute. It comes with the price of one more class that represents the array (nested in the root node). If you have no problem with this addition, then the solution can look like this:

public class ArrayOfMovie
{
// define the custom attribute
[XmlAttribute(AttributeName="CustomAttribute")]
public String Custom { get; set; }
// define the collection description
[XmlArray(ElementName="Items")]
public List<Movie> Items { get; set; }
}

public class Movie
{
public string VideoId { get; set; }
public string Title { get; set; }
}

Then create, fill and serialize as you already do - the one new thing is to fill your custom attribute:

// create and fill the list
var tmpList = new List<Movie>();
tmpList.Add(new Movie { VideoId = "1", Title = "Movie 1" });
tmpList.Add(new Movie { VideoId = "2", Title = "Movie 2" });
// create the collection
var movies = new ArrayOfMovie
{
Items = tmpList,
Custom = "yes" // fill the custom attribute
};
// serialize
using (var writer = XmlWriter.Create(serializationFile, settings))
{
var serializer = new XmlSerializer(typeof(ArrayOfMovie));
serializer.Serialize(writer, movies);
}

The XML output looks like this:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfMovie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
CustomAttribute="yes">
<Items>
<Movie>
<VideoId>1</VideoId>
<Title>Movie 1</Title>
</Movie>
<Movie>
<VideoId>2</VideoId>
<Title>Movie 2</Title>
</Movie>
</Items>
</ArrayOfMovie>

C# XmlSerializer: keep the value, override the element label

Here is another idea. If you define your class like so:

[Serializable]
public class MyObject
{
[XmlElement(ElementName = "MyElement")]
public string CurrentValueElement
{
get
{
return Element.CurrentValue;
}

set
{
Element = new MyElement
{
CurrentValue = value, PreviousValue = value
};
}
}

[XmlElement(ElementName = "MyOtherElement")]
public string CurrentValueOtherElement
{
get
{
return OtherElement.CurrentValue;
}
set {}
}

[XmlIgnore]
public MyElement Element { get; set; }

[XmlIgnore]
public MyElement OtherElement { get; set; }

}

Then, when the object is serialized, the output XML will look exactly like your example.

Also, if you extend the CurrentValueElement/CurrentValueOtherElement setter like this:

[XmlElement(ElementName = "MyElement")]
public string CurrentValueElement
{
get
{
return Element.CurrentValue;
}

set
{
Element = new MyElement
{
CurrentValue = value, PreviousValue = value
};
}
}

Then you'll be able to use the XmlSerializer to deserialize your objects directly without needing to resorting to LINQ.

C# Xml serialization, collection and root element

Ok, here is my final solution (hope it helps someone), that can serialize a plain array, List<>, HashSet<>, ...

To achieve this, we'll need to tell the serializer what root node to use, and it's kind of tricky...

1) Use 'XmlType' on the serializable object

[XmlType("link")]
public class LinkFinalVersion
{
[XmlAttribute("href")]
public string Url { get; set; }

[XmlAttribute("rel")]
public string Relationship { get; set; }
}

2) Code a 'smart-root-detector-for-collection' method, that will return a XmlRootAttribute

private XmlRootAttribute XmlRootForCollection(Type type)
{
XmlRootAttribute result = null;

Type typeInner = null;
if(type.IsGenericType)
{
var typeGeneric = type.GetGenericArguments()[0];
var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
if(typeCollection.IsAssignableFrom(type))
typeInner = typeGeneric;
}
else if(typeof (ICollection).IsAssignableFrom(type)
&& type.HasElementType)
{
typeInner = type.GetElementType();
}

// yeepeeh ! if we are working with a collection
if(typeInner != null)
{
var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
if((attributes != null)
&& (attributes.Length > 0))
{
var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
result = new XmlRootAttribute(typeName);
}
}
return result;
}

3) Push that XmlRootAttribute into the serializer

// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);

I told you it was tricky ;)


To improve this, you can :

A) Create a XmlTypeInCollectionAttribute to specify a custom root name (If the basic pluralization does not fit your need)

[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}

B) If possible, cache your XmlSerializer (in a static Dictionary for example).

In my testing, instanciating a XmlSerializer without the XmlRootAttributes takes 3ms.
If you specify an XmlRootAttribute, it takes around 80ms (Just to have a custom root node name !)



Related Topics



Leave a reply



Submit