faloi
faloi

Reputation: 401

EmitMapper - Generic mapping from abstract model object to abstract DTO

I need some help transitioning from ValueInjecter to EmitMapper (I've decided so for performance reasons). My use case is one of the most common ones: mapping a Model object to a DTO, based on some rules.

One of this rules is: if a property's type is a subclass of DomainObject, then it should be mapped it to its correspondent DTO. With concrete types that's ok, but I also want it to work with abstract types. The problem is that I don't how to tell EmitMapper which DTO should be used, in a dynamic fashion.

In ValueInjecter code:

public bool IsDomainObjectAndTargetIsDto(ConventionInfo it) 
{
    return it.SourceProp.Value.IsNotNull()
        && typeof(DomainObject).IsAssignableFrom(it.SourceProp.Type)
        && it.TargetProp.Type.Name.EndsWith("DTO");
}

As all of my DTOs implements DTO<> interface, I thought I could use EmitMapper's DefaultMapConfig.ConvertGeneric method but I just can't figure out how.

Just for completeness, I include my current (not working) code:

public class ModelToDtoConventions() 
{
    public IMappingConfigurator GetConfig() 
    {
        return new DefaultMapConfig()
            .ConvertUsing<IdentificableObject, int>(o => o.Id)
            .ConvertGeneric(
                typeof (DomainObject), 
                typeof (DTO<>),
                new DefaultCustomConverterProvider(
                    typeof (DomainObjectToDtoConverter<>)
                )
            );
    }
}

public class DomainObjectToDtoConverter<TDomainObject>
{
    public DTO<TDomainObject> Convert(TDomainObject from, object state)
    {
        return (DTO<TDomainObject>)this.CreateDtoFor(@from);
    }

    private object CreateDtoFor(object modelObject)
    {
        var modelType = modelObject.GetType();
        var dtoInterface = typeof(DTO<>).MakeGenericType(modelType);

        var dtoType = dtoInterface
            .GetConcreteSubtypes()
            .Single();

        return Activator.CreateInstance(dtoType);
    }
}

When I try to use this mapping on a test, I'm getting the following exception

'MyProject.WebApi.Test.Utils.DTOInjectorTest.Abstract_DTO_property_of_DTO_can_be_mapped_from_its_model' failed: System.ArgumentException : Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
    at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method, Boolean throwOnBindFailure)
    at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method)
    at EmitMapper.MappingConfiguration.MapConfigBaseImpl.GetGenericConverter(Type from, Type to)
    at EmitMapper.MappingConfiguration.MapConfigBaseImpl.FilterOperations(Type from, Type to, IEnumerable`1 operations)
    at EmitMapper.MappingConfiguration.DefaultMapConfig.GetMappingOperations(Type from, Type to)
    at EmitMapper.EmitBuilders.MappingBuilder.BuildCopyImplMethod()
    at EmitMapper.ObjectMapperManager.BuildObjectsMapper(String MapperTypeName, Type from, Type to, IMappingConfigurator mappingConfigurator)
    at EmitMapper.ObjectMapperManager.GetMapperInt(Type from, Type to, IMappingConfigurator mappingConfigurator)
    at EmitMapper.ObjectMapperManager.GetMapperImpl(Type from, Type to, IMappingConfigurator mappingConfigurator)
    at MyProject.WebApi.Adapters.DTOInjector.Transform[TDestination](IMappingConfigurator config, Object source, TDestination destination) in c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs:line 56
    at MyProject.WebApi.Adapters.DTOInjector.CreateDto[TDTO](Object entity) in c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs:line 47
    at MyProject.WebApi.Test.Utils.DTOInjectorTest.Abstract_DTO_property_of_DTO_can_be_mapped_from_its_model() in c:\Users\faloi\Documents\GitHub\api\WebApi.Test\Utils\DTOInjectorTest.cs:line 334 c:\Users\faloi\Documents\GitHub\api\WebApi\Adapters\DTOInjector.cs  56  

EDIT: this is an example of objects that I'd like to map.

//Domain objects
public class Game
{
    public IEnumerable<Map> Maps { get; set; }
    public Map MostPlayedMap { get; set; }

    public Game()
    {
        this.Maps = new List<Map>();
    }
}

public abstract class Map : DomainObject
{
    public string Name { get; set; }
}

public class BombDefuseMap : Map
{
    public Player BombHolder { get; set; }
}

public class HostageRescueMap : Map
{
    public int QuantityOfHostages { get; set; }
}

//DTOs
public class GameDTO : DTOWithId<Game>
{
    public List<MapDTO> Maps { get; set; }
    public MapDTO MostPlayedMap { get; set; }
}

public abstract class MapDTO
{
    public string Name { get; set; }
}

public class BombDefuseMapDTO : MapDTO, DTO<BombDefuseMap>
{
    public int BombHolder { get; set; }
}

public class HostageRescueMapDTO : MapDTO, DTO<HostageRescueMap>
{
    public int QuantityOfHostages { get; set; }
}

Upvotes: 0

Views: 1501

Answers (1)

Omu
Omu

Reputation: 71288

if you're concerned about performance have a look at this page: http://valueinjecter.codeplex.com/wikipage?title=SmartConventionInjection

it's an injections that performs much better but you don't get the value in the matching algorithm, most times you don't need it anyway

Upvotes: 0

Related Questions