如何教Automapper将X映射到IContainer <y>?

时间:2018-01-14 12:21:57

标签: c# automapper mvvmcross

这样就可以从模型映射到mvvmcross视图模型,在这里您可以使用一些容器类,例如:

namespace MvvmCross.Binding
{
  interface INC<T>
  {
    T Value { get; set; }
  }
}
class model
{
  String name { get; set; }
  DateTime when { get; set; }
  othermodel data { get; set; }
}
class viewmodel
{
  INC<String> Name { get; set; }
  INC<String> When { get; set; }
  INC<otherviewmodel> Data { get; set; }
}
  • 我需要教AutoMapper从A映射到INC<B>(并返回),而不指定A或B.

  • 应创建空目标INC<>,不应重新创建非空。

  • 映射应继续,以便destination.Value = Mapper.Map<A,B>(source)

  • 映射null --> INC<T>应该会产生INC<T>.Value = SomeDefaultValue

4 个答案:

答案 0 :(得分:0)

感谢Lucians帮助评论。

这些映射器适用于Mapper.Map<INC<String>>("test"),但不适用于Mapper.Map<String, INC<String>>(null, new NC<String>("I shouldnt be here")),因为AutoMapper不会发送空源值。

Mapper.Initialize(c =>
{
    c.Mappers.Insert(0, new DestinationObjectContainerMapper());
    c.Mappers.Insert(0, new SourceObjectContainerMapper());
});
Mapper.AssertConfigurationIsValid();

public class DestinationObjectContainerMapper : BlackBoxObjectMapper
{
    bool InterfaceMatch(Type x) => x.IsGenericType && typeof(INC<>).IsAssignableFrom(x.GetGenericTypeDefinition());
    Type CType(Type cdt) => cdt.GetInterfaces().Concat(new[] { cdt }).Where(InterfaceMatch).Select(x => x.GenericTypeArguments[0]).FirstOrDefault();
    public override bool IsMatch(TypePair context) => CType(context.DestinationType) != null;

    public override object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context)
    {
        var dcType = CType(destinationType);

        // Create a container if destination is null
        if (destination == null)
            destination = Activator.CreateInstance(typeof(NC<>).MakeGenericType(dcType));

        // This may also fail because we need the source type
        var setter = typeof(INC<>).MakeGenericType(dcType).GetProperty("Value").GetSetMethod();
        var mappedSource = context.Mapper.Map(source, sourceType, dcType);

        // set the value
        setter.Invoke(destination, new[] { mappedSource });
        return destination;
    }
}
public class SourceObjectContainerMapper : BlackBoxObjectMapper
{
    bool InterfaceMatch(Type x) => x.IsGenericType && typeof(INC<>).IsAssignableFrom(x.GetGenericTypeDefinition());
    Type CType(Type cdt) => cdt.GetInterfaces().Concat(new[] { cdt }).Where(InterfaceMatch).Select(x => x.GenericTypeArguments[0]).FirstOrDefault();
    public override bool IsMatch(TypePair context) => CType(context.SourceType) != null;

    public override object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context)
    {
        // this is only obtainable if destination is not null - so this will not work.
        var scType = CType(sourceType);

        // destination could also be null, this is another anavoidable throw
        object sourceContainedValue = null;
        if (source != null)
        {
            var getter = typeof(INC<>).MakeGenericType(scType).GetProperty("Value").GetGetMethod();
            sourceContainedValue = getter.Invoke(source, new Object[0]);
        }

        // map and return
        return context.Mapper.Map(sourceContainedValue, scType, destinationType);
    }
}

这使用稍微扩展的ObjectMapper:

public abstract class BlackBoxObjectMapper : IObjectMapper
{
    private static readonly MethodInfo MapMethod = typeof(BlackBoxObjectMapper).GetTypeInfo().GetDeclaredMethod("Map");

    public abstract bool IsMatch(TypePair context);

    public abstract object Map(object source, Type sourceType, object destination, Type destinationType, ResolutionContext context);

    public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
        PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
        Expression contextExpression) =>
        Expression.Call(
            Expression.Constant(this),
            MapMethod,
            sourceExpression,
            Expression.Constant(sourceExpression.Type),
            destExpression,
            Expression.Constant(destExpression.Type),
            contextExpression);
}

答案 1 :(得分:0)

感谢Lucians帮助评论。这个使用表达式的映射器有效,并且会根据需要通过this patchIObjectMapperInfo.CanMapNullSource = true的一些帮助来接收空值。

AM团队花了很多精力避免传递对Mappers求值为null的表达式,并且它肯定简化了AutoMapper.Mappers命名空间,所以这个补丁显然是有争议的。我想到了一些不那么具有侵入性的方式来表示这个像NullSafeExpression,或者像interface ICanHandleNullSourceExpressions {}这样的匿名接口名字来试图阻止流程,但我找不到任何看起来更好的东西。

public class ContainerDestinationMapper : BaseContainerMapper, IObjectMapperInfo
{

    readonly Func<Type, Expression> createContainer;

    public bool CanMapNullSource => true;

    public ContainerDestinationMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName, Func<Type, Expression> createContainer)
        : base(GenericContainerTypeDefinition, ContainerPropertyName)
    {
        this.createContainer = createContainer;
    }
    public bool IsMatch(TypePair context) => ContainerDef(context.DestinationType) != null;

    public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
        PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
        Expression contextExpression)
    {

        var dparam = DigParameter(destExpression);
        var dVal = Expression.Property(dparam, dparam.Type.GetTypeInfo().GetDeclaredProperty("Value"));
        var cdt = ContainerDef(destExpression.Type);
        var tp = new TypePair(sourceExpression.Type, cdt);
        var ret = Expression.Block
                    (
                        // make destination not null
                        Expression.IfThen
                        (
                            Expression.Equal(dparam, Expression.Constant(null)),
                            Expression.Assign(dparam, createContainer(cdt))
                        ),
                        // Assign to the destination
                        Expression.Assign
                        (
                            dVal,
                            ExpressionBuilder.MapExpression // what you get if you map source to dest.Value
                            (
                                configurationProvider,
                                profileMap,
                                tp,
                                sourceExpression,
                                contextExpression,
                                null,
                                dVal,
                                true
                            )
                        ),
                        destExpression
                    // But we need to return the destination type!
                    // Sadly it will go on to assign destExpression to destExpression.
                    );
        return ret;
    }
    public TypePair GetAssociatedTypes(TypePair initialTypes)
    {
        return new TypePair(initialTypes.SourceType, ContainerDef(initialTypes.DestinationType));
    }
}

public class ContainerSourceMapper : BaseContainerMapper, IObjectMapperInfo
{
    public bool CanMapNullSource => true;

    public ContainerSourceMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName)
        : base(GenericContainerTypeDefinition, ContainerPropertyName) { }

    public bool IsMatch(TypePair context) => ContainerDef(context.SourceType) != null;

    public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap,
        PropertyMap propertyMap, Expression sourceExpression, Expression destExpression,
        Expression contextExpression)
    {
        var dstParam = DigParameter(destExpression);
        return Expression.Block(
            Expression.IfThenElse
            (
                Expression.Equal(sourceExpression, Expression.Constant(null)),
                Expression.Assign(dstParam, Expression.Default(destExpression.Type)),
                Expression.Assign(dstParam,
                    ExpressionBuilder.MapExpression(configurationProvider, profileMap,
                        new TypePair(ContainerDef(sourceExpression.Type), destExpression.Type),
                        Expression.Property(sourceExpression, sourceExpression.Type.GetTypeInfo().GetDeclaredProperty(containerPropertyName)),
                        contextExpression,
                        propertyMap,
                        destExpression
                    )
                )
            ),
            dstParam
        );
    }

    public TypePair GetAssociatedTypes(TypePair initialTypes)
    {
        return new TypePair(ContainerDef(initialTypes.SourceType), initialTypes.DestinationType);
    }
}
public class BaseContainerMapper
{
    protected readonly Type genericContainerTypeDefinition;
    protected readonly String containerPropertyName;
    public BaseContainerMapper(Type GenericContainerTypeDefinition, String ContainerPropertyName)
    {
        genericContainerTypeDefinition = GenericContainerTypeDefinition;
        containerPropertyName = ContainerPropertyName;
    }
    protected ParameterExpression DigParameter(Expression e)
    {
        if (e is ParameterExpression pe) return pe;
        if (e is UnaryExpression ue) return DigParameter(ue.Operand);
        throw new ArgumentException("Couldn't find parameter");
    }
    public static Type ContainerDef(Type gen, Type to)
    {
        return new[] { to }.Concat(to.GetInterfaces())
                            .Where(x => x.IsGenericType)
                            .Where(x => gen.IsAssignableFrom(x.GetGenericTypeDefinition()))
                            .Select(x => x.GenericTypeArguments.Single())
                            .FirstOrDefault(); // Hopefully not overloaded!

    }
    protected Type ContainerDef(Type to)
    {
        return ContainerDef(genericContainerTypeDefinition, to);
    }
    protected PropertyInfo Of(Expression expr)
    {
        return expr.Type.GetTypeInfo().GetDeclaredProperty(containerPropertyName);
    }
}

答案 2 :(得分:0)

This patch允许在typeof(void)中使用CreateMap来表示未包含在某个类型中的单个参数(即它将匹配任何类型)并构造ITypeConverter就这样。

未修补的AutoMapper的替代方法是将XY替换为以下转换器中的object。我已经将注释添加到了失败的行。即使忽略了这些失败,这种尝试也需要反思,因此更复杂,效率更低。

Mapper.Initialise(cfg =>
{
    cfg.CreateMap(typeof(void), typeof(IContainer<>)).ConvertUsing(typeof(TCf<,>));
    cfg.CreateMap(typeof(IContainer<>), typeof(void)).ConvertUsing(typeof(TCb<,>));
});

class TCf<X, Y> : ITypeConverter<X, IContainer<Y>>
{
    public IContainer<Y> Convert(X source, IContainer<Y> destination, ResolutionContext context)
    {
        if (destination == null)
        {
            // if Y was object we could not create the correct container type
            destination = new Container<Y>(); 
            destination.Configure();
        }
        // if Y was object and source was null, we could not map to the correct type
        destination.Value = context.Mapper.Map<Y>(source);
        return destination;
    }
}

class TCb<X, Y> : ITypeConverter<IContainer<X>, Y>
{
    public Y Convert(IContainer<X> source, Y destination, ResolutionContext context)
    {
        // if X was object and source was null, we could not choose an appropriate default
        var use = source == null ? GetSomeDefault<X>() : source.Value;
        // if Y was object and destination was null, we could not map to the correct type
        return context.Mapper.Map<Y>(use);
    }
}

答案 3 :(得分:0)

通过使用ForAllMaps,您可以获取源/目标类型并提供封闭的,完全通用的转换器类型。如果您想直接致电 - Map<X,IContainer<Y>,这无济于事,但您不应该这样做。

Mapper.Initialize(c =>
{
    c.CreateMap<model, viewmodel>().ReverseMap();
    c.ForAllMaps((p, mc) =>
    {
        Type st = p.SourceType, sct = GetContained(st);
        Type dt = p.DestinationType, dct = GetContained(dt);
        if (sct != null) mc.ConvertUsing(typeof(TCReverse<,>).MakeGenericType(sct, dt));
        if (dct != null) mc.ConvertUsing(typeof(TCForward<,>).MakeGenericType(st, dct));
    });
});
Mapper.AssertConfigurationIsValid();
Mapper.Map<viewmodel>(new model());
Mapper.Map<model>(new viewmodel());

使用简单的转换器:

public class TCReverse<X,Y> : ITypeConverter<IContainer<X>, Y>
{
    public Y Convert(IContainer<X> source, Y destination, ResolutionContext context)
    {
        var use = source == null ? default(X) : source.Value;
        return context.Mapper.Map(use, destination);
    }
}

public class TCForward<X,Y> : ITypeConverter<X, IContainer<Y>>
{
    public IContainer<Y> Convert(X source, IContainer<Y> destination, ResolutionContext context)
    {
        if (destination == null)
            destination = new Container<Y>();
        destination.Value = context.Mapper.Map(source, destination.Value);
        return destination;
    }
}

我在这里使用了一个辅助方法:

Type GetContained(Type t)
{
    return t.GetInterfaces()
            .Concat(new[] { t })
            .Where(x => x.IsGenericType)
            .Where(x => typeof(IContainer<>).IsAssignableFrom(x.GetGenericTypeDefinition()))
            .Select(x => x.GenericTypeArguments[0])
            .FirstOrDefault();
}