Search code examples
c#automapper

How to skip/set to null Properties in AutoMapper


I'm using Automapper for C# to map an xml object received through an API to a C# model. The xml received may or may not have the same attributes compared to other xml data received through the same endpoint. (Example: One set of data would have FirstName, MiddleName, LastName while another set would have FirstName, LastName, and Suffix)

I know the simple solution would be to consolidate the xml and supply the xml attribute even if the value was nothing, but I don't have access to what is creating or sending the data.

Currently, when trying to map this example, if the mapper is trying to map this section and it finds the middle name or the suffix attribute is absent it will completely nullify the whole object rather than just set this field to empty or whitespace.

(Code below is a simplified version of work project code I'm unable to share)

public class Person
{
    public PersonName? PersonName { get; set; }
}

public class PersonName
{
    public string? PersonFirstName { get; set; }
    public string? PersonMiddleName { get; set; }
    public string? PersonLastName { get; set; }
    public string? PersonSuffix { get; set; }
}

CreateMap<XMLData, Person>()
.ForMember(dest => dest.Person, opt => opt.MapFrom(
    (src => new Person {
        PersonName = new PersonName {
        PersonFirstName = src.PersonFirstName.Value,
        PersonMiddleName = src.PersonMiddleName.Value,
        PersonLastName = src.PersonLastName.Value,
            PersonSuffix = src.PersonSuffix.Value,
        }       
    })));

I started with trying to create a Custom Value Resolver and I am still working on that as an approach. It is just seeming to be too complex and I was hoping to see if there is a simpler approach that I may have missed.


Solution

  • You should look for Custom Type Resolver.

    In your mapping profile, it should contain 2 mapping rules:

    1. Map from XMLData to Person.
    2. Map from XMLData to PersonName
    CreateMap<XMLData, Person>()
        .ConvertUsing((src, dest, context) =>
        {
            dest = new Person();
            dest.PersonName = context.Mapper.Map<PersonName>(src);
                                                                                          
            return dest;
        });
                                                                      
    CreateMap<XMLData, PersonName>();
    

    Or suggested by @Lucian which is simpler:

    cfg.CreateMap<XMLData, Person>()
        .ForMember(dest => dest.PersonName, 
            opt => opt.MapFrom(src => src));
    
    CreateMap<XMLData, PersonName>();
    

    Alternatively, based on your existing code, you should work with ForPath. Reference: Reverse Mapping and Unflattening

    CreateMap<XMLData, Person>()
        .ForPath(dest => dest.PersonName.PersonFirstName, 
            opt => opt.MapFrom(src => src.PersonFirstName))
        .ForPath(dest => dest.PersonName.PersonMiddleName, 
            opt => opt.MapFrom(src => src.PersonMiddleName))
        .ForPath(dest => dest.PersonName.PersonLastName, 
            opt => opt.MapFrom(src => src.PersonLastName))
        .ForPath(dest => dest.PersonName.PersonFirstName, 
            opt => opt.MapFrom(src => src.PersonFirstName))
        .ForPath(dest => dest.PersonName.PersonSuffix, 
            opt => opt.MapFrom(src => src.PersonSuffix));