Search code examples
ajaxdynamicasp.net-mvc-4automappergeneric-collections

AutoMapper UseValue, ResolveUsing or MapFrom reusing value from previous call. Is it caching or something?


I have a situation where starting from the 2nd call and subsequent ones (Ajax GET calls), AutoMapper is reusing the previous value (the value from the 1st call that comes from a click in an action link). It's like a "caching" problem...

public virtual ActionResult List(int assessmentId, int? chapterId, bool? isMenuClick)
{

    Mapper.CreateMap<Element, AssessmentQuestionViewModel>().
    ForMember(dest => dest.AssessmentId, opt => opt.MapFrom(e => assessmentId));

    ...

}

It doesn't matter if I use UseValue, ResolveUsing or MapFrom in the above opt => lambda. The behavior is the same, that is, it reuses the value from previous calls.

AssessmentId property does not exist in the source type ( Element ). This way I try to assign AssessmentId a value that "may" change dynamically during subsequent calls to the method where I have this code. assessmentId is a parameter in my ASP.NET MVC action method as shown above in the method signature.

Then I call this code in the List action method:

var questions =
    Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>
     (Database.Elements.Where(e => !elementIds.Contains(e.ElementId) &&
                              e.Standard.ChapterId == chapterId));

The first time, questions is OK, that is, all AssessmentQuestionViewModel objects have the AssessmentId property set correctly as per the CreateMap defined.

Starting from the 2nd call, it reuses the assessmentId from the 1st call and it messes up with my business logic because I expect it to map AssessmentId to the updated assessmentId that's being passed as a parameter to the List method.

Just to be sure: I've set a breakpoint in the code and I can see that the value of the assessmentId parameter is correct. It's just the returned mapped objects questions that have the wrong value in the AssessmentId property - a value that differs from the current assessmentId value. The values should be equal as I understand it since I'm asking AutoMapper to do the mapping using that current value.

I have AutoMapper 2.2.1-ci9000 (Prerelease), but I tested this with the previous version and I saw this same behavior. I updated to the Prerelease thinking that this "misbehavior" would go away.

I think this is a bug. Please correct me if I'm wrong or if I'm trying to use it in a way not supported. :)


Solution

  • I think the problem here your trying to create multiple mappings of the same type - which AutoMapper doesn't support. Everytime your List action is called, you create a new mapping (which has a different ForMember(...) clause). AutoMapper won't throw an exception it just ignores the duplicate mapping so what you are seeing here isn't a bug, it's expected behaviour.

    ForMember is infact called on every map, however, you have a scoping issue here as your variable is hard-coded into the expression. As a work-around you could do something like:

    public class MyController
    {
        public MyController()
        {
            // define mapping once, but make assessment expression dynamic
            Mapper.CreateMap<Element, AssessmentQuestionViewModel>().
                ForMember(dest => dest.AssessmentId, opt => opt.MapFrom(e => GetCurrentAssessmentId()));
        }
    
        private int GetCurrentAssessmentId()
        {
            return (int)TempData["AssessmentId"];
        }
    
        public ActionResult List(int assessmentId, ...)
        {
            // store current assessment temporarily
            TempData.Add("AssessmentId", assessmentId);
            // execute mapping
            var questions = Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>
     (Database.Elements.Where(e => !elementIds.Contains(e.ElementId) &&
                              e.Standard.ChapterId == chapterId));
        }
    }
    

    I will say though, your jumping through a lot of hoops for this to work, it would be much simpler to manually set the property without the help of AutoMapper e.g.

    var questions = Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>(...);
    foreach (var q in questions)
    {
        q.AssessmentId = assessmentId;
    }