XmlSerializer is responsible of the Xml serialization.
|
SoapFormatter is used to create a Soap envelop and use an object graph to generate the result. The XmlSerializer process use only the public data and the result is a more common xml file. The Web Service in .Net use an XmlSerializer to generate the output contained in the Soap message. The SoapFormatter and the BinaryFormatter are used in the .Net Remoting serialization process.
|
|
Nothing, but there is some constraint :
- Your object must have a public empty constructor.
- Field or property must be public
- Their return type must also respect serialization rules.
- Property must be read write.
|
|
- Based on international standard (XML).
- Cross platforms.
- Readable and can be edited easily.
|
public static void SerializeXml( object aObject, string aFileName) {
using (FileStream _FileStream = new FileStream(aFileName, FileMode.Create)) {
XmlSerializer _Serializer = new XmlSerializer ( aObject.GetType());
_Serializer.Serialize(_FileStream, aObject);
}
}
|
public static object DeserializeXml( string aFileName, Type aType) {
using (FileStream _FileStream = new FileStream(aFileName, FileMode.Create)) {
XmlSerializer _Serializer = new XmlSerializer (aType);
return _Serializer.Deserialize(_FileStream);
}
}
|
public static T DeserializeXml<T>(string aFileName) {
using (FileStream _FileStream = new FileStream(aFileName, FileMode.Open)) {
XmlSerializer _Serializer = new XmlSerializer (typeof(T));
return (T)_Serializer.Deserialize(_FileStream);
}
}
|
If you use a XmlSerializer, mark your property with the custom attribute XmlIgnoreAttribute and if you use
a SoapFormatter, use a SoapIgnoreAttribute instead.
|
|
Use the attribute XmlElementAttribute or XmlAttributeAttribute with the new name as parameter. To rename a class, use the XmlTypeAttribute.
[XmlType("city")]
public class Town {
private string name;
private string state;
[XmlElement("townname")]
public string Name {
get {
return name;
}
set {
name = value;
}
}
[XmlAttribute("state")]
public string State {
get {
return state;
}
set {
state = value;
}
}
}
Result:
<?xml version="1.0"?>
<city state="CA">
<townname>Los Angeles</townname>
</city>
|
XPath can do the job.. This is an example where we read the town name (Xml element) )and the state attribute from the Xml file:
XmlDocument document = new XmlDocument();
document.Load("town.xml");
//Select an element
string _TownName = document.SelectSingleNode("//city/townname").InnerText;
//Select an attribute
string _State =
document.SelectSingleNode("//city").Attributes["state"].InnerText;
MessageBox.Show(_TownName + " is in " + _State);
This is an example where we read an application setting from a web config file.
XmlDocument configDocument = new XmlDocument();
configDocument.Load("web.config");
// Add the namespace with .Net web.config
XmlNamespaceManager nsmgr = new XmlNamespaceManager(configDocument.NameTable);
nsmgr.AddNamespace("ms", "http://schemas.microsoft.com/.NetConfiguration/v2.0");
string appSetting = configDocument.SelectSingleNode(
"/ms:configuration/ms:appSettings/ms:add[@key='DatabaseName']",
nsmgr).Attributes["value"].InnerText;
The orignal config file:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<appSettings>
<add key="DatabaseName" value="Pubs"/>
<add key="DatabaseServer" value="(local)"/>
</appSettings>
</configuration>
Note, you don't need the XmlNamespaceManager if you don't have a default namespace. Web.config in the ealier version does not contain a default namespace.
|
|
By default properties are serialized as Xml elements, but if you add an XmlAttributeAttribute to a property, .Net will generate an attribute instead. It's must be type compatible with an Xml attribute. See example here
...
[XmlAttribute("state")]
public string State {
get {
return state;
}
set {
state = value;
}
}
|
|
You need to Implement the interface IXmlSerializable. This class is available in the .Net 1.X but it's was not documented. It's now official available with .Net 2.0. With custom serialization, it's possible to optimize the output and generate only what is needed. In this example, we generate only the non empty properties
public class SessionInfo : IXmlSerializable {
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema() {
return null;
}
public void ReadXml(XmlReader reader) {
UserName = reader.GetAttribute("UserName");
while (reader.Read()) {
if (reader.IsStartElement()) {
if (!reader.IsEmptyElement) {
string _ElementName = reader.Name;
reader.Read(); // Read the start tag.
if(_ElementName == "MachineName") {
MachineName = reader.ReadString();
} else {
reader.Read();
}
}
}
}
}
public void WriteXml(XmlWriter writer) {
if (!String.IsNullOrEmpty(UserName))
writer.WriteAttributeString("UserName", UserName);
if (!String.IsNullOrEmpty(MachineName))
writer.WriteElementString("MachineName", MachineName);
}
#endregion
private string machineName;
private string userName;
public string MachineName {
get {
return machineName;
}
set {
machineName = value;
}
}
public string UserName {
get {
return userName;
}
set {
userName = value;
}
}
}
The output could be something like that if UserName and Machine are not empty
:
<?xml version="1.0"?>
<SessionInfo UserName="David">
<MachineName>MyMachine</MachineName>
</SessionInfo>
or it could be like that if MachineName is empty :
<?xml version="1.0"?>
<SessionInfo UserName="David">
|
|
Array are compatible with the serialization, but all elements must be of the same type. If not all types must be specified, see below.
|
|
It's possible to tag the array with all the possible type. Use XmlInclude on the class containing the array
or XmlArrayItem. All the possible types must be specified with the attributes. Array types must be known because of the Xml schema. XmlInclude could be used for property that returned differente types. It's complicate object inheritance since all types must be known.
This example will fail with a message "There was an error generating the XML document." because the Cars could contain undefined types.
public class Car {}
public class Ford : Car{}
public class Honda: Car {}
public class Toyota: Car {}
public class CarSeller {
private List<Car> cars = new List<Car>();
public List<Car> Cars {
get {
return cars;
}
set {
cars = value;
}
}
}
...
Ford _Ford = new Ford();
Honda _Honda = new Honda();
Toyota _Toyota = new Toyota();
CarSeller _Seller = new CarSeller();
_Seller.Cars.Add(_Ford);
_Seller.Cars.Add(_Honda);
_Seller.Cars.Add(_Toyota);
SerializationHelper.SerializeXml(_Seller, @"seller.xml");
Three possible solutions:
#1 Add XmlIncludeAttribute to the Seller class:
[XmlInclude(typeof(Ford))]
[XmlInclude(typeof(Honda))]
[XmlInclude(typeof(Toyota))]
public class CarSeller {
private List<Car> cars = new List<Car>();
public List<Car> Cars {
get {
return cars;
}
set {
cars = value;
}
}
}
with this result
<?xml version="1.0"?>
<CarSeller>
<Cars>
<Car xsi:type="Ford" />
<Car xsi:type="Honda" />
<Car xsi:type="Toyota" />
</Cars>
</CarSeller>
#2 Add XmlArrayItem to the property named Cars:
public class CarSeller {
private List<Car> cars = new List<Car>();
[XmlArrayItem(typeof(Ford))]
[XmlArrayItem(typeof(Honda))]
[XmlArrayItem(typeof(Toyota))]
public List<Car> Cars {
get {
return cars;
}
set {
cars = value;
}
}
}
with this result
<?xml version="1.0"?>
<CarSeller>
<Cars>
<Ford />
<Honda />
<Toyota />
</Cars>
</CarSeller>
#3 Implement our own serialization with IXmlSerializable :
see items above
|
|
Collection are serialized correctly, but they must contains only object of same types. Read only properties of type ArrayList, List<type> and other collections will be serialized and deserialized correctly.
public class Role {
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public class UserAccount {
private string userName;
public string UserName {
get {
return userName;
}
set {
userName = value;
}
}
// Generic version
private List<Role> roles = new List<Role>();
public List<Role> Roles {
get {
return roles;
}
}
// ArrayList version
private ArrayList roleList = new ArrayList();
public ArrayList RoleList {
get {
return roleList;
}
}
// String collection version
private StringCollection roleNames = new StringCollection();
public StringCollection RoleNames {
get {
return roleNames;
}
}
UserAccount _UserAccount = new UserAccount();
_UserAccount.UserName = "dhervieux";
Role _RoleAdmin = new Role();
_RoleAdmin.Name = "Admin";
Role _RoleSales = new Role();
_RoleSales.Name = "Sales";
_UserAccount.RoleList.Add(_RoleAdmin);
_UserAccount.RoleList.Add(_RoleSales);
_UserAccount.Roles.Add(_RoleAdmin);
_UserAccount.Roles.Add(_RoleSales);
_UserAccount.RoleNames.Add("Admin");
_UserAccount.RoleNames.Add("Sales");
SerializationHelper.SerializeXml(_UserAccount, @"useraccount.xml");
UserAccount _Result =
SerializationHelper.DeserializeXml<UserAccount>(@"useraccount.xml");
will produce:
<?xml version="1.0"?>
<UserAccount>
<UserName>dhervieux</UserName>
<Roles>
<Role>
<Name>Admin</Name>
</Role>
<Role>
<Name>Sales</Name>
</Role>
</Roles>
<RoleList>
<anyType xsi:type="Role">
<Name>Admin</Name>
</anyType>
<anyType xsi:type="Role">
<Name>Sales</Name>
</anyType>
</RoleList>
<RoleNames>
<string>Admin</string>
<string>Sales</string>
</RoleNames>
</UserAccount>
|
XmlSerializer generate an assembly in memory optimized for each type. That's explain why the first call to a Web Service is so long. In .Net 2.0, there is an option in the project properties of Visual Studio to generate the Xml serialization assembly. Use it directly in the IDE or use sgen.exe, this tools come with the .Net Framework SDK.
|
Pregenerate your serialization assembly with Visual Studio or sgen.exe. See details in answer above. Implementing your own serialization could also increase the performance.
|
Yes it possible. .Net will name the Array and save the content. All the data must be of the same type.
bool[] _BoolArray = new bool[] { true, false, false, true };
// Serialization
SerializationHelper.SerializeXml(_BoolArray, @"boolarray.xml");
//Deserialization
_Result = SerializationHelper.DeserializeXml<bool[]>(@"directboolarray.xml");
will produce:
<?xml version="1.0"?>
<ArrayOfBoolean>
<boolean>true</boolean>
<boolean>false</boolean>
<boolean>false</boolean>
<boolean>true</boolean>
</ArrayOfBoolean>
|
Web Services are using SOAP to communicate, but returned objets or parameters are serialized with the XmlSerializer. Write unit test to be sure that your objects are serializable.
|
|
No, they are not, except for collections.
|
You need to encapsulate your array in a structure or a class an serialize it. Multidimensional array are not serializable by default.
|
|
There is an undocumented way of doing that, you need to create a method named ShouldSerialize<propertyname> where <propertyname> is replaced by the property name. This method should return a boolean that indicate if the property must be serialized or not. For exemple, if you have list with no item, there is no need to serialize an empty list.
public class Registration {
private string[] users = new string[0];
public bool ShouldSerializeUsers() {
return users.Length > 0;
}
public string[] Users {
get {
return users;
}
set {
users = value;
}
}
}
Result :
<?xml version="1.0"?>
<Registration />
Without the ShouldSerializeUsers :
<?xml version="1.0"?>
<Registration >
<Users />
</Registration>
|
The SerializationAttribute is only used for the binary serialization. That does not mean that it will work with an XmlSerializer. That's the case of the SortedList.
|
|
It's possible to remove the xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" and xmlns:xsd="http://www.w3.org/2001/XMLSchema" from the serialization result, it's possible to add an XmlSerializerNamespaces with an empty namespace mapping.
User _User = new User(new string[] { "Admin", "Manager" });
using (FileStream _FileStream = new FileStream("user.xml", FileMode.Create)) {
XmlSerializer _Serializer = new XmlSerializer(_User.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
_Serializer.Serialize(_FileStream, _User, ns);
}
|