arettatonyan
arettatonyan

Reputation: 31

Two way mapping with automapper using same objects and different TypeConverters

I want to use AutoMapper for mapping DTO s to Models. But my project contains some custom rules for default values for primitive types like [string null value is "_"], [default int value is -1] and [date time fields must be integer like 19990221] etc.

In my test i have a outer DTO which haves Null string values. But my inner system model has a "null string must be represents as a "_" string" rule (its sucks but its a legacy design decision).

Models:

public class OuterDTO
{
    public string TestString { get; set; }
}

public class InnerModel
{
    public string TestString { get; set; }
}

Automapper type converters:

public class CustomStringToStringConverter : ITypeConverter<string, string>
{
    public string Convert(ResolutionContext context)
    {
        string source = (string)context.SourceValue;

        if (source == "_")
            return null;

        return source;
    }
}

public class StringToSafirStringConverter : ITypeConverter<string, string>
{
    public string Convert(ResolutionContext context)
    {
        string source = (string)context.SourceValue;

        if (source == null)
            return "_";

        return source;
    }
}

And my Test is :

[TestMethod]
    public void StringToSafirStringConverterAndSafirStringToStringConverter_SafirDefaultNullStringAndNullString_ReturnsNullAndSafirNullString()
    {
        //Arrenge
        Mapper.CreateMap<string, string>().ConvertUsing(new StringToSafirStringConverter());//this has to used be DTO To Model map
        Mapper.CreateMap<string, string>().ConvertUsing(new SafirStringToStringConverter());//this has to used be Model To DTO map
        Mapper.CreateMap<InnerModel, OuterDTO>();
        Mapper.CreateMap<OuterDTO, InnerModel>();
        Mapper.AssertConfigurationIsValid();

        InnerModel expected1 = new InnerModel()
        {
            TestString = null
        };

        OuterDTO inner1 = new OuterDTO()
        {
            TestString = "_"
        };

        OuterDTO expected2 = new OuterDTO()
        {
            TestString = "_"
        };

        InnerModel outer1 = new InnerModel()
        {
            TestString = null
        };

        //Act
        InnerModel actual1 = Mapper.Map<OuterDTO, InnerModel>(inner1);
        OuterDTO actual2 = Mapper.Map<InnerModel, OuterDTO>(outer1);

        //Assert
        Assert.AreEqual(expected1.TestString, actual1.TestString);
        Assert.AreEqual(expected2.TestString, actual2.TestString);
    }

Second assert fails.

I have to map DTO s to Models and Models to DTO s hundreds of cases and I have same issue in integer too. How can i choose converters to do different mappings?

Upvotes: 3

Views: 2251

Answers (1)

stuartd
stuartd

Reputation: 73243

You're declaring two mappings from string to string:

Mapper.CreateMap<string, string>().ConvertUsing(new StringToSafirStringConverter());
Mapper.CreateMap<string, string>().ConvertUsing(new SafirStringToStringConverter());

Mappings cannot be overwritten, so if you look at the AutoMapper configuration you'll see just one mapping for string to string. (Even if you could define two mappings for string to string, AutoMapper could not know which one to use.)

The way to do this is to define the mapping for the property using ValueResolvers:

Mapper.CreateMap<InnerModel, OuterDTO>()
                .ForMember(dest => dest.TestString,
                    opt => opt.ResolveUsing<StringToSafirStringResolver>()
                       .FromMember(source => source.TestString));

Mapper.CreateMap<OuterDTO, InnerModel>()
                .ForMember(dest => dest.TestString,
                    opt => opt.ResolveUsing<SafirStringToStringResolver>()
                      .FromMember(source => source.TestString));

Then you write the ValueResolvers, say:

public class StringToSafirStringResolver : ValueResolver<string, string>
{
    protected override string ResolveCore(string source)
    {
        return source ?? "_";
    }
}

public class SafirStringToStringResolver : ValueResolver<string, string>
{
    protected override string ResolveCore(string source)
    {
        return source == "_" ? null : source;
    }
}

You can then do the same thing for your int and datetime values.

Upvotes: 2

Related Questions