12
votes

Been having some real issues with automapper. I think I have found the solution but unsure of how to implement it.

basically I am using a custom mapping with ResolveUsing and ConstructedBy to pass in params to the constructor, I understand that most people set this up in the global.asax once and forget about it.

But the problem is that my method (on a wcf) passes in different params to the constructor of a ResolveUsing ......

Before I was using the Mapper.CreateMap and Mapper.Map which are static methods and it appears that when different petitions come into the wcf service via methods (multi -user) they are conflicting with each other.

After reading something it appears I can use the instance version of CreateMap and Map so that each individual petition gets its own map and can pass in its own params.

But I can't seem to find how to do it. Can anyone explain please? I am really stuck...

Before now and again I would get duplicate key errors and also I put in a log trace on the constructor and it appears that 1 petition is overwriting the other - hence the static versions of Mapper.

Well I hope I am correct, but I can't find anything else...

EDITED - AN EXAMPLE OF WHAT I HAVE

Basically all mapping is working as it should, as I am using MapFrom in most cases.

Then I create an instance of my Resolver which I pass in a URL. I have checked the url before I pass it in and its correct. But once it returns it returns the wrong URL.

The reason I need pass in the URL is that it has variables in there so I need to replaced the variables... Basically there are 2 urls depending on the office and I have logs everywhere and I can see what I am passing in but once I pass it in - it isn't the one I passed in, if that makes sense, this is weird!!

Its a WCF service and a client has called the method twice passing in 2 different offices hence 2 different URLs. But they always return the same URL. It's like one session is overwriting the other...

I hope this makes sense.

  SalesPointResolver newSalesPointResolver = new SalesPointResolver(returnReservationUrl, reservationSite.ReservationUrl, startDate, endDate, officeCode);


        Mapper.CreateMap<Models.Custom.House, DTO.House>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
            .ForMember(dest => dest.TaxIncluded,
                       opt => opt.MapFrom(src => src.Segments.FirstOrDefault().TaxIncluded))
            .ForMember(dest => dest.TaxPercentage,
                       opt => opt.MapFrom(src => src.Segments.FirstOrDefault().TaxPercentage))

            .ForMember(dest => dest.SalesPoints,
                       opt =>
                       opt.ResolveUsing(newSalesPointResolver))
            ;

FOUND OUT WHERE IS FAILING - BUT UNKNOWN WHY

See my comments inline with code. In the constructor the urlTemplate arrives, I save it in a private var and then in the overridden ResolveCore it's something else :-)

I have placed some log4net logs on there, so I can see whats happening.

[Log]
public class SalesPointResolver : ValueResolver<Models.Custom.House, IList<DTO.SalesPoint>>
{
    private readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    private string urlTemplate;

    public SalesPointResolver (bool returnReservationUrl, string urlTemplate, DateTime startDate, DateTime endDate, string officeCode)
    {
        this.urlTemplate = urlTemplate;

        log.Error("passed in " + urlTemplate); // THIS IS PERFECT
        log.Error("I am now " + this.urlTemplate); // THIS IS PERFECT
    }

    protected override IList<DTO.SalesPoint> ResolveCore(House source)
    {
        this.house = source;

        log.Error("in  resolveCore :" + this.urlTemplate); // THIS IS RETURNING THE WRONG VALUE

TEMPORARY SOLUTION

I have done a temporary solution but it's really bad. I am sure automapper can do what I am trying, but I am obviously doing something wrong.

Basically I return via LINQ a collection of records (THIS IS MY SOURCE) so I entered a new field on every record that has the correct URL template on there. And then, instead of passing in (via constructor) the url template, I have it available as a property on EVERY record on the collection (THE SOURCE) ... and it works perfect.

Of course, this really is patch and not ideal but it gets me running.

Where am I going wrong?

5
In your example is it that you do not know the source until runtime but you know what target you are mapping to at compile time?Michael Mann
No i know the source ... but i am passing in variables to ResolveUsing using constructor hence the map needs to be create everytime and needs not to be shared by any other session etcmark smith
If this is a WCF service it is running in its own app domain so the maps will not be shared with any other process. It sounds like the arguments to ResolveUsing vary, but ResolveUsing takes the source type typically. What is the reasoning for you to pass in arguments into the constructor of your custom Value Resolver that are outside of your source type?Michael Mann
Hi thank you for getting back to me, well for example i am passing in RentalDays (number of rental days) as its not in the source as I need to use this in my ResolveUsing.mark smith
The other type i am doing is passing in a List to a resolver to return a list to the src? why? because the list is a collection of strings but the strings contain variables .. so my resolveusing replaces the variables with real values and rebuilds the List to return it to the source..mark smith

5 Answers

34
votes

Yes, there is a way to use an instance version of AutoMapper.

Instead of...

Mapper.CreateMap<Dto.Ticket, Entities.Ticket>()

you can use:

var configurationStore =
    new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var mapper = new MappingEngine(configurationStore);
configurationStore.CreateMap<Dto.Ticket, Entities.Ticket>()
13
votes

In response to Luke Woodwards's comment on the newer syntax:

ConfigurationStore store 
   = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
store.AssertConfigurationIsValid();
MappingEngine engine = new MappingEngine(store);

//add mappings via Profiles or CreateMap
store.AddProfile<MyAutoMapperProfile>();
store.CreateMap<Dto.Ticket, Entities.Ticket>();
2
votes

Well it appears that my question is abandoned but after quite a while playing around I finally found a GOOD fix..

basically i was inside a Resolve and i had another MAP which one of the properties called another ResolveUsing ...

It appears there seems to be an issue with this. Another weird thing is that it failed everytime the application pool was started or recycled.. Hence it failed the first time and then was ok until the recycle happened (i am using a wcf app).

So i replaced the second Mapping with with a foreach and did my mapping like that inside my original Resolve ...

I have put the answer here in case it can help anybody else in the future..

I was using the Mapper static methods to do my mappings, these were not in global.asax as i need to pass different things depending on certain factors..

I always wondered if it would be possible to do it with Instance versions of mappper, i though it existed..... but never found out..

But anyway all is working 100% now...

1
votes

Have you looked at using the Map call that takes in the destination object?

var bar = new Bar("Custom each call");

Mapper.Map(foo, bar);

1
votes

If you want to use an instanced version of the Mapper in Automapper, then I think you can use the MappingEngine class. I believe the static Mapper class instantiates and configures an MappingEngine object to do all of the nitty gritty mapping work.

Here's an example of applying IoC to Automapper (which requires instantiation of the MappingEngine)

http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/05/11/automapper-and-ioc.aspx