3

I have a simple poco that need to be mapped to an object supplied by a third party that uses a complex object hierarchy. I would like to use AutoMapper if possible but I am unsure how to set it up correctly.

I have supplied a simplified example below to show what I am trying to do.

My poco:

public class Person
{
    public string FirstName { get; set; }
    public string Lastname { get; set; }
    public string FullName { get { return Firstname + " " + Lastname; } }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string Postcode { get; set; }
    public string Reference { get; set; }
}

Third Party objects

public class People
{
    public Person[] Person { get; set; }
}

public class Person
{
    public Names Names { get; set; }
    public Address Address { get; set; }
    public ReasonsForRequest[] Reasons { get; set; }
}

public class Names
{
    public string Fullname { get; set; }
}

public class Address
{
    public string AddressLine[] { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string Postcode { get; set; }
}

public class ReasonsForRequest
{
    public StructuredReasons StructuredReasons { get; set; }
}

public class StructuredReasons
{
    public Reference Ref { get; set; }
}

public class Reference
{
    public string Ref { get; set; }
}

The issue I am having is Registering the mappings to get the reference mapped. How can I register a mappig to an object that just contains an object when I need to map to the inner object and to a parent object?

LoftyTowers
  • 563
  • 5
  • 24

2 Answers2

5

[For clarity, I'm going to call your Person POCO PersonDTO, since the 3rd party code also has a class called Person.]

There are a couple of ways of doing this. One, which I've used in the past, involves setting up a mapping from PersonDTO to Names, another from PersonDTO to Address, and another from PersonDTO to Reasons. Finally, you add a mapping from PersonDTO to Person. It looks like this (I've left out Reasons, but you get the idea):

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<PersonDTO, Names>()
        .ForMember(d => d.Fullname, o => o.MapFrom(s => s.FullName));
    cfg.CreateMap<PersonDTO, Address>()            
        .ForMember(d => d.AddressLine,
                o => o.MapFrom(s => new[] { s.AddressLine1, s.AddressLine2 }));
    cfg.CreateMap<PersonDTO, Person>()
        .ForMember(d => d.Names, o => o.MapFrom(s => s))
        .ForMember(d => d.Address, o => o.MapFrom(s => s));
});
var mapper = config.CreateMapper();

var myPerson = new PersonDTO() { 
    FirstName = "Bob", 
    LastName = "Gold",
    AddressLine1 = "123 Main Street", 
    AddressLine2 = "Apt. 2"
};

var theirPerson = mapper.Map<Person>(myPerson);

But a recent version of AutoMapper added a ForPath() method which makes all of this simpler by letting you access inner objects. Now the code collapses to this:

var config = new MapperConfiguration(cfg => 
    cfg.CreateMap<PersonDTO, Person>()
        .ForPath(d => d.Names.Fullname, o => o.MapFrom(s => s.FullName))
        .ForPath(d => d.Address.AddressLine, 
                o => o.MapFrom(s => new[]  { s.AddressLine1, s.AddressLine2 }))
);
var mapper = config.CreateMapper();

Edit: I left out one aspect which may change the balance between these two methods. In the first method, with multiple maps defined, you get for free any fields with matching names. For example, you don't need to explicitly map PesronDTO.City to Address.City (and if you change the case on PersonDTO.FullName to be PersonDTO.Fullname, you'd get that for free as well). But in the second method, you have to explicitly map all nested fields, even if the names match. In your case, I think the first method would probably work better for you, because you'd have to map only 3 fields explicitly and would get the other 4 for free. For the second method, you'd have to do a ForPath() for all 7 fields.

asherber
  • 2,508
  • 1
  • 15
  • 12
  • 1
    .ForPath does exactly what I need it too. You are a champion. Just for my own learning, if one of the properties was an array of properties, can AutoMapper still handle this. Mysecond question is if one of the properties on the third party is just company name that never changes, can I tell autoMapper to look for a const string value stored elsewhere or does everything have to be in the source poco? – LoftyTowers Mar 08 '18 at 09:37
  • 1
    For your first question, you can't directly access array members in the destination, but you can create a whole new array and populate it, as I did above with `Address.AddressLine`. You could also use `ResolveUsing()` with a custom resolver if you wanted to get more complicated. For the second question, you can do something like `.ForMember(d => d.MyField, o => o.UseValue("foo"))`. – asherber Mar 08 '18 at 12:55
  • That is really helpful, I will have a play later. Thank you. – LoftyTowers Mar 08 '18 at 13:18
0

By default it tries to match the properties of the SourceType to those of the DestinationType. But for your scenario you'll have add some specifications using the ForMember extension method, like this:

Mapper.CreateMap<Foo, FooDTO>()
.ForMember(e => e.Bars, o => o.ExplicitExpansion());

There's an existing thread on this one here: AutoMapper define mapping level

Ovy.Istrate
  • 474
  • 3
  • 15
  • I might not be understanding the answers given in the other question. When I have added for member it looks like I can not map an object that only has an object inside. So is the .MaxDepth(1); method telling it to look for hierarchies it can find? – LoftyTowers Mar 07 '18 at 13:00