Reputation: 146
I need to convert one class to another using automapper and my objects looks like this:
public class Foo
{
public List<object> Objects { get; set; }
}
public class Bar
{
public List<object> Objects { get; set; }
}
public class FooItem
{
public string Name { get; set; }
}
public class BarItem
{
public string Name { get; set; }
}
There could be more types for items, not just FooItem
and BarItem
but I just use these two for simplicity and I need to have this design.
I've tried several things like type converters and so on but still no luck.
Here the current basic conversion code
Mapper.Initialize(cfg =>
{
cfg.CreateMap<FooItem, BarItem>();
cfg.CreateMap<Foo, Bar>();
});
var foo = new Foo()
{
Objects = new List<object>() { new FooItem() { Name = "name" } }
};
var map = Mapper.Map<Foo, Bar>(foo);
The goal is that the Bar
object contains a list of BarItems
at runtime, but so far I only have only managed to get a list of FooItem
at runtime.
Any ideas?
Upvotes: 2
Views: 4515
Reputation: 146
Inspired by @Ogglas I manage to found out an interesting solution where it is not needed to check for all the types.
Basically I wrote my own ITypeConverter
public class CustomResolver : ITypeConverter<List<object>, List<object>>
{
public List<object> Convert(List<object> source, List<object> destination, ResolutionContext context)
{
var objects = new List<object>();
foreach (var obj in source)
{
var destinationType = context.ConfigurationProvider.GetAllTypeMaps().First(x => x.SourceType == obj.GetType()).DestinationType;
var target = context.Mapper.Map(obj, obj.GetType(), destinationType);
objects.Add(target);
}
return objects;
}
}
And get from the context the proper mapping for each type. Currently is not aware of nulls and so on...
Then, when configuring automapper
Mapper.Initialize(cfg =>
{
cfg.CreateMap<FooItem, BarItem>();
cfg.CreateMap<List<object>, List<object>>().ConvertUsing<CustomResolver>();
cfg.CreateMap<Foo, Bar>();
});
Upvotes: 0
Reputation: 70184
Are you satisfied with only a list of BarItems
? In this case you can do it like this:
var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(foo.Objects.Cast<FooItem>());
Update: You can do it like this.
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<FooItem, BarItem>();
cfg.CreateMap<Foo, Bar>()
.ForMember(dest => dest.Objects, opt => opt.ResolveUsing<CustomResolver>());
});
Mapper.AssertConfigurationIsValid();
var foo = new Foo()
{
Objects = new List<object>() { new FooItem() { Name = "name" } }
};
//var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(foo.Objects.Cast<FooItem>());
var map = Mapper.Map<Foo, Bar>(foo);
}
}
public class CustomResolver : IValueResolver<Foo, Bar, List<object>>
{
public List<object> Resolve(Foo source, Bar destination, List<object> member, ResolutionContext context)
{
var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(source.Objects.Cast<FooItem>());
return map.Cast<object>().ToList();
}
}
Upvotes: 3
Reputation: 9463
You can do type checks inside a ConstructUsing
clause:
cfg.CreateMap<object, object>()
.ConstructUsing(src => {
if (src is FooItem) {
return Mapper.Map<BarItem>(src);
}
// ...
throw new InvalidOperationException($"Can not map source item of type '{src.GetType().FullName}'.");
});
But you probably need to introduce an interface for the items in the Objects
collection, because the map object -> object overrides all other maps, but we want to use the map FooItem -> BarItem.
Upvotes: 2