AutoMapper – jak używać

Zakładam, że zalety AutoMappera są znane (Co to jest AutoMapper i dlaczego warto go znać). Skupię się więc na dodatkowych rzeczach, z których zawsze staram się korzystać w projekcie. Wspomnę o kilku ciekawostkach, które nie były od razu takie oczywiste.

Krzystanie z Profili

AutoMapper Configuration/Profile. Jeden mapping jeden profil. Jest to o tyle fajne, że miejsca gdzie konfigurujemy mapping (Profile) są blisko kodu ViewModeli a nie siedzą w globalnym ustawiaczu w innym projekcie (webowym, podczas gdy nasze ViewModele są w bibliotekach). Przykład:

public class UserViewModelProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<User, UserViewModel>()
            .MapProperty(dest => dest.DescriptionText, src => src.UserDescription)
            .Ignore(x => x.Parent);
    }
}

Nazwę profilu bierzemy z klasy docelowej (tej na którą mapujemy). Jeśli więc na dany typ można zmapować z kilku innych typów wszystkie znajdą się w tym profilu.

Konfiguracja składa się tylko z dodawania kolejnych profili:

    public static void RegisterMappings(IKernel kernel)
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile<UserViewModelProfile>();
            cfg.AddProfile<ClassroomViewModelProfile>();
            // (...)      
        });

        // (...)      
    }

Konfiguracja w jednym dedykowanym miejscu

Globalny plik konfiguracyjny jest konkretnie związany z konfiguracja AutoMappera, nie mieszamy tego (np. w Global.asax) z innymi konfiguracjami:

public class AutoMapperConfig
{
    public static void RegisterMappings(IKernel kernel)
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile<UserProfile>();
            cfg.AddProfile<UserViewModelProfile>();
            cfg.AddProfile<ClassroomViewModelProfile>();
        });

        AssertConfigurationInDebug(config);

        var mapper = config.CreateMapper();

        kernel.Bind<IMapper>().ToConstant(mapper);
    }

    [Conditional("DEBUG")]
    private static void AssertConfigurationInDebug(IConfigurationProvider config)
    {
        config.AssertConfigurationIsValid();
    }
}

Korzystaj z AssertConfigurationIsValid()

W poprzednim kodzie jest właśnie to wywołanie. W skrócie: sprawdzane jest czy każda właściwość z klasy docelowej będzie wypełniona. Czyli alboo nazywa się tak samo (lub poprzez jakieś konfiguracje nazewnicze będzie dopasowana), albo explicite jest podane z czego należy zmapować albo jest explicite ignorowane. Zapewnia nam to że nie zapomnimy o niczym, a jeśli tak to zostanie rzucony AutoMapperConfigurationException. W tym przypadku podczas startu aplikacji w trybie DEBUG ([Conditional(„DEBUG”)] poczytaj) będzie to zawsze sprawdzane.

Co się stanie gdy nie będziemy sprawdzać a dokonamy wykonamy takie mapowanie w runtime? Ano nic, zostanie tam null. Jednak ja wolę wiedzieć o takich sytuacjach i gdy są porządane to explicite ignorować te właściwości:


        CreateMap<User, UserViewModel>()
            .MapProperty(dest => dest.DescriptionText, src => src.UserDescription)
            .Ignore(x => x.Parent);

Korzystaj z AssertConfigurationIsValid() w testach jednostkowych

Jeśli kogoś przekonał poprzedni paragraf to idziemy krok dalej. Sprawdzamy to w testach jednostkowych które są często wykonywane (częściej niż start aplikacji). Tworzymy jeden profil per projekt, a globalny plik konfiguracyjny składa Profile z wszystkich projektów. Dobra zasada jest taka, że jeden projekt testowy testuje jeden projekt „zwykły” więc zawsze jeden Unit Test testuje AssertConfigurationIsValid().

Dwie proste Extension methods skracające kodowanie

Skleiłem (pewnie ze Stacka) takie dwie dodatkowe methodki które minimalnym nakładem kodu robią maping customowych właściwości oraz ignorują docelowe właściwości.

AutoMapperExtensions.cs z których to korzystam w przykładach z tego wpisu.

Radzi sobie z nullami

AutoMapper mapuje dane rekursywnie to znaczy, że gdy mapujemy User na UserViewModel oraz Classroom na ClassroomViewModel, a User ma w sobie Classroom to AutoMapper się połapie że Classroom zmapuje na ClassroomViewModel. Lecz jeśli Classroom będzie nullem to zostanie po prostu przepisany null. Była to taka mała niepodzianka bo na początku pisaliśmy kod który zabezpiecza przed takimi przypadkami, co było zbędne.

CreateMap<User, UserViewModel>();
CreateMap<Classroom, ClassroomViewModel>();
// (...)
var user = new User{
    Age = 23,
    Name = "Marek",
    Classroom = null
};

var classroomViewModel = mapper.Map<UserViewModel>(user);
Console.WriteLine(user.Classroom == null); // true
        

Mapuje Enumy

Potrafi mapować int (byte) na Enum gdy nazwa pasuje.

class User
{
    public int Status { get; set; }
}

class UserViewModel
{
    public StatusType Status { get; set; }
}

enum StatusType 
{
    Offline = 0,
    Connecting = 1,
    Offline = 2
}

user.Status = 1;
var userViewModel = mapper.Map<UserViewModel>(user);
Console.WriteLine(user.Status == StatusType.Connecting); // true

Czy tylko ViewModele?

AutoMapper służy do mapowania domeny na DTO czy ViewModele. Można przesadzić gdy użyje się go do przepisywania wartości między modelami domeny które „mają coś podobnego”.

Reklamy
Ten wpis został opublikowany w kategorii Programowanie i oznaczony tagami . Dodaj zakładkę do bezpośredniego odnośnika.