Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper 5 Custom Value Resolver "Cannot convert expression type X to return type Y"

I upgraded from an old version of AutoMapper and converted my custom resolvers, but I'm having a hard time.

public class ProductMappingProfile : Profile
{
    public ProductMappingProfile()
    {
        CreateMap<Product, ProductViewModel>()
            .ForMember(
                dest => dest.Model,
                opt => opt.ResolveUsing<ModelNameResolver>(src => src.ModelId));
        // won't compile
    }
}

Where Product has a int? ModelId property and ProductViewModel has a string Name property.

Custom Resolver

public class ModelNameResolver : IValueResolver<short?, string, string>
{
    private readonly InventoryService _inventoryService;
    public ModelNameResolver(InventoryService inventoryService)
    {
        _inventoryService = inventoryService;
    }

    public string Resolve(short? source, string destination, string destMember, ResolutionContext context)
    {
        if (!source.HasValue)
            return "n/a";

        return _inventoryService.GetModel(source.Value)
            .Name;
    }
}

Compilation Error:

The type 'MyNamespace.Web.Resolvers.ModelCodeResolver' cannot be used as type parameter 'TValueResolver' in the generic type or method `'AutoMapper.IMemberConfigurationExpression<TSource,TDestination,TMember>.ResolveUsing<TValueResolver>()'`.

There is no implicit reference conversion from `'MyNamespace.Web.Resolvers.ModelCodeResolver'` to `'AutoMapper.IValueResolver<Data.Models.Product,Web.ViewModels.ProductViewModel,string>'`.

What am I doing wrong? I suspect I am misunderstanding the new custom resolver interface.

like image 705
Petrus Theron Avatar asked Feb 15 '17 16:02

Petrus Theron


1 Answers

IValueResolver interface should be parametrized with source object type, destination object type and type of destination member (that should be type of Resolve method result). In your case parameters should be

 IValueResolver<Product, ProductViewModel, string>

But you are creating resolver which is parametrized with

 IValueResolver<short?, string, string>

short? is not your source object type

string is not your destination object type

You should use something like:

public class ModelNameResolver : IValueResolver<Product, ProductViewModel, string>
{
    private readonly InventoryService _inventoryService;
    public ModelNameResolver(InventoryService inventoryService)
    {
        _inventoryService = inventoryService;
    }

    public string Resolve(Product source, ProductViewModel destination,
       string destMember, ResolutionContext context)
    {
        var modelId = source.ModelId;
        if (!modelId.HasValue)
            return "n/a";

        return _inventoryService.GetModel(modelId.Value).Name;
    }
}

And your mapping should look like

var inventoryService = new InventoryService();
var modelNameResolver = new ModelNameResolver(inventoryService);

Mapper.Initialize(c =>
{
    c.CreateMap<Product, ProductViewModel>()
        .ForMember(dest => dest.Model, opt => opt.ResolveUsing(modelNameResolver));
});

Of course, you can ask your IoC container for instance of model name resolver.


UPDATE: If you want resolver reusable between different source and destination data types, then you have two options:

If your resolver can have parameterless constructor, then you can use IMemberValueResolver:

public class ModelNameResolver : IMemberValueResolver<object, object, int?, string>
{
    // create or assign _inventoryService
    // also note objects as source and destination
    public string Resolve(object source, object destination,
       int? sourceMember, string destMember,
       ResolutionContext context)
    {
         if (!sourceMember.HasValue)
            return "n/a";

         return _inventoryService.GetModel(sourceMember.Value).Name;
    }
}

Usage

.ForMember(dest => dest.Model, 
           opt => opt.ResolveUsing<ModelNameResolver, int?>(src => src.ModelId)

Second option - do not use resolver. Just use MapFrom with your custom class which does mapping:

.ForMember(dest => dest.Model, 
           opt => opt.MapFrom(src => someClass.GetModelName(src.ModelId)));     

And someSlass should contain method for getting model name by id

public string GetModelName(int? modelId)
{
    if (!modelId.HasValue)
        return "n/a";

    return _inventoryService.GetModel(modelId.Value).Name;
}
like image 161
Sergey Berezovskiy Avatar answered Sep 20 '22 12:09

Sergey Berezovskiy