I have one main interface that other interfaces inherit from. I would like the main automapper map to switch and return another relevant defined mapping depending on the object type.
For example, call the main interface IGroup:
public interface IGroup {...}
And then make some more interfaces that inherit from that:
public interface IFamily : IGroup {...}
public interface IFriends : IGroup {...}
public interface INeighbours : IGroup {...}
I define relevant mappings for each inherited group:
CreateMap<IFamily, GroupListView>() ...etc
On the main IGroup map, I would like to switch so that if I have a collection of IGroup it returns the relevant mapping for each item.
I have tried
CreateMap<IGroup, GroupListView>()
.Include<IFamily, GroupListView>()
.Include<IFriends, GroupListView>()
.Include<INeighbours, GroupListView>()
.ConstructUsing((IGroup group, ResolutionContext context) =>
{
switch (group)
{
case IFamily family:
return context.Mapper.Map<IFamily, GroupListView>(family);
case IFriends friends:
return context.Mapper.Map<IFriends, GroupListView>(friends);
case INeighbours neighbors:
return context.Mapper.Map<INeighbours, GroupListView>(neighbors);
default:
throw new InvalidOperationException("Unknown group type: " + group.GetType().FullName);
}
});
But validation complains that there are unmapped members on the IGroup interface because I'm not actually defining anything.
If I define anything after or opt.Ignore, then it overrides the ConstructUsing result.
What would be even better is if instead of throwing an error on unrecognised IGroup type, it returned a default map for IGroup.
Many thanks.
EDIT:
Interesting. So it looks like a couple of issues are going on. I'll document them incase anyone has a similar issue. The switch on the main map is working, and you can define a default case manully (although it would be nice to somehow make a map for this). The code looks like this:
CreateMap<IGroup, GroupListView>()
.Include<IFamily, GroupListView>()
.Include<IFriends, GroupListView>()
.Include<INeighbours, GroupListView>()
.ConstructUsing((IGroup group, ResolutionContext context) =>
{
switch (group)
{
case IFamily family:
return context.Mapper.Map<IFamily, GroupListView>(family);
case IFriends friends:
return context.Mapper.Map<IFriends, GroupListView>(friends);
case INeighbours neighbors:
return context.Mapper.Map<INeighbours, GroupListView>(neighbors);
default:
return new GroupListView() {...} //Would prefer a default map
}
})
.ForAllOtherMembers(opt => opt.Ignore());
What is interesting, is that despite having maps for those interfaces and specifically calling them in the switch case, it seems to prefer the concrete class implementation.
I have a main abstract class base:
public abstract class Group {...}
One accepting generics that inherits from it AND the main interface
public abstract class Group<TPerson, TPlace> : Group, IGroup {...}
And then abstracts for each group type:
public abstract class FamilyGroup<TPerson, TPlace> : Group<TPerson, TPlace> {...}
public abstract class FriendGroup<TPerson, TPlace> : Group<TPerson, TPlace> {...}
public abstract class NeighborGroup<TPerson, TPlace> : Group<TPerson, TPlace> {...}
And finally concrete classes that inherit from these abstract classes and their interfaces
public class BestFriendGroup : FriendGroup<Bestie, Home>, IFriends {...}
Now, for some reason even when explicitly calling a map on one of the interfaces, it is prefering the abstract class. Which is odd because it's not even looking for a concrete type. So despite my method returning a collection of IGroup containing IFriends etc, manually returning
context.Mapper.Map<IFriends,GroupListView>(group)
Is actually picking up the map for the root abstract class
CreateMap<Group, GroupListView>()...
I have no idea why. None of these maps include each other as a base. I have even confirmed this by maping a random property to a manually defined string on the group map and it's that map that is being picked up.
UPDATE 2:
Well this has come full circle and new things have been learned. Turns out that while my classes were indeed instances of the interface, automapper actually pays attention the the list order of inheritence and will always select the first option. Details here: https://github.com/AutoMapper/AutoMapper/issues/1295
So my final classes that inherited from both the abstract class and the interface i.e.
public class BestFriendGroup : FriendGroup<Bestie, Home>, IFriends {...}
would always choose the abstract class map and there is no way to change it because classes must always come before interfaces.
From the automapper dev: "What you're running into here is that AutoMapper takes into account the runtime type of the object when determining how to map. The generic arguments do NOT "select" a map, they merely do some casting underneath the covers for convenience. The runtime types trump all."
I will list my final solution under solutions.
The irony of me saying that I want to define default maps rather than manual is palpable now, as the whole thing has to be manually defined. My final solution to switch to the appropriate type is as follows:
CreateMap<IGroup, GroupListView>()
.ConstructUsing((group, context) =>
{
var groupView = new GroupListView()
{...shared properties};
switch (group)
{
case IFamily family:
{
groupView.familyPropertyOne = family.foo;
break;
}
case IFriends friends:
{
groupView.neighborPropertyOne = friends.foo;
break;
}
case INeighbours neighbors:
{
groupView.neighborPropertyOne = neighbors.foo;
break;
}
default:
break;
}
return groupView;
})
.ForAllOtherMembers(opt => opt.Ignore());
Notice the lack of .include so nothing overrides this. Because it always returns something from the ConstructUsing method, we can tell automapper to ignore the other properties. You could also define the initial shared properties using the fluent syntax. Entirely up to preference.