0

we are using AutoMapper and ReactiveProperty in one of my projects extensively. So I created some mappings for all interfaces and classes of ReactiveProperty to Objects and reverse. It was a straight forward task as long as the IReactiveProperty<T> is mapped to destination type T. e.G.

class Model
{
    public string Foo { get; set; }
}
class ViewModel
{
    public IReactiveProperty<string> Foo { get; set; }
}
class MyProfile : AutoMapper.Profile
{
    public MyProfile()
    {
        CreateMap<Model, ViewModel>();
        CreateMap<ViewModel, Model>();
        CreateMap(typeof(object), typeof(IReactiveProperty<>))
            .ConvertUsing(typeof(ValueToReactivePropertyConverter<>));
        CreateMap(typeof(IReadOnlyReactiveProperty), typeof(object))
            .ConvertUsing(src => ((IReadOnlyReactiveProperty)src).Value);
    }
    
}

public class ValueToReactivePropertyConverter<TValue> : ITypeConverter<object, IReactiveProperty<TValue>>
{
    private static IReactiveProperty<TValue> SetDestination(IReactiveProperty<TValue>? destination, object? value,
        IRuntimeMapper mapper)
    {
        if (destination != null)
            destination.Value = mapper.Map<TValue>(value!);
        else
            destination = new ReactiveProperty<TValue?>(mapper.Map<TValue>(value))!;
        return destination;
    }

    public IReactiveProperty<TValue> Convert(
        object? source,
        IReactiveProperty<TValue>? destination,
        ResolutionContext context)
    {
        return source switch
        {
            null => SetDestination(destination, default, context.Mapper),
            TValue value => SetDestination(destination, value, context.Mapper),
            IReadOnlyReactiveProperty rp => SetDestination(destination, rp.Value, context.Mapper),
            _ => SetDestination(destination, source, context.Mapper),
        };
    }
}

But now I try to map from an IReactiveProperty<TSource> to TDestination since the ViewModel again contains AnotherViewModel like

class Model
{
    public AnotherModel Foo { get; set; }
}
class AnotherModel
{
    public string AnotherFoo { get; set; }
}
class ViewModel
{
    public IReactiveProperty<AnotherViewModel> Foo { get; set; }
}
class AnotherViewModel
{
    public IReactiveProperty<string> AnotherFoo { get; set; }
}
mapper.Map<Model>(new ViewModel());

To be able to map these classes I'd need to call Mapper again like

CreateMap(typeof(IReadOnlyReactiveProperty), typeof(object))
            .ConvertUsing((src, dest, ctx)  => ctx.Mapper.Map<???>((IReadOnlyReactiveProperty)src).Value);

But now I've the problem that I can not figure out the destination type I require in the mapping. I get stranded with the same problem when trying to use ITypeConverter<,>.

Is there any way to get the destination type for all the open generic mappings from IReadOnlyReactiveProperty<>to object? Or does someone maybe have a better solution for my problem?

All the best, Butzei

Progman
  • 16,827
  • 6
  • 33
  • 48
Butzei
  • 1
  • 1
  • https://github.com/AutoMapper/AutoMapper/blob/a50adf8fa0024df653b7f7e4f6d555e1f1648542/src/UnitTests/Mappers/CustomMapperTests.cs#L122 – Lucian Bargaoanu Sep 30 '22 at 14:38

1 Answers1

0

Thank you Lucian Bargaoanu!

Your hint brought me to the solution.

Instead of creating a map I now create an IObjectMapper for ReactiveProperties

public class ReactivePropertyToValueMapper : IObjectMapper
{
    public bool IsMatch(TypePair context) => context.SourceType.IsAssignableTo(typeof(IReadOnlyReactiveProperty)) && !context.DestinationType.IsAssignableTo(typeof(IReadOnlyReactiveProperty));
    
    public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) =>
        configuration.MapExpression(profileMap, GetAssociatedTypes(sourceExpression.Type, destExpression.Type),
            ExpressionBuilder.Property(sourceExpression, "Value"), memberMap, destExpression);
    TypePair GetAssociatedTypes(Type sourceType, Type destinationType) => new(sourceType.GetGenericArguments().Single(), destinationType);
}

and register it with my MapperConfiguration using the IntenalApi

new MapperConfiguration(x =>
{
   x.AddProfile(typeof(ReactivePropertyProfile));
   x.Internal().Mappers.Insert(0,new ReactivePropertyToValueMapper());
}).CreateMapper();

For the direction TSource -> IReactiveProperty<TDestination> I keep the Profile

public ReactivePropertyProfile()
{
    CreateMap(typeof(object), typeof(ReactiveProperty<>))
        .ConvertUsing(typeof(ValueToReactivePropertyConverter<>));
    CreateMap(typeof(object), typeof(IReactiveProperty<>))
        .ConvertUsing(typeof(ValueToReactivePropertyConverter<>));
}
public class ValueToReactivePropertyConverter<TValue> : ITypeConverter<object, IReactiveProperty<TValue>>
{
    private static IReactiveProperty<TValue> SetDestination(IReactiveProperty<TValue>? destination, object? value,
        IRuntimeMapper mapper)
    {
        if (destination != null)
            destination.Value = mapper.Map<TValue>(value!);
        else
            destination = new ReactiveProperty<TValue?>(mapper.Map<TValue>(value))!;
        return destination;
    }

    public IReactiveProperty<TValue> Convert(
        object? source,
        IReactiveProperty<TValue>? destination,
        ResolutionContext context)
    {
        return source switch
        {
            null => SetDestination(destination, default, context.Mapper),
            TValue value => SetDestination(destination, value, context.Mapper),
            IReadOnlyReactiveProperty rp => SetDestination(destination, rp.Value, context.Mapper),
            _ => SetDestination(destination, source, context.Mapper),
        };
    }
}
Butzei
  • 1
  • 1