#c# #asp.net-core #automapper
#c# #asp.net-ядро #automapper
Вопрос:
У меня есть отношения с самоссылками, связанные с .NET Core ApplicationUser
:
public class Network
{
public ApplicationUser ApplicationUser { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser Follower { get; set; }
public string FollowerId { get; set; }
}
Это позволяет вести список «подписчиков» и «подписчиков» в пользовательской модели:
public class ApplicationUser : IdentityUser
{
//...
public ICollection<Network> Following { get; set; }
public ICollection<Network> Followers { get; set; }
}
Я использую Automapper для сопоставления списков подписчиков и подписчиков с viewmodel. Вот модели представления:
public class UserProfileViewModel
{
//...
public IEnumerable<FollowerViewModel> Followers { get; set; }
public IEnumerable<NetworkUserViewModel> Following { get; set; }
}
public class NetworkUserViewModel
{
public string UserName { get; set; }
public string ProfileImage { get; set; }
public bool IsFollowing { get; set; } = true;
public bool IsOwnProfile { get; set; }
}
public class FollowerViewModel
{
public string UserName { get; set; }
public string ProfileImage { get; set; }
public bool IsFollowing { get; set; } = true;
public bool IsOwnProfile { get; set; }
}
Чтобы учесть различные способы сопоставления подписчиков и последователей, мне пришлось создать два идентичных класса: NetworkUserViewModel
и FollowerViewModel
чтобы логика сопоставления Automapper могла различать, как сопоставлять подписчиков и как сопоставлять последователей. Вот профиль сопоставления:
CreateMap<Network, NetworkUserViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.ApplicationUser.UserName))
.ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.ApplicationUser.ProfileImage))
.ReverseMap();
CreateMap<Network, FollowerViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.Follower.UserName))
.ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.Follower.ProfileImage))
.ReverseMap();
CreateMap<ApplicationUser, UserProfileViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.UserName))
.ForMember(x => x.Followers, y => y.MapFrom(x => x.Followers))
.ForMember(x => x.Following, y => y.MapFrom(x => x.Following))
.ReverseMap();
Вы можете видеть, что подписчики отображаются как .MapFrom(x => x.Follower.UserName))
, а следующие пользователи отображаются как .MapFrom(x => x.ApplicationUser.UserName))
.
Мой вопрос: могу ли я определить различные способы сопоставления подписчиков и подписанных пользователей без необходимости определять повторяющиеся классы? Я бы предпочел использовать NetworkUserViewModel
for followed и followers; так что мне не нужен дубликат FollowerViewModel
, если это возможно. Есть ли способ это сделать?
Обновить
Я реализовал предложение @Matthijs (см. Первый комментарий) использовать наследование, чтобы избежать ненужного дублирования свойств. Мои viewmodels теперь такие:
public class UserProfileViewModel
{
//...
public IEnumerable<FollowerViewModel> Followers { get; set; }
public IEnumerable<FollowingViewModel> Following { get; set; }
}
public class NetworkUserViewModel
{
public string UserName { get; set; }
public string ProfileImage { get; set; }
public bool IsFollowing { get; set; }
public bool IsOwnProfile { get; set; }
}
public class FollowingViewModel : NetworkUserViewModel
{
}
public class FollowerViewModel : NetworkUserViewModel
{
}
Со следующим изменением логики Automapper:
CreateMap<Network, FollowingViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.ApplicationUser.UserName))
.ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.ApplicationUser.ProfileImage))
.ReverseMap();
CreateMap<Network, FollowerViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.Follower.UserName))
.ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.Follower.ProfileImage))
.ReverseMap();
CreateMap<ApplicationUser, UserProfileViewModel>()
.ForMember(x => x.UserName, y => y.MapFrom(x => x.UserName))
.ForMember(x => x.Followers, y => y.MapFrom(x => x.Followers))
.ForMember(x => x.Following, y => y.MapFrom(x => x.Following))
.ReverseMap();
Этот рефакторинг уменьшает дублирование и упрощает логику сопоставления.
Я оставляю это открытым на несколько дней на случай, если есть решение, основанное исключительно на логике Automapper…
Комментарии:
1. Возможно, вы могли бы (на данный момент) создать базовый класс, содержащий общие свойства, и пусть
NetworkUserViewModel
иFollowerViewModel
производные от него.2. Спасибо @Matthijs. Я обновил свой ответ вашим предложением, что означает, что я могу избежать дублирования свойств и улучшает логику отображения / читаемость. Я оставляю открытым на всякий случай, если есть решение, основанное на Automapper mogic (я подозреваю, что такого решения нет). Еще раз спасибо
Ответ №1:
Причина, по которой вы столкнулись с проблемами, заключается в том, что целевой объект тот же, но Automapper не может его определить. Automapper просто использует отражение для поиска переменных с тем же именем.
В следующий раз для сложных сопоставлений вы можете подумать о написании пользовательского mapper. Это дает вам большую гибкость и упрощает настройку сопоставления. Вы можете создать пользовательский преобразователь сопоставления, чтобы возвращать нужный вам объект из любого источника, даже если вы укажете несколько источников. Вы сопоставляете источник своему собственному члену назначения. Я нашел это действительно приятным, и это может помочь вам в будущем. Это работает следующим образом:
Распознаватель:
public class MappingResolver: IValueResolver<object, object, MyResponseObject>
{
// Automapper needs parameterless constructor
public MappingResolver()
{
}
// Resolve dependencies here if needed
public MappingResolver(IWhateverINeed whateverINeed)
{
}
public MyResponseObject Resolve(
object source, object destination, MyResponseObject> destMember, ResolutionContext context)
{
if (source.GetType() == typeof(NetworkUserViewModel)
{
// Specific logic for source object, while destination remains the same response
var castedObject = source as NetworkUserViewModel;
return MyResponseObject;
}
}
И добавьте это так в конфигурацию Mapper:
CreateMap<SourceObjectA, MyResponseObject>()
.ForMember(dest => dest.ObjectA, src => src.MapFrom<MappingResolver>());
CreateMap<SourceObjectB, MyResponseObject>()
.ForMember(dest => dest.ObjectB, src => src.MapFrom<MappingResolver>());