博客已被删除 - 这是 @DannyDouglass 帖子的 Bing 存档
通过 AutoMapper 和 Linq-to-Xml 简化 Xml 数据的使用
我最近在工作中遇到了一个需要手动使用多个 SOAP Web 服务的场景,我相信您可以想象这是相当单调的。我和一位同事 (Seth Carney) 尝试了几种不同的方法,但我们最终确定了一种简化 xml 使用并最终使代码更具可测试性的解决方案。该解决方案的中心是利用 AutoMapper(一种开源对象-对象映射工具)以可重用的方式在 SOAP 消息中返回的 XElements(http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.aspx) 和我们创建的自定义合同之间创建链接。
我整理了一个快速演示,展示了如何使用相同的方法来使用和显示 Twitter 公共时间线 (http://api.twitter.com/1/statuses/public_timeline.xml)(使用 API 的 Xml 响应类型)。
注意:以下示例的源代码可以在我的 GitHub 页面上找到:https://github.com/DannyDouglass/AutoMapperXmlMappingDemo
- 获取项目设置
创建一个基本的 MVC3(下载 beta)项目和相关的测试项目后,第一步是安装 AutoMapper 包。我一直在使用微软最近发布的包管理系统 NuGet 来安装任何开源依赖项。以下命令是在我的 MVC3 项目中设置 AutoMapper 所需的全部内容(在此处(http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx)和此处(http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx)阅读有关 NuGet 的更多信息):
PM> add-package AutoMapper
- 创建映射
安装 AutoMapper 后,我已准备好开始创建 xml 到对象映射所需的组件。第一步是创建一个在我的应用程序中使用的快速合约来表示 Tweet 对象:
public interface ITweetContract
{
ulong Id { get; set; }
string Name { get; set; }
string UserName { get; set; }
string Body { get; set; }
string ProfileImageUrl { get; set; }
string Created { get; set; }
}
这里没有什么疯狂的——只是一个简单的实体。这些都是在来自 Twitter API 的响应中提供的所有字段,某些字段使用不同的名称。在源对象和目标对象具有相同名称的简单情况下,您可以使用以下语法快速设置地图:
Mapper.CreateMap<SourceObj, DestinationObj>();
但是,AutoMapper 默认不支持 Xml,我必须指定要映射的字段。使用 AutoMapper 中的 Fluent API,我可以链接我的字段映射。看看我的示例中映射的一个示例字段——推文的正文:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
一开始可能看起来很复杂,但这里真正发生的事情是我们正在向 AutoMapper 提供有关在我的源对象中使用什么值以及如何将其映射到目标对象的属性的详细信息。在上面的 Body 字段映射中,我想重点关注一个特定的行:
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
XElementResolver 是一个自定义值解析器 (http://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers),Seth 想出它来处理解析 XmlElement 源对象以检索强类型值以用于映射。稍后我会详细介绍,但在我们继续之前先看看我的完整地图:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Id,
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
.ForMember(
dest => dest.Name,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("name").Single()))
.ForMember(
dest => dest.UserName,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("screen_name").Single()))
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
.ForMember(
dest => dest.ProfileImageUrl,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("profile_image_url").Single()))
.ForMember(
dest => dest.Created,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("created_at")));
- 通用 XElementResolver
这个自定义值解析器是允许这些 XElement-to-Contract 映射在原始解决方案中工作的真正关键。正如我们在上面看到的,我在这个例子中重用了这个解析器。这就是创建自定义解析器类所需的全部内容:
public class XElementResolver<T> : ValueResolver<XElement, T>
{
protected override T ResolveCore(XElement source)
{
if (source == null || string.IsNullOrEmpty(source.Value))
return default(T);
return (T)Convert.ChangeType(source.Value, typeof(T));
}
}
这个通用的 XElementResolver 允许使用轻松传递在我们上面的映射中检索到的值的类型。例如,以下语法用于在上面的 Id 字段的 .ForMember() 声明中强类型化从 XmlElement 检索到的值:
ResolveUsing<XElementResolver<ulong>>()
在我的映射完全配置和实例化后,我已准备好调用 Twitter API 并利用 AutoMapper 显示最新的公共时间线。
- 拼凑起来
我创建了一个负责检索 Twitter API 响应的简单类:
public class TwitterTimelineRetriever
{
private readonly XDocument _twitterTimelineXml;
public TwitterTimelineRetriever()
{
_twitterTimelineXml = XDocument
.Load("http://api.twitter.com/1/statuses/public_timeline.xml");
}
public IEnumerable<ITweetContract> GetPublicTimeline(int numberOfTweets)
{
var tweets = _twitterTimelineXml.Descendants("status")
.Take(numberOfTweets);
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
}
}
GetPublicTimeline 方法是一个简单的方法,它利用我们之前创建的地图返回 Twitter 公共时间线:
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
在我的 MVC3 站点的 HomeController 中,我可以快速调用检索方法,请求最后 10 个结果:
public class HomeController : Controller
{
private TwitterTimelineRetriever _twitterTimelineRetriever;
public ActionResult Index()
{
_twitterTimelineRetriever = new TwitterTimelineRetriever();
ViewModel.Message = "Twitter Public Timeline";
return View(_twitterTimelineRetriever.GetPublicTimeline(10));
}
}
最后,在使用 Microsoft 的新 Razor 视图引擎对我的视图进行了一些格式化之后,我的公共时间线显示出来了!