Blog removed - here is the archive of Bing posts from @DannyDouglass
Simplify the use of Xml data with AutoMapper and Linq-to-Xml
Recently, I came across a script at work that required manually consuming several SOAP web services, which, I am sure you can imagine, were pretty monotonous. An employee (Seth Carney) and I tried several different approaches, but we finally settled on a solution that simplified the consumption of xml and, ultimately, made the code more suitable for testing. This solution focused on using AutoMapper, an open-source object-to-object mapping tool, to create a link between XElements ( http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.aspx ) returned in SOAP messages and user contracts we have created - reusable.
I put together a short demo that shows how you can use the same approach to consume and display the public Twitter timeline ( http://api.twitter.com/1/statuses/public_timeline.xml ) (using the Xml API response type )
Note. The source code for the following example can be found on my GitHub page: https://github.com/DannyDouglass/AutoMapperXmlMappingDemo
After creating the MVC3 base project (beta download) and the associated test project, the first step was to install the AutoMapper package. I use NuGet, the recently announced Microsoft package management system, to install any open source dependencies. The following command was given to configure AutoMapper in my MVC3 project (more about NuGet here ( http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3- beta-and-webmatrix-beta-2.aspx ) and here ( http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and -webmatrix-beta-2.aspx )):
PM> add-package AutoMapper
- Create Mapping
With AutoMapper installed, Im ready to start creating the components needed for xml-to-object mapping. The first step is to create a quick contract used in my application to represent the Tweet object:
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; } }
Nothing crazy here is just a simple entity. These are all fields that are specified in the Twitter API response using a different name for some fields. In simple cases, when the source and target objects have the same name, you can quickly install the map using this syntax:
Mapper.CreateMap<SourceObj, DestinationObj>();
However AutoMapper does not support Xml by default, I have to specify the fields that I will display. Using the Fluent API in AutoMapper Im is able to bind my field mappings. Take a look at one example field displayed in my example - Body tweets:
Mapper.CreateMap<XElement, ITweetContract>() .ForMember( dest => dest.Body, options => options.ResolveUsing<XElementResolver<string>>() .FromMember(source => source.Element("text")))
It might look complicated at first, but all that actually happens here is that we provide AutoMapper details about what value to use in my source object and how to map it to the property of the destination objects. There is one specific line that I would like to dwell on in the above field to display the body:
options => options.ResolveUsing<XElementResolver<ulong>>() .FromMember(source => source.Element("id")))
XElementResolver is a custom value converter ( http://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers ) that Seth came up with to parse the original XmlElement object to get a strongly typed value to use in the display. I will talk more about this in more detail, but before moving on, take a look at my full display:
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")));
- Generic XElementResolver
This custom value converter is the real key that allowed these XElement-to-Contract cards to work in the original solution. Ive reused this resolver in this example, as we saw above. This was all that was needed to create a custom recognizer class:
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)); } }
This universal XElementResolver makes it easy to pass the type of value obtained in our mapping above. For example, the following syntax is used to strongly enter a value obtained from an XmlElement into Id fields. The forMember () declaration above:
ResolveUsing<XElementResolver<ulong>>()
When my display is fully configured and instantiated, I am ready to call the Twitter API and use AutoMapper to display the latest public history.
- Putting parts together
I created a simple class responsible for receiving a Twitter API response:
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(); } }
The GetPublicTimeline method is a simple method that returns, you guessed it, a public Twitter timeline using the map we created earlier:
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
In my MVC3 HomeController sites, I can quickly call the extraction method by requesting the last 10 results:
public class HomeController : Controller { private TwitterTimelineRetriever _twitterTimelineRetriever; public ActionResult Index() { _twitterTimelineRetriever = new TwitterTimelineRetriever(); ViewModel.Message = "Twitter Public Timeline"; return View(_twitterTimelineRetriever.GetPublicTimeline(10)); } }
And finally, after a little formatting in my view using the new Microsoft Razor viewer, I have my own timeline!