DynamicXml -- 动态读取操作XML (一个从XML到Object的通用实现)
最近的一个项目用到很多不同结构的XML文件. 于是就在网上搜索了一些文章, 结合实际遇到的问题写成自己要的代码.
既然已经获取了这方面的知识,不敢独取, 拿出来共享. 这个也还不是很成熟, 希望大家共同完善, 提出宝贵意见, 共同进步.
目标
基于已经有的XML文件,例如:
<root>
<books>
<book>
<author>John Savacki</author>
<title>E.G.Title</title>
<price>20.50</price>
</book>
<book>
<author>Tom Curly</author>
<title>E.G.Title 2</title>
<price>26.50</price>
</book>
</books>
</root>
并且读取属性值:
dx.books.book[0].author, dx.books.book[2].price
解决方案
自然是选择C# 4.0 中引入的 DynamicObject, 关键是已有的.net框架中不能提供这样的功能.
那么就从这个类中继承生成子类, 同时也需要实现 IEnumerable.
稍微介绍一下:
- DynamicObject 类使您能够定义可以动态对象上执行哪些操作以及如何执行这些操作。
如果只需要设置和获取属性的操作,您可以只覆盖 TrySetMember 和 TryGetMember 方法。
- IEnumerable 公开枚举器,该枚举器支持在非泛型集合上进行简单迭代。
应该大家都知道要实现自定义集合的IEnumerable 就要完成
public IEnumerator GetEnumerator()
也就是说, 为了实现需求, 至少我们需要完成三个函数:
TrySetMember, TryGetMember, GetEnumerator
实现
通过上面的分析, 我们知道这个类看起来应该是这样:
public class DynamicXml : DynamicObject, IEnumerable
{
public DynamicXml(string text)
{
}public override bool TryGetMember(GetMemberBinder binder, out object result)
{
}public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
}public IEnumerator GetEnumerator()
{
}
}
在构造函数里我们希望可以读取XML的内容, 加载所有元素的值.
在TrySetMember中, 我们为设置成员值的操作提供实现, 以便为设置属性值指定动态行为。在本文中, 我们只考虑读取XML内容, 所以不需要重写这个函数.
在TryGetMember中, 我们要为获取成员值的操作提供实现。
在 TryGetIndex中, 我们要为按索引获取值的操作提供实现.
实现的代码如下:
public class DynamicXml : DynamicObject, IEnumerable
{
private readonly List<XElement> _elements;
public DynamicXml(string text)
{
var doc = XDocument.Parse(text);
_elements = new List<XElement> { doc.Root };
}
protected DynamicXml(XElement element)
{
_elements = new List<XElement> { element };
}
protected DynamicXml(IEnumerable<XElement> elements)
{
_elements = new List<XElement>(elements);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
if (binder.Name == "Value")
result = _elements[0].Value;
else if (binder.Name == "Count")
result = _elements.Count;
else
{
var attr = _elements[0].Attribute(
XName.Get(binder.Name));
if (attr != null)
result = attr;
else
{
var items = _elements.Descendants(
XName.Get(binder.Name));
if (items == null || items.Count() == 0)
return false;
result = new DynamicXml(items);
}
}
return true;
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
int ndx = (int)indexes[0];
result = new DynamicXml(_elements[ndx]);
return true;
}
public IEnumerator GetEnumerator()
{
foreach (var element in _elements)
yield return new DynamicXml(element);
}
}
运用
在具体运用中, 有些地方是非常有趣的, 因为我们有了dynamic, 所以你可以用同一个变量去获取不同结构的XML文件. 运用代码如下:
static void Main(string[] args)
{
string xml = @"<root>
<books>
<book>
<author>John Savacki</author>
<title>E.G.Title</title>
<price>20.50</price>
</book>
<book>
<author>Tom Curly</author>
<title>E.G.Title 2</title>
<price>26.50</price>
</book>
</books>
</root>";
dynamic dx = new DynamicXml(xml);
Console.WriteLine("----- Book List -----");
foreach (dynamic b in dx.books.book)
{
Console.WriteLine("author='{0}'", b.author.Value);
print(b);
}
Console.WriteLine("------ Book List End ------");
string xml2 = @"<root>
<products>
<product>
<title>iPhone 4</title>
<price>2222.50</price>
<quantity>10</quantity>
</product>
<product>
<title>Lenovo IdeaPad</title>
<price>5432.50</price>
<quantity>15</quantity>
</product>
</products>
</root>";
dx = new DynamicXml(xml2);
Console.WriteLine("----- Product List -----");
foreach (dynamic b in dx.products.product)
{
Console.WriteLine("quantity='{0}'", b.quantity.Value);
print(b);
}
Console.WriteLine("------ Product List End ------");
Console.Read();
}
static void print(dynamic b)
{
Console.WriteLine("Title='{0}'", b.title.Value);
Console.WriteLine("price='{0}'", b.price.Value);
}
不是什么很深奥的东西, 但是希望可以为有同样问题的朋友们节省一些时间.
如果可以较好的掌握Dynamic, 可以节省一些反射方面的代码. 不过这是另外一个话题了.
本文来自于喜乐的ASP.NET(Alex Song) 转贴请注明出处
参考文章: