Стоит отметить, что вы на самом деле не отображаете ObjectA
в ViewModelA
. Скорее (ObjectA
, List<ObjectA>
) до ViewModelA
, поскольку вы не можете определить ViewModelA
без List<ObjectA>
.
Для моделирования, скажем, ObjectA
имеет свойство Index
, а также число Pages
, которое оно содержит.
public class ObjectA
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
А для ViewModelA
мы хотим разрешить StartPage
, основываясь на свойствах предыдущих ObjectA
.
public class ViewModelA
{
public int StartPage { get; set; }
public string MyProperty { get; set; }
}
Мы можем очистить ваш текущий подход, используя методы расширения.
public static class AutoMapperExt
{
public static TDestination MapWithSource<TSource, TDestination>(this IMapper mapper, TSource source)
=> mapper.Map<TSource, TDestination>(source, opts => opts.Items[typeof(TSource).ToString()] = source);
public static TSource GetSource<TSource>(this ResolutionContext context)
=> (TSource)context.Items[typeof(TSource).ToString()];
}
Используя эти методы, нам больше не нужно напрямую обрабатывать коллекцию Items
контекста.
class Program
{
static void Main(string[] args)
{
var config =
new MapperConfiguration(cfg =>
cfg.CreateMap<ObjectA, ViewModelA>()
.ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver, int>(src => src.Index))
);
var mapper = config.CreateMapper();
var source = new List<ObjectA>
{
new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
};
var result = mapper.MapWithSource<List<ObjectA>, List<ViewModelA>>(source);
result.ForEach(o => Console.WriteLine(o.StartPage)); // prints 1,4,6
Console.ReadKey();
}
}
public class CustomResolver : IMemberValueResolver<object, object, int, int>
{
public int Resolve(object source, object destination, int sourceMember, int destMember, ResolutionContext context)
{
var index = sourceMember;
var list = context.GetSource<List<ObjectA>>();
var pages = 1;
for (int i = 0; i < index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
Если вы хотите повторно использовать CustomResolver
в разных классах, вы можете абстрагировать свойства, с которыми он работает, в интерфейс.
public interface IHavePages
{
int Index { get; }
int Pages { get; }
}
public class ObjectA : IHavePages
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
Таким образом, распознаватель больше не привязан к конкретной реализации. Теперь мы можем даже использовать интерфейс в качестве параметра типа.
public class CustomResolver : IMemberValueResolver<IHavePages, object, int, int>
{
public int Resolve(IHavePages source, object destination, int sourceMember, int destMember, ResolutionContext context)
{
var hasPages = source;
var index = sourceMember;
var list = context.GetSource<List<IHavePages>>();
var pages = 1;
for (int i = 0; i < index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
Все, что нам нужно сделать, это преобразовать наш List<ObjectA>
перед отображением.
var listOfObjectA = new List<ObjectA>
{
new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
};
var source = listOfObjectA.OfType<IHavePages>().ToList();
var result = mapper.MapWithSource<List<IHavePages>, List<ViewModelA>>(source);
// AutoMapper still maps properties that aren't part of the interface
result.ForEach(o => Console.WriteLine($"{o.StartPage} - {o.MyProperty}"));
Как только вы кодируете интерфейс, sourceMember
в CustomResolver
становится избыточным. Теперь мы можем получить его через пройденный source
. С учетом одного окончательного рефакторинга, как мы получаем из IValueResolver
вместо IMemberValueResolver
.
public class CustomResolver : IValueResolver<IHavePages, object, int>
{
public int Resolve(IHavePages source, object destination, int destMember, ResolutionContext context)
{
var list = context.GetSource<List<IHavePages>>();
var pages = 1;
for (int i = 0; i < source.Index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
Обновление подписи.
cfg.CreateMap<ObjectA, ViewModelA>()
.ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver>())
Как далеко вы берете это, зависит только от вас, но вы можете улучшить повторное использование кода, введя абстракции.