Object to object mapping – automapper

Time:2021-6-28

summary

Automapper is an object to object mapper that maps one object to another.

To solve a seemingly complex problem, this type of code is rather boring,

Official website address:

http://automapper.org/

Official document:

https://docs.automapper.org/en/latest/

introduction

Automapper supports the construction of “custom value parser”, “custom type converter” and “value converter” using static service location

var configuration = new MapperConfiguration(cfg =>
{
    cfg.ConstructServicesUsing(ObjectFactory.GetInstance);

    cfg.CreateMap();
});

Or dynamic service location for instance based containers (including subcontainers / nested containers):

var mapper = new Mapper(configuration, childContainer.GetInstance);

var dest = mapper.Map(new Source { Value = 15 });

You can use the configuration file to define the configuration. Then, by calling the iservicecollection extension method addautomapper at startup, automapper knows in which assemblies these profiles are defined:

services.AddAutoMapper(profileAssembly1, profileAssembly2 /*, ...*/);

Or tag type:

services.AddAutoMapper(typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2) /*, ...*/);

Now you can inject automapper into the service / controller at run time:

public class EmployeesController {
  private readonly IMapper _mapper;

  public EmployeesController(IMapper mapper) => _mapper = mapper;

  // use _mapper.Map or _mapper.ProjectTo
}

Of course, there are a lot of scalability, such as:

Custom type converter

Sometimes you need full control over the conversion from one type to another. Usually, this is when one type looks different from another, there are conversion functions, and you want to change from a “loose” type to a stronger type, such as the source type of a string to the target type of int32.

For example, suppose our source type is:

public class Source
{
  public string Value1 { get; set; }
  public string Value2 { get; set; }
  public string Value3 { get; set; }
}

But you want to map it to:

public class Destination
{
  public int Value1 { get; set; }
  public DateTime Value2 { get; set; }
  public Type Value3 { get; set; }
}

If we try to map the two types as is, automapper will throw an exception (during mapping and configuration checking) because automapper does not know any mapping from string to int, datetime or type. To create mappings for these types, we must provide a custom type converter, and we can do it in three ways:

void ConvertUsing(Func mappingFunction);
void ConvertUsing(ITypeConverter converter);
void ConvertUsing() where TTypeConverter : ITypeConverter;

The first option is any function with a source and a destination (there are also multiple overloads). This works for simple situations, but it’s clumsy for larger ones. In more difficult cases, we can create a custom itypeconverter:

public interface ITypeConverter
{
  TDestination Convert(TSource source, TDestination destination, ResolutionContext context);
}

And provide an instance of a custom type converter to automapper, or provide the type that automapper will instantiate at run time for the type. The mapping configuration of the source / target type above will change to:

public void Example()
{
    var configuration = new MapperConfiguration(cfg => {
      cfg.CreateMap().ConvertUsing(s => Convert.ToInt32(s));
      cfg.CreateMap().ConvertUsing(new DateTimeTypeConverter());
      cfg.CreateMap().ConvertUsing();
      cfg.CreateMap();
    });
    configuration.AssertConfigurationIsValid();

    var source = new Source
    {
        Value1 = "5",
        Value2 = "01/01/2000",
        Value3 = "AutoMapperSamples.GlobalTypeConverters.GlobalTypeConverters+Destination"
    };

    Destination result = mapper.Map(source);
    result.Value3.ShouldEqual(typeof(Destination));
}

public class DateTimeTypeConverter : ITypeConverter
{
    public DateTime Convert(string source, DateTime destination, ResolutionContext context)
    {
        return System.Convert.ToDateTime(source);
    }
}

public class TypeTypeConverter : ITypeConverter
{
    public Type Convert(string source, Type destination, ResolutionContext context)
    {
          return Assembly.GetExecutingAssembly().GetType(source);
    }
}

In the first mapping, from string to int32, we only use the built-in convert. Toint32 function (provided as a method group). The next two are implemented using custom itypeconverter.

The real power of custom type converters is that they can be used as long as automapper finds source / target pairs on any mapping type. We can build a set of custom type converters and use other mapping configurations on them without any other configuration. In the above example, we don’t have to specify the string / int conversion again. Since the custom value resolver must be configured at the type member level, the scope of the custom type converter is global.

Of course, there are many functions that need to be implemented in the actual project.