AutoMapper - passing parameter to custom resolver weird behavior

palroj picture palroj · Sep 17, 2014 · Viewed 9.9k times · Source

Although I'm relatively new to AutoMapper I'm using it in a small project I'm developing. I've never had problems using it before but now I'm facing some weird behavior passing parameters to a Custom Resolver.

Here's the scenario: I get a list of messages from my repository and then map those to a frontend friendly version of it. Nothing fancy, just some normal mapping between objects. I have a field in that frontend object that tells if a certain user already voted for that message and that's what I'm using the Custom Resolver for (it's that second "ForMember"):

    public List<SupportMessageUi> GetAllVisible(string userId)
    {
        Mapper.CreateMap<SupportMessage, SupportMessageUi>()
              .ForMember(dest => dest.Votes,
                         opt => opt.ResolveUsing<SupportMessageVotesResolver>())
              .ForMember(dest => dest.UserVoted,
                         opt => opt.ResolveUsing<SupportMessagesUserVotedResolver>()
                                   .ConstructedBy(() => new SupportMessagesUserVotedResolver(userId)));

        var messages = _unitOfWork.MessagesRepository.Get(m => m.Visible);

        var messagesUi = Mapper.Map<List<SupportMessageUi>>(messages);

        return messagesUi;
    }


I'm calling this method on a web service and the problem is: the first time I call the webservice (using the webservice console) it all runs perfectly. For example, if I pass '555' as the userId I get to this method with the correct value:

enter image description here


And in the Custom Resolver the value was correctly passed to the constructor: enter image description here


The results returned are correct. The problem comes next. The second time I call the service, passing a different argument ('666' this time) the argument that gets to the constructor of the Custom Resolver is the old one ('555'). Here's what I mean:

Right before mapping the objects we can see that the value passed to the constructor was correct ('666'): enter image description here


But when it gets to the constructor of the Resolver the value is wrong, and is the old one ('555'): enter image description here


All subsequent calls to the service use the original value in the Custom Resolver constructor ('555'), independently of the value I pass to the service (also happens if I make the call from another browser). If I shut down the server and relaunch it I can pass a new parameter (that will be used in all other calls until I shut it down again).

Any idea on why this is happening?

Answer

sliderhouserules picture sliderhouserules · Mar 7, 2015

It's happening because AutoMapper.CreateMap is a static method, and only needs to be called once. With the CreateMap code in your web method, you're trying to call it every time you call that method on your web service. Since the web server process stays alive between calls (unless you restart it, like you said) then the static mappings stay in place. Hence, the necessity of calling AutoMapper.Reset, as you said in your answer.

But it's recommended that you put your mapping creation in AppStart or Global or a static constructor or whatever, so you only call it once. There are ways to call Map that allow you to pass in values, so you don't need to try to finesse things with the constructor of your ValueResolver.

Here's an example using a ValueResolver (note the change to implementing IValueResolver instead of inheriting ValueResolver<TSource, TDestination>):

[Test]
public void ValueTranslator_ExtraMapParameters()
{
    const int multiplier = 2;
    ValueTranslator translator = new ValueTranslator();
    Mapper.AssertConfigurationIsValid();

    ValueSource source = new ValueSource { Value = 4 };
    ValueDest dest = translator.Translate(source, multiplier);
    Assert.That(dest.Value, Is.EqualTo(8));

    source = new ValueSource { Value = 5 };
    dest = translator.Translate(source, multiplier);
    Assert.That(dest.Value, Is.EqualTo(10));
}

private class ValueTranslator
{
    static ValueTranslator()
    {
        Mapper.CreateMap<ValueSource, ValueDest>()
            .ForMember(dest => dest.Value, opt => opt.ResolveUsing<ValueResolver>().FromMember(src => src.Value));
    }

    public ValueDest Translate(ValueSource source, int multiplier)
    {
        return Mapper.Map<ValueDest>(source, opt => opt.Items.Add("multiplier", multiplier));
    }

    private class ValueResolver : IValueResolver
    {
        public ResolutionResult Resolve(ResolutionResult source)
        {
            return source.New((int)source.Value * (int)source.Context.Options.Items["multiplier"]);
        }
    }
}

private class ValueSource { public int Value { get; set; } }
private class ValueDest { public int Value { get; set; } }

And here's an example using a TypeConverter:

[Test]
public void TypeTranslator_ExtraMapParameters()
{
    const int multiplier = 3;
    TypeTranslator translator = new TypeTranslator();
    Mapper.AssertConfigurationIsValid();

    TypeSource source = new TypeSource { Value = 10 };
    TypeDest dest = translator.Translate(source, multiplier);
    Assert.That(dest.Value, Is.EqualTo(30));

    source = new TypeSource { Value = 15 };
    dest = translator.Translate(source, multiplier);
    Assert.That(dest.Value, Is.EqualTo(45));
}

private class TypeTranslator
{
    static TypeTranslator()
    {
        Mapper.CreateMap<TypeSource, TypeDest>()
            .ConvertUsing<TypeConverter>();
    }

    public TypeDest Translate(TypeSource source, int multiplier)
    {
        return Mapper.Map<TypeDest>(source, opt => opt.Items.Add("multiplier", multiplier));
    }

    private class TypeConverter : ITypeConverter<TypeSource, TypeDest>
    {
        public TypeDest Convert(ResolutionContext context)
        {
            TypeSource source = (TypeSource)context.SourceValue;
            int multiplier = (int)context.Options.Items["multiplier"];

            return new TypeDest { Value = source.Value * multiplier };
        }
    }
}

private class TypeSource { public int Value { get; set; } }
private class TypeDest { public int Value { get; set; } }