tags:

views:

59

answers:

3

Consider the following scenario. I have a number of classes that share a common base class and I have defined an automapper mapping for each derived class. Something like this:

class A : Base {}

class B : Base {}

class ContractA : ContractBase {}

class ContractB : ContractBase {}

void Foo()
{
    Mapper.CreateMap<A, ContractA>();
    Mapper.CreateMap<B, ContractB>();
}

So far so good. But now I want to create a method like this:

ContractBase Foo()
{
    Base obj = GetObject();

    return Mapper.???
}

The problem is that all of AutoMapper's Map variants require that I either know the destination type at compile time or have an object of that type available at runtime. This is seriously frustrating since I have defined only one map for each source type. AutoMapper should be able to infer the destination type given only the source type.

Is there any good way around this? I want to avoid creating a dictionary mapping source types to destination types. While this would work, it would mean that I'd essentially have to define two mappings for every source type.

+1  A: 

I think Mapper.DynamicMap() and its various overloads are what you're looking for.

Josh Kodroff
It seems these methods simply map one object onto another (instead of returning a new object). They still require the destination type.
Peter Ruderman
+1  A: 

You can turn it around and ask Base to give you a mapped contract:

ContractBase Foo() {
  Base obj = GetObject();
  return obj.ToContract();
}

With this code:

abstract class Base {
  public abstract ContractBase ToContract();
}
class A : Base {
  public override ContractBase ToContract() {
    return Mapper.Map<A, ContractA>(this);
  }
}
class B : Base {
  public override ContractBase ToContract() {
    return Mapper.Map<B, ContractB>(this);
  }
}

UPDATE: if you must separate the logic from the classes, you could use a visitor:

ContractBase Foo() {
  Base obj = GetObject();
  var visitor = new MapToContractVisitor();
  obj.Accept(visitor);
  return visitor.Contract;
}

This is what it looks like:

abstract class Base {
  public abstract void Accept(IBaseVisitor visitor);
}
class A : Base {
  public override void Accept(IBaseVisitor visitor) {
    visitor.Visit(this);
  }
}
class B : Base {
  public override void Accept(IBaseVisitor visitor) {
    visitor.Visit(this);
  }
}
interface IBaseVisitor {
  void Visit(A a);
  void Visit(B b);
}
class MapToContractVisitor : IBaseVisitor {
  public ContractBase Contract { get; private set; }
  public void Visit(A a) {
    Contract = Mapper.Map<A, ContractA>(a); 
  }
  public void Visit(B b) {
    Contract = Mapper.Map<B, ContractB>(b);
  }
}

Now, all the mapper logic is in the MapToContractVisitor class, not in the Base hierarchy classes.

Jordão
That's clever, but unfortunately it doesn't work for us. The classes in question represent messages to different systems. They aren't mutually aware of each other.
Peter Ruderman
OK, let me try again....
Jordão
This doesn't really solve the original problem. My issue is not that I see no way to do the mapping, but that I want to avoid duplication of effort. With this solution, I must do two things every time I add a new pair of classes. First, I must define the automapper mapping, then I must create a visitor method. I'm hoping for a way to define _just_ the mapping and nothing else.
Peter Ruderman
There are two things you need to do anyway: `Mapper.CreateMap` and later `Mapper.Map`. This code deals with the `Mapper.Map`. You could also centralize it all in the visitor, and put the `Mapper.CreateMap` in its static constructor, for example. Then, just one class knows about these mappings.
Jordão
I guess I haven't explained this very well. A very common scenario I envision in the future is having to add new pairs of mapped classes. I want to structure things such that, in this scenario, the only line the programmer has to write is Mapper.CreateMap<newClass, newClassContract>().
Peter Ruderman
I see what you mean now. You should somehow access AutoMapper's repository of mappings with the source type to find and map to the destination type. I don't know how to do this. If it's not possible, you can take control of the mappings and create an abstraction on top of AutoMapper that knows that information.
Jordão
I think I know how to do it now, I'll post it in another answer...
Jordão
+1  A: 

You could access the mappings stored in AutoMapper:

ContractBase Foo() {
  Base obj = GetObject();

  var sourceType = obj.GetType();
  var destinationType = Mapper.GetAllTypeMaps().
    Where(map => map.SourceType == sourceType).
    // Note: it's assumed that you only have one mapping for the source type!
    Single(). 
    DestinationType;

  return (ContractBase)Mapper.Map(obj, sourceType, destinationType);
}
Jordão