How to use the new IValueResolver of AutoMapper?

PaddySe picture PaddySe · Jul 11, 2016 · Viewed 17k times · Source

I am at a loss as to how to use the new IValueResolver interface in the new version of AutoMapper. Perhaps I used them improperly in the previous versions of AutoMapper...

I have a lot of model classes, some of them are generated from several databases on several database servers, using sqlmetal.

Some of these classes has a string property, PublicationCode, which identifies which publication the subscription, or offer, or invoice, or whatever it is, belongs to.

The publication can exist in either of two systems (the old and the new system), hence I have a bool property on the destination model classes which tells whether the publication is in the old or the new system.

Using the old version (<5?) of AutoMapper, I used a ValueResolver<string, bool> which took the PublicationCode as an input parameter, and returned a bool indicating the location of the publication (old or new system).

With the new version (5+?) of AutoMapper, this seems to no longer be possible. The new IValueResolver requires a unique implementation of each and every combination of source and destination models that I have, where src.PublicationCode needs to be resolved into a dst.IsInNewSystem.

Am I just trying to use the value resolvers in the wrong way? Is there a better way? The main reason I would like to use a resolver is that I would prefer to have services injected into the constructor, and not having to use DependencyResolver and the like in the code (I'm using Autofac).

Currently, I use it in the following way:

// Class from Linq-to-SQL, non-related properties removed.
public class FindCustomerServiceSellOffers {
    public string PublicationCode { get; set; }
}

This is one of several data model classes I have, which contains a PublicationCode property). This particular class is mapped to this view model:

public class SalesPitchViewModel {
    public bool IsInNewSystem { get; set; }
}

The mapping definition for these two classes is (where expression is an IProfileExpression), non-related mappings removed:

expression.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
          .ForMember(d => d.IsInNewSystem, o => o.ResolveUsing<PublicationSystemResolver>().FromMember(s => s.PublicationCode));

And the resolver:

public class PublicationSystemResolver : ValueResolver<string, bool>
{
    private readonly PublicationService _publicationService;
    public PublicationSystemResolver(PublicationService publicationService)
    {
        _publicationService = publicationService;
    }

    protected override bool ResolveCore(string publicationCode)
    {
        return _publicationService.IsInNewSystem(publicationCode);
    }
}

And the use of the mapper:

var result = context.FindCustomerServiceSellOffers.Where(o => someCriteria).Select(_mapper.Map<SalesPitchViewModel>).ToList();

Answer

Rhumborl picture Rhumborl · Jul 11, 2016

You can create a more general value resolver by implementing IMemberValueResolver<object, object, string, bool> and using that in your mapping configuration. You can provide a source property resolution function as before:

public class PublicationSystemResolver : IMemberValueResolver<object, object, string, bool>
{
    private readonly PublicationService _publicationService;

    public PublicationSystemResolver(PublicationService publicationService)
    {
        this._publicationService = publicationService;
    }

    public bool Resolve(object source, object destination, string sourceMember, bool destMember, ResolutionContext context)
    {
        return _publicationService.IsInNewSystem(sourceMember);
    }
}



cfg.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
    .ForMember(dest => dest.IsInNewSystem,
        src => src.ResolveUsing<PublicationSystemResolver, string>(s => s.PublicationCode));