Статьи по c++

AutoMapper с ASP.NET CORE или отображение классов

В этой статье мы рассмотрим, как интегрировать AutoMapper в ASP.NET CORE для .NET 5, хотя, будем честны, вы можете использовать эту библиотеку в любом проекте на C#.

Что такое AutoMapper и какие проблемы он решает?

Что такое AutoMapper?

AutoMapper помогает копировать данные из одного объекта в другой. Честно говоря, в моей карьере программиста был момент, когда я не знал о существовании такой библиотеки.

Поэтому я создал статический класс, который имел общий метод для присвоения определенных значений, повторяющийся во всех классах, наследующих от StatusInfo

public class TransformToResponse<T> where T : StatuInfo, new() { public static T AddStatusOutput(ResponseDataDetail result) { var transformed = new T(); transformed.StatusOutput = new StatusOutput() { HostName = result.StatusOutput.HostName, LoginId = result.StatusOutput.LoginId, MessagesOutput = new List<MessageOutput>(), }; foreach (var message in result.StatusOutput.MessagesOutput) { var newmessage = new MessageOutput(); newmessage.MessageCode = message.MessageCode; newmessage.Text = message.Text; newmessage.AdditionalInfo = message.AdditionalInfo; transformed.StatusOutput.MessagesOutput.Add(newmessage); } return transformed; }
Code language: HTML, XML (xml)

Однажды я также создал метод, который просто присваивал значения от одного объекта к другому.

private User TranslateUser(UserViewModel userView) { return new User() { FirstName = userView.FirstName, LastName = userView.LastName, Login = userView.Login, Role = userView.Role, UserId = userView.UserId }; }
Code language: PHP (php)

Я написал метод, который, используя отражение, копирует свойства одного объекта в другой объект, если типы и имена совпадают.

public static T2 ConvertToTheSameProperties<T1,T2>(T1 employee) where T2 : new() { var prop = employee.GetType().GetProperties(); var prop2 = typeof(T2).GetProperties(); T2 u = new T2(); foreach (var propertyInfo in prop) { bool propValue = propertyInfo.PropertyType == typeof (string) || propertyInfo.PropertyType == typeof (int) || propertyInfo.PropertyType == typeof (long) || propertyInfo.PropertyType == typeof (Guid) || propertyInfo.PropertyType == typeof(bool) || propertyInfo.PropertyType == typeof(bool?); foreach (var info in prop2) { if (propertyInfo.Name == info.Name && info.CanWrite && propertyInfo.CanRead) { if (propValue && propertyInfo.PropertyType == info.PropertyType) info.SetValue(u, propertyInfo.GetValue(employee)); } } } return u; }
Code language: JavaScript (javascript)

Он также работает только для типов: bool, int, long, Guid, string.

Эти решения выглядят прекрасно, пока вы не поймете, что для решения этой проблемы существует библиотека, которая существует уже много лет.

В вашем приложении будут возникать ситуации, когда сущности, которые вы используете для связи с базой данных, и сущности, которые вы отправляете клиенту, будут пересекаться, хотя и не будут одинаковыми.

Вы, конечно, можете спросить себя, почему все это так разорвано. Лучшая практика гласит, что у вас должен быть один класс для всего во всех слоях.

Это делается для того, чтобы обеспечить гибкость при изменении кода, чтобы изменение определения класса, который вы автоматически извлекаете из базы данных, не повлияло на то, что вы видите на сайте. Кто знает, где этот класс будет также выполнять сериализацию или десериализацию в XML или JSON.

Однако существует проблема. В какой-то момент мы должны сопоставить эти объекты между собой.

Представьте, что у нас есть два таких класса.

Employee

public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

и EmployeeDto, который является объектом, используемым для связи с базой данных

public class EmployeeDto { public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

Теперь, когда у вас есть эти два объекта, было бы полезно иметь метод, который сопоставляет значения одного класса с другим. В зависимости от количества этих параметров написание такого метода может быть раздражающим.

private static Employee Map(EmployeeDto em) { return new Employee() { //insert code here }; }
Code language: JavaScript (javascript)

Что если у вас 200 таких классов. 200 классов означают, что вам придется написать для них 100 мапперов.

Давайте посмотрим, как automapper может помочь нам во всем этом.

Как работает AutoMapper?

Как вы уже догадались, он использует отражение. Ранее вы видели мой тривиальный метод присвоения одного свойства другому.

AutoMapper может делать все это также, но лучше.

Давайте создадим проект ASP.NET CORE

Для того чтобы это имело хоть какое-то значение, необходимо создать проект ASP.NET CORE Web API в .NET 5.

Прежде чем мы начнем что-либо делать, хорошо бы добавить пакет NuGet.

Найдите AutoMapper.Extensions.Microsoft.DependencyInjections. Этот пакет предоставит нам необходимую логику инъекции зависимостей для ASP.NET CORE.

Вы также можете сделать все это с помощью “Консоли менеджера пакетов”. Просто введите эту команду

Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
Code language: CSS (css)

В классе “startup.cs” добавьте следующий код в метод “ConfigureServices”.

public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); //... }
Code language: JavaScript (javascript)

Теперь AutoMapper будет сканировать эту библиотеку и проект и искать класс, который наследуется от “Profile”.

Код “AppDomain.CurrentDomain.GetAssemblies()” выдает массив всех библиотек, которые есть в проекте.

Пришло время указать AutoMapper, как отобразить классы. Для создания такого класса нам необходимо наследоваться от Profile.

public class AutoMapperProfile : Profile { public AutoMapperProfile() { CreateMap<EmployeeDto, Employee>(); } }
Code language: HTML, XML (xml)

Итак, мы отобразили объекты, что нам нужно сделать дальше?

Я создал контроллер и добавил в него имитацию загрузки данных в объект DTO.

IMaper, который будет инжектирован AutoMapper, позаботится о сопоставлении.

[ApiController] [Route("[controller]")] public class EmployeeController : Controller { private readonly IMapper _mapper; public Employee Index() { EmployeeDto eDTO = new EmployeeDto() { EmployeeId = 121, FirstName = "Иван", LastName = "Иванов", Login = "ivan", OrganizationCompanyName = "Рога и копыта", OrganizationId = 34, Role = "Средство для мытья полов 34", RootOrganizationCompanyName = "Ивановы и ко", RootOrganizationId = 12, Type = "Очиститель" }; return _mapper.Map<Employee>(eDTO); } }
Code language: HTML, XML (xml)

Поскольку мы говорим об инъекции, конечно, он должен использовать конструктор. Все остальное будет делать AutoMapper, который мы настроили ранее.

[ApiController] [Route("[controller]")] public class EmployeeController : Controller { private readonly IMapper _mapper; public EmployeeController(IMapper mapper) { _mapper = mapper; }

Что делать, если имена свойств разные

Мы снова вернулись к нашим занятиям. На этот раз я добавил два свойства, которые называются по-разному.

В Employee у нас есть CurrentCity и HomePhone.

public class Employee { public string CurrentCity { get; set; } public string HomePhone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

В EmployeeDto у нас есть City и Phone.

public class EmployeeDto { public string City { get; set; } public string Phone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

AutoMapper действительно работает на отражение, но, конечно, он не может читать наши мысли.

public Employee Index() { EmployeeDto eDTO = new EmployeeDto() { EmployeeId = 121, FirstName = "Иван", LastName = "Иванов", Login = "ivan", OrganizationCompanyName = "Рога и копыта", OrganizationId = 34, Role = "Средство для мытья полов 34", RootOrganizationCompanyName = "Иванов и ко", RootOrganizationId = 12, Type = "Очиститель", City = "Москва", Phone = "74955555555" }; return _mapper.Map<Employee>(eDTO); }
Code language: PHP (php)

Поэтому в данном случае сопоставление не будет работать.

Вам нужно указать AutoMapper, как свойство сопоставляется с чем.

public class AutoMapperProfile : Profile { public AutoMapperProfile() { CreateMap<EmployeeDto, Employee>() .ForMember(dest => dest.CurrentCity, opt => opt.MapFrom(src => src.City)) .ForMember(dest => dest.HomePhone, opt => opt.MapFrom(src => src.Phone)); } }
Code language: HTML, XML (xml)

Что, если наши классы содержат больше классов, которые нуждаются в отображении.

Вот классы, которые определяют адрес.

public class AdressDto { public string CityCode { get; set; } public string Street { get; set; } public int StreetNumber { get; set; } public string Country { get; set; } } public class Adress { public string CityCode { get; set; } public string OnStreet { get; set; } public string Country { get; set; } }
Code language: JavaScript (javascript)

Адреса включены в наши классы.

public class Employee { public Adress Adress { get; set; } public string CurrentCity { get; set; } public string HomePhone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } } public class EmployeeDto { public AdressDto Adress { get; set; } public string City { get; set; } public string Phone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

Нам просто нужно добавить еще одну функцию отображения в наше определение отображения.

public AutoMapperProfile() { CreateMap<EmployeeDto, Employee>() .ForMember(dest => dest.CurrentCity, opt => opt.MapFrom(src => src.City)) .ForMember(dest => dest.HomePhone, opt => opt.MapFrom(src => src.Phone)); CreateMap<AdressDto, Adress>() .ForMember(dest => dest.OnStreet, opt => opt.MapFrom (src => src.Street + " " + src.StreetNumber)); }
Code language: JavaScript (javascript)

Кроме того, я усложнил себе задачу и, как вы можете видеть, теперь превратил два свойства в одно. Кто сказал, что вы не можете этого сделать?

Условное отображение

Что если я захочу добавить дополнительные условия.

Я добавил два свойства к сотруднику. Один определяет логическое условие, является ли сотрудник старше 30 лет. Второе свойство – это просто комментарий

public class Employee { public string Comment { get; set; } public bool isOver30 { get; set; } public Adress Adress { get; set; } public string CurrentCity { get; set; } public string HomePhone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } } public class EmployeeDto { public int Age { get; set; } public AdressDto Adress { get; set; } public string City { get; set; } public string Phone { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Login { get; set; } public string OrganizationCompanyName { get; set; } public int OrganizationId { get; set; } public string Role { get; set; } public string RootOrganizationCompanyName { get; set; } public int RootOrganizationId { get; set; } public string Type { get; set; } public int EmployeeId { get; set; } }
Code language: JavaScript (javascript)

Хочет, чтобы для возраста более 30 лет определенное свойство было установлено в true. Для возраста более 55 лет он хочет добавить комментарий “Скоро на пенсию”.

CreateMap<EmployeeDto, Employee>() .ForMember(dest => dest.CurrentCity, opt => opt.MapFrom(src => src.City)) .ForMember(dest => dest.HomePhone, opt => opt.MapFrom(src => src.Phone)) .ForMember(dest => dest.isOver30, opt => opt.MapFrom(src => src.Age > 30 ? true : false)) .ForMember(dest => dest.Comment, opt => opt.MapFrom(src => src.Age > 55 ? "Скоро на пенсию" : ""));
Code language: JavaScript (javascript)

Чтобы проверить, работают ли все отображения.

public Employee Index() { EmployeeDto eDTO = new EmployeeDto() { Age = 61, Adress = new AdressDto() { CityCode = "11250-50", Country = "Poland", Street = "Polonozea ", StreetNumber = 128 }, EmployeeId = 121, FirstName = "Иван", LastName = "Иванов", Login = "ivan", OrganizationCompanyName = "Рога и копыта", OrganizationId = 34, Role = "Средство для мытья полов 34", RootOrganizationCompanyName = "Иванов и ко", RootOrganizationId = 12, Type = "Очиститель", City = "Москва", Phone = "74955555555" }; return _mapper.Map<Employee>(eDTO); }
Code language: PHP (php)

Резюме

Как видите, AutoMapper очень прост в использовании. Для продвинутых примеров вы, вероятно, захотите написать свои методы преобразования.

Эти методы могут быть использованы с AutoMapper, так что вы не проиграете.

В конце концов, приятно иметь эту логику, завернутую в интерфейс “IMapper”, а не разбросанную по всему коду. Наслаждайтесь картографией.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *