Keri sisuni

AutoMapper

Data ja Models kataloogi klasside vahel andmete edasi-tagasi liigutamine pole keerukas, kuid see-eest on tegemist küllaltki tüütu ja monotoonse programmeerimistööga. Seda annab olulisel määral lihtsustada AutoMapperi nimelise komponendi abil, mille ülesandeks objektide vahel andmete liigutamine.

AutoMapperi lisamine veebirakendusse

ASP.NET Core veebirakendusse lisatakse AutoMapper NuGeti paketi abil. Kõige lihtsam on kasutada sellist paketti nagu AutoMapper.Extensions.Microsoft.DependencyInjection. See pakett võtab kaasa ka AutoMapperi põhipaketi ja midagi täiendavat tegema ei pea.

Veebirakenduse Startup-klassi ConfigureServices() meetodis peame AutoMapperi rakendusele külge võtma.

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddAutoMapper(typeof(Program).Assembly);
    // ...
}

Kaardistusprofiilide lisamine

AutoMapper ei hakka objektide vahel omadusi kopeerima iseenesest. Me peame ütlema ette tüübid, mille vahel me soovime andmeid liigutada. Selleks kasutatakse kaardistusprofiile (mapping profile). Visual Studio lahenduses lisame need veebirakenduse kausta MappingProfiles.

Vaatame näitena kaardistusprofiili, mis defineerib kaardistused Car ja CarEditModel vahel.

using AutoMapper;

namespace KooliProjekt
{
    public class CarProfile : Profile
    {
        public CarProfile()
        {
            // Kopeerimine Car => CarListModel
            CreateMap<Car, CarListModel>();

            // Kopeerimine Car => CarEditModel
            CreateMap<Car, CarEditModel>();

            // Kopeerimine CarEditModel => Car, kuid ignoreeri
            // omadusi Id ja Manufacturer
            CreateMap<CarEditModel, Car>()
                .ForMember(c => c.Id, m => m.Ignore())
                .ForMember(c => c.Manufacturer, m => m.Ignore());
        }
    }
}

Omadust Id ignoreerime me seepärast, et EF Core jälgib talle tuntud objektide omaduste muutumist ning Id muutumisega võime sattuda probleemide otsa.

Omadus Manufacturer on teise loomuga probleem. Kui CarEditModel klassil on omadus nimega ManufacturerName ning klassil Car on omadus Manufacturer, millel on omakorda omadus Name, siis AutoMapper saab sellest aru. Kui me ei keela, siis kirjutataks Car.Manufacturer.Name ka üle.

AutoMapperi kasutamine teenusklassides

AutoMapperit kasutame klassides, kus me teisendame Data ja Models kataloogide klasside vahel. Meie puhul toimub see teenuste klassides. Rakenduse käivitumisel sätib AutoMapper meie jaoks kõik vajaliku valmis. Ühtlasi lisab see DI-sse IMapper liidese, mida saame kasutada klassides, kus AutoMapperit vajame.

Näitena vaatame kuidas CarService klassi List() meetodis tagastada autode loend. Alustame klassidest, mille vahel kaardistame. Car ja Manufacturer on Data kausta klassid ning CarListModel on Models kausta klass.

public class Manufacturer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Car
{
    public int Id { get; set; }
    public string RegNo { get; set; }
    public string Model { get; set; }
    // ...

    public Manufacturer Manufacturer { get; set; }
}

public class CarListModel
{
    public int Id { get; set; }
    public string RegNo { get; set; }
    public string Model { get; set; }
    public string ManufacturerName { get; set; }
}

Kuna CarService teenuse List() meetod tagastab meile PagedResult<Car>, mille see saab Repository käest, siis peame AutoMapperile ütlema ette reegli PagedResult kohta.

public class CarProfile : Profile
{
    public CarProfile()
    {
        // Kopeerimine Car => CarListModel
        CreateMap<Car, CarListModel>();
        CreateMap<Pagedresult<Car>, PagedResult<CarListModel>>();

        // Kopeerimine Car => CarEditModel
        CreateMap<Car, CarEditModel>();

        // Kopeerimine CarEditModel => Car, kuid ignoreeri
        // omadusi Id ja Manufacturer
        CreateMap<CarEditModel, Car>()
            .ForMember(c => c.Id, m => m.Ignore())
            .ForMember(c => c.Manufacturer, m => m.Ignore());
    }
}

Nüüd saame List() meetodi viia Automapperi peale.

public class CarService : ICarService
{
    // ...
    private readonly IMapper _objectMapper;

    public CarService(
            // ...
            IMapper objectMapper
        )
    {
        // ...
        _objectMapper = objectMapper;
    }

    public async Task<PagedResult<CarListModel>> List(int page, int pageSize)
    {
        var results = await _carRepository.List(page, pageSize);

        return _objectMapper.Map<PagedResult<CarListModel>>(results);
    }

    // ...
}

Sarnaselt saame kasutada AutoMapperit andmete salvestamisel, kuid siis ei lase meil tal ise objekti luua, vaid anname ette mõlemad objektid - nii mudeli, millelt andmeid kopeeritakse kui ka objekti, millele andmeid kopeeritakse.

public class CarService : ICarService
{
    // ...
    private readonly IMapper _objectMapper;

    public CarService(
            // ...
            IMapper objectMapper
        )
    {
        // ...
        _objectMapper = objectMapper;
    }

    // ...

    public async Task Save(CarEditModel model)
    {
        var car = new Car();

        if(model.Id != 0)
        {
            car = await _carRepository.Get(model.Id);
        }

        _objectMapper.Map(model, car);

        car.Manufacturer = await _manufacturerRepository.Get(model.ManufacturerId);

        await _unitOfWork.Save(car);
        await _unitOfWork.Commit();
    }

    // ...
}