Click here to Skip to main content
Click here to Skip to main content

Using AutoMapper with Complex XML Data

, 6 Jan 2014
Rate this:
Please Sign up or sign in to vote.
Need to use automapper for non-trivial XML data? Here's how!

Introduction

AutoMapper is a very cool object-to-object mapping library that works great mapping POCO-to-POCO and even handles collections pretty seamlessly. Unfortunately, in the real world, sometimes the data we need comes to us in other formats. This article explores mapping XML data using AutoMapper.

Background

If you're like me, you found this article because you went searching for how to get AutoMapper to work with XML. If you did, you probably ran across the article by Danny Douglass on his blog. While this was extremely helpful, it only gets us part of the way there, as the source and target objects in this scenario are fairly simple. How would you handle more complicated XML data? Perhaps with nested types, or nested collections of types? This article picks up where that one left off and shows how to quickly and easily build a mapping for more complicated XML data.

At this point, some may be asking, "If it's XML data, why wouldn't you use XML serialization to solve the problem?" The answer, obviously, is that you could. However, in some circumstances, using object mapping may be easier, or you may have other constraints and considerations why you might want to use object mapping. Plus, it was an interesting problem to solve.

Using the Code

For our sample program here, we're going to consume an XML file which defines a nested menu structure. This menu supports menu items, and submenus, where each submenu in turn supports menu items and submenus. The file we're going to process looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<Menu>
  <SubMenus>
    <Menu name="SubMenu 1">
      <SubMenus>
        <Menu name="Sub-SubMenu 1">
          <MenuItems>
            <MenuItem name="Menu Item 1-1-1" action="Action1.exe" />
            <MenuItem name="Menu Item 1-1-2" action="Action2.exe" parameters="-2" />
          </MenuItems>
        </Menu>
        <Menu name="Sub-SubMenu 2">
          <MenuItems>
            <MenuItem name="Menu Item 1-2-1" action="Action3.exe" />
            <MenuItem name="Menu Item 1-2-2" action="Action4.exe" parameters="-2" />
          </MenuItems>
        </Menu>
      </SubMenus>
    </Menu>
    <Menu name="Sub Menu 2">
      <MenuItems>
        <MenuItem name="Menu Item 2-1" action="Action5.exe" parameters="-item 2.1" />
      </MenuItems>
    </Menu>
  </SubMenus>
  <MenuItems>
    <MenuItem name="Menu Item 1" action="Action6.exe"/>
    <MenuItem name="Menu Item 2" action="Action7.exe" parameters="-7" />
  </MenuItems>
</Menu>

The classes for the types we need are pretty straight forward from a modeling standpoint.  

public class Menu
{
    public Menu()
    {
        SubMenus = new List<Menu>();
        MenuItems = new List<MenuItem>();
    }
    public IList<Menu> SubMenus { get; set; }
    public IList<MenuItem> MenuItems { get; set; }
    public string Name { get; set; }
}

public class MenuItem
{
    public string Name { get; set; }
    public string Action { get; set; }
    public string Parameters { get; set; }
}

At this point, AutoMapper needs to be told how to map the XML to these objects. At first, this might seem like it would be a lot of coding, but it's surprisingly small. Well, at least it was to me. YMMV. In the post from Danny Douglass, he shows a custom XElement resolver which he uses in his example. That's almost what's needed here, but since the sample XML has data as attributes instead of elements, a custom XAttributeResolver class is needed instead. As you can see in the snippet below, the name of the attribute is included in the constructor so the resolver knows which attribute to resolve.

public class XAttributeResolver<T> : ValueResolver<XElement, T>
{
    public XAttributeResolver(string attributeName)
    {
        Name = attributeName;
    }

    public string Name { get; set; }

    protected override T ResolveCore(XElement source)
    {
        if (source == null)
            return default(T);
        var attribute = source.Attribute(Name);
        if (attribute == null || String.IsNullOrEmpty(attribute.Value))
            return default(T);

        return (T)Convert.ChangeType(attribute.Value, typeof(T));
    }
}

With the custom resolver complete, the maps can be built. We'll create a static MapInitializer class with a CreateMenuMap method to contain the map construction logic. For the MenuItem class, the mapping is relatively straight forward - simply map the relevant attributes to the property names. The attribute name is passed to the XAttributeResolver using the ConstructedBy() member of the Mapper class.

Mapper.CreateMap<XElement, MenuItem>()
      .ForMember(dest => dest.Name,
                 opt => opt.ResolveUsing<XAttributeResolver<string>>()
                           .ConstructedBy(() => new XAttributeResolver<string>("name")))
      .ForMember(dest => dest.Action,
                 opt => opt.ResolveUsing<XAttributeResolver<string>>()
                           .ConstructedBy(() => new XAttributeResolver<string>("action")))
      .ForMember(dest => dest.Parameters,
                 opt => opt.ResolveUsing<XAttributeResolver<string>>()
                           .ConstructedBy(() => new XAttributeResolver<string>("parameters")));

The Menu class is a bit trickier, as in addition to the Name property, there are nested Menu and MenuItem collections to deal with. Fortunately, given the structure of the XML data, it is possible to deal with both in a single delegate method. This method looks through descendants of the current node for a collection node, which contains the sub-element nodes. The delegate, then (with a little bit of error- prevention code), essentially flattens the source data and allows AutoMapper to walk the collection of XElement like it would any other collection.

private static Func<XElement, string, 
    string, List<XElement>>

Armed now with this delegate, the Menu map is fairly straight forward to create using the MapFrom method.

Mapper.CreateMap<XElement, Menu>()
      .ForMember(dest => dest.Name,
                 opt => opt.ResolveUsing<XAttributeResolver<string>>

Putting it all together, the MapInitializer class looks like this:

public static class MapInitializer
{
    private static Func<XElement, string, string, List<XElement>> _mapItems =
        (src, collectionName, elementName) =>
        (src.Element(collectionName) ?? new XElement(collectionName)).Elements(elementName).ToList();

    public static void CreateMenuMap()
    {
        //MenuItem map
        Mapper.CreateMap<XElement, MenuItem>()
                .ForMember(dest => dest.Name,
                           opt => opt.ResolveUsing<XAttributeResolver<string>>()
                                     .ConstructedBy(() => new XAttributeResolver<string>("name")))
                .ForMember(dest => dest.Action,
                           opt => opt.ResolveUsing<XAttributeResolver<string>>()
                                     .ConstructedBy(() => new XAttributeResolver<string>("action")))
                .ForMember(dest => dest.Parameters,
                           opt => opt.ResolveUsing<XAttributeResolver<string>>()
                                     .ConstructedBy(() => new XAttributeResolver<string>("parameters")));
        // Menu map
        Mapper.CreateMap<XElement, Menu>()
                .ForMember(dest => dest.Name,
                            opt =>
                            opt.ResolveUsing<XAttributeResolver<string>>()
                                  .ConstructedBy(() => new XAttributeResolver<string>("name")))
                .ForMember(dest => dest.MenuItems,
                            opt => opt.MapFrom(src => _mapItems(src, "MenuItems", "MenuItem")))
                .ForMember(dest => dest.SubMenus,
                            opt => opt.MapFrom(src => _mapItems(src, "SubMenus", "Menu")));
    }
}

At this point, everything we need to parse the XML should be ready to go. In my first pass, I did the XDocument.Load() and passed the results to Mapper.Map(). Pretty simple, right?

MapInitializer.CreateMenuMap();
var xml = XDocument.Load(@".\Menu.xml");
var menu = Mapper.Map<XDocument, Menu>(xml);
    

Only one problem - the Menu resolver needs an XElement, not an XDocument. Doh! A quick tweak of the code:

var menu = Mapper.Map<XElement,>(xml.Element("Menu"));

and some magic handwaving, the voila! - AutoMapper parses the XML and we can display an output of the menu.

> SubMenu 1
  > Sub-SubMenu 1
      Menu Item 1-1-1
      Menu Item 1-1-2
  > Sub-SubMenu 2
      Menu Item 1-2-1
      Menu Item 1-2-2
> Sub Menu 2
    Menu Item 2-1
  Menu Item 1
  Menu Item 2

Conclusion

Armed with the basics outlined in the article here, you should now have the tools you need to tackle almost any complex XML documents with AutoMapper.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Scott Toberman
Technical Lead
United States United States
No Biography provided
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy vote of 3 PinmemberSergioRykov15-Jan-14 2:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 7 Jan 2014
Article Copyright 2014 by Scott Toberman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid