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
Getting Relative Virtual Path from Physical Path
How to Print a Text File on Thermal Printer Using Printdocument
How to Determine the True Pixel Size of My Monitor in .Net
Sqlite .Net Performance, How to Speed Up Things
Webclient Does Not Support Concurrent I/O Operations
How to Create a Constant Value - Only Primitive Types
Use a Custom Thousand Separator in C#
Split String into Smaller Strings by Length Variable
Newtonsoft.JSON Assembly Package Version Mismatch
Generic List of Generic Objects
How to Disable Visual Styles for Just One Control, and Not Its Children
Deserialize JSON String to Dictionary<String,Object>
When Is Using the C# Ref Keyword Ever a Good Idea
How Does Deferred Linq Query Execution Actually Work