views:

484

answers:

2

I have the following hook in my Global.aspx

 protected void Application_Start()
 {
  AreaRegistration.RegisterAllAreas();
  RegisterRoutes(RouteTable.Routes);
  AutoMapper.Mapper.CreateMap<FormCollection, Models.IAmACustomer>().ForAllMembers(form => form.ResolveUsing<Models.FormCollectionValueResolver<Models.IAmACustomer>>());
 }

In My controller:

[HttpPost]
 public ActionResult Create(FormCollection formCollection)
 {

  var customer = AutoMapper.Mapper.Map<FormCollection,Models.IAmACustomer> (formCollection,null);
    }

This line executes but my custom resolver is never called.

The resolver looks like this:

public class FormCollectionValueResolver<TDestination>:ValueResolver<FormCollection,TDestination>
{
//Code removed for brevity
}

The application compiles and runs, however without the custom resolver, nothing comes into the object, it just creates a mock object with exception throwing get accessors.

A: 

The reason the FormCollectionValueResolver<Customer> never gets called is that the ForAllMembers() method iterates over all your property mappings, as defined by the ForMember() method, applying the specified member options. However, in the code sample you supplied no property mappings have been defined, thus the resolver never gets called.

Here is an example of how the ForAllMembers() method could be used.

[Test]
public void AutoMapperForAllMembersTest()
{
    Mapper.CreateMap<Source, Destination>()
        .ForMember(dest => dest.Sum, 
            opt => opt.ResolveUsing<AdditionResolver>())
        .ForMember(dest => dest.Difference,
            opt => opt.ResolveUsing<SubtractionResolver>())
        .ForAllMembers(opt => opt.AddFormatter<CustomerFormatter>());

    Source source = new Source();
    source.Expression = new Expression
    {
        LeftHandSide = 2,
        RightHandSide = 1
    };

    Destination destination = Mapper.Map<Source, Destination>(source);
    Assert.That(destination.Sum, Is.EqualTo("*3*"));
    Assert.That(destination.Difference, Is.EqualTo("*1*"));
}    

public class Expression
{
    public int LeftHandSide { get; set; }

    public int RightHandSide { get; set; }
}

public class Source
{
    public Expression Expression { get; set; }
}

public class Destination
{
    public string Sum { get; set; }

    public string Difference { get; set; }
}

public class AdditionResolver : ValueResolver<Source, int>
{
    protected override int ResolveCore(Source source)
    {
        Expression expression = source.Expression;
        return expression.LeftHandSide + expression.RightHandSide;
    }
}

public class SubtractionResolver : ValueResolver<Source, int>
{
    protected override int ResolveCore(Source source)
    {
        Expression expression = source.Expression;
        return expression.LeftHandSide - expression.RightHandSide;
    }
}

public class CustomerFormatter : IValueFormatter
{
    public string FormatValue(ResolutionContext context)
    {
        return string.Format("*{0}*", context.SourceValue);
    }
}
mrydengren
A type converter doesn't get around the manual name mapping for every business object I use in every project that I'm hoping automapper will if I can figure out how to use it for this. I'm not seeing what the advantage is to do the conversion in the modelbinder rather than the controller? Also this answer doesn't appear to solve any of the issues if validation or conversion has issues like invalid user input that the customer object would throw exceptions for.
Maslow
I was a bit hasty in my original answer. I did a complete edit of it. Hopefully my new answer will be a bit more helpful.
mrydengren
So If understand this answer correctly, the `ForAllMembers()` only applies to custom defined `.ForMember` properties? Is there a way to define a generic here's where the property names you need are without explictly defining each one?
Maslow
A: 

You should consider ditching FormCollection altogether:

http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx

Basically, you'll lean on strongly-typed views + custom-created ViewModel types for forms. These forms have things like validation attributes on them so you can run them through validation frameworks. If it's valid, only then do you update your persistence model from the posted form. We stay away from creating domain objects directly from the posted form.

Jimmy Bogard
If I can get the automapper to do what I want, I can circumvent having to design custom-created ViewModelTypes for forms.
Maslow
Then I suggest a custom IObjectMapper - it'll offer more flexibility for you and a cleaner hook. If you get it working, I have no problem at all adding it to AM.
Jimmy Bogard
I could not find my way through designing a custom IObjectMapper, any pointers or ideas of how I might start that? I tried a few times and could not figure out how that class worked or would be used for this purpose.
Maslow