Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
312 views
in Technique[技术] by (71.8m points)

c# - How do I teach Automapper to map X to IContainer<Y>?

This came up mapping from models to mvvmcross viewmodels, where you have some container classes you might use, for example:

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; }
}
  • I need to teach AutoMapper to map from A to INC<B> (and back), without specifiying A or B.

  • Null destination INC<> should be created, non-null should not be re-created.

  • Mapping should continue such that destination.Value = Mapper.Map<A,B>(source).

  • Mapping null --> INC<T> should result in INC<T>.Value = SomeDefaultValue

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Thanks for Lucians help in the comments.

These mappers will work for Mapper.Map<INC<String>>("test"), but not for Mapper.Map<String, INC<String>>(null, new NC<String>("I shouldnt be here")), since AutoMapper won't send a null source value.

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);
    }
}

This uses a slightly extended 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);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...