AutoMapViewResult with complex view models

372 views
Skip to first unread message

Ben

unread,
Jul 5, 2011, 5:17:13 PM7/5/11
to AutoMapper-users
After watching Jimmy's "Put your controllers on a diet" presentation
I've been trying out AutoMapViewResult.

Whilst this works fine for a simple 1 to 1 mapping e.g.

return AutoMapView<ConferenceShowModel>(View(eventName));

I'm curious how people are handling models that require additional
information, say a list of SelectListItem for a drop down list.

Previously we may have done something like this:

public ActionResult Details(Guid id) {
var product = service.GetProduct(id);
var model = mapper.Map<Product, ProductDetailsModel>(product);
model.CategoryList = new SelectList(service.GetCategories(), "Id",
"Name");
return View(model)
}

This would change to:

public ActionResult Details(Guid id) {
var product = service.GetProduct(id);
return AutoMapView<ProductDetailsModel>(View(product));
}

but of course, now we're not setting our select list property.

One solution I came up with was to create an IModelEnricher<TModel>
interface so that I could define classes that would "enrich" the model
within the AutoMapViewResult. However, since this is a fairly common
scenario I'm curious how others are doing it.

Also, whilst not directly related to AutoMapper, in the same
presentation Jimmy demonstrates user of IFormHandler for posts. Same
question remains, as when ModelState is not valid, we would need to
rebind any drop down lists.

Mathias Stenbom

unread,
Jul 6, 2011, 10:17:03 AM7/6/11
to automapp...@googlegroups.com
Im also very curious as to how others are solving these situations. To me this is common to any scenario where you have more than one source mapping to a single destination. Iv tried a few different approaches, and to be honest none which im really satisfied with. Ill post em to see what you think anyway!

One way of doing it is to create a class that contains all the data you need. Following your example, it might look like this:
class MappingSource {
  Product Product;
  IEnumerable<Category> Categories;
}
And then create a map like so: CreateMap<DomainData, ViewModel>()...
In the controller you can then create your class and fill it with data from your services, and then map from that object.

But then the next step might be to create a maps like these aswell:
CreateMap<Product, DomainData>().. and
CreateMap<Product, ViewModel>()...
And then I end up with a 2 step mapping.

Also, the 3rd step might even be to let your MappingSource have some required services (the category service in this case) as dependencies and let your ioc container resolve that for you.

Samples here are far from complete, but I hope you understand what I mean =)

.Ms






--
You received this message because you are subscribed to the Google Groups "AutoMapper-users" group.
To post to this group, send email to automapp...@googlegroups.com.
To unsubscribe from this group, send email to automapper-use...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/automapper-users?hl=en.


Mathias Stenbom

unread,
Jul 6, 2011, 10:18:24 AM7/6/11
to automapp...@googlegroups.com
EDIT:

Im also very curious as to how others are solving these situations. To me this is common to any scenario where you have more than one source mapping to a single destination. Iv tried a few different approaches, and to be honest none which im really satisfied with. Ill post em to see what you think anyway!

One way of doing it is to create a class that contains all the data you need. Following your example, it might look like this:
class MappingSource {
  Product Product;
  IEnumerable<Category> Categories;
}
And then create a map like so: CreateMap<MappingSource, ViewModel>()...
In the controller you can then create your class and fill it with data from your services, and then map from that object.

But then the next step might be to create a maps like these aswell:
CreateMap<Product, MappingSource>().. and
CreateMap<Product, ViewModel>()...
And then I end up with a 2 step mapping.

Also, the 3rd step might even be to let your MappingSource have some required services (the category service in this case) as dependencies and let your ioc container resolve that for you.

Samples here are far from complete, but I hope you understand what I mean =)

Jimmy Bogard

unread,
Jul 6, 2011, 10:43:29 PM7/6/11
to automapp...@googlegroups.com
Yes, none of these options is very great.

We have a few ways of doing this, depending on how the select list varies. If it's a hard list of items, then often there's some model behind it (like State or Country), so our view model just has that one type and our editor templates takes care of actually pulling the list of states.

If it varies based on some parameter, we have to pass the actual select list down, and basically do a Mapper.Map + post-map manipulation (not in AutoMapper) to fill in the pieces. We use AutoMapper to fill in the major pieces, and the details w/ something else.

Finally, we might have something like a select list provider-type approach in our view:

Html.InputFor(m => m.UserRoles, opt => opt.SelectListProvider<UserBasedRolesProvider>());

HTH,

Jimmy

Mathias Stenbom

unread,
Jul 7, 2011, 4:04:24 AM7/7/11
to automapp...@googlegroups.com
... to continue:

Another approach we'v also used is to inject services into the automapper profile. Following the original example, something like this:

class ProductProfile : Profile {
    public ProductProfile(ICategoryService service) { .... }
    protected override void Configure() {
        CreateMap<Product, ViewModel>()
            .ForMember(d => d.CategoryList, c => c.MapFrom(s => service.GetCategoryList());
    }
}

And this use the automapper configuration option ConstructServicesUsing(...) to instruct automapper to resolve our profiles using the ioc container.

The drawback here is that the mappings always gets a single instance of the service, and our ioc container cant dictate its lifetime. This problem we'v solved 2 different ways, one way is to put the services in the MappingSource as described earlier, and another way it so do it like this:

class ProductProfile : Profile {
    public ProductProfile(IMappingDependencyResolver resolver) { .... }
    protected override void Configure() {
        CreateMap<Product, ViewModel>()
            .ForMember(d => d.CategoryList, c => c.MapFrom(s => resolver.Resolve<ICategoryService>().GetCategoryList());
    }
}

And this approach has the drawback that we'r creating some hidden dependencies, and I would prefer to have all my dependencies in the constructor. That may imply that using a MappingSource class with services in its contructor might not be such a bad idea, because although that approach has the drawback of the mapping taking place in 2 steps, it doesnt have these 2 drawbacks described here.

Anyway, trying and using all of these approaches for all those special cases (special cases that I think is getting more and more common I might add) makes me question the use of automapper here in the first place. But I really like automapper, and I think that sometimes when the mapping is so complex you almost miss out of the "auto" part, it still makes more declarative style code than say writing a very complex ViewModelFactory.

.Ms

Jimmy Bogard

unread,
Jul 7, 2011, 8:12:47 AM7/7/11
to automapp...@googlegroups.com
I think this could be one of those cases where you don't use AutoMapper for that one piece, and have it filled in later. How would that affect things?

Thanks,

Jimmy

ulu

unread,
Jul 7, 2011, 4:22:13 PM7/7/11
to AutoMapper-users
I always retrieve things like CategoryList in a separate Action,
calling RenderAction from inside my View.

The point is, your Product is "what", your CategoryList is "how". This
is an orthogonal concern: the list of categories is something
completely independent of the Product class, hence it should be
handled elsewhere IMO.

On Jul 6, 6:17 pm, Mathias Stenbom <math...@stenbom.com> wrote:
> Im also very curious as to how others are solving these situations. To me
> this is common to any scenario where you have more than one source mapping
> to a single destination. Iv tried a few different approaches, and to be
> honest *none which im really satisfied with*. Ill post em to see what you

Ben

unread,
Jul 7, 2011, 7:11:36 PM7/7/11
to AutoMapper-users
I've also tested injecting the services directly into the AutoMapper
profiles. You can actually get round the hard dependency on your
service locator by injecting in a Func<YourService>. However, an
additional downside of this approach is when you simply return the
view when modelstate is invalid (as we need to rebind any select
lists).

I quickly found that using the AutoMapView result doesn't really work
for our read models. Specifically, if we have a "list" model, it's
unlikely we will bind our view directly to the list. We tend to have
models like:

public class ProductListModel {
public IList<ProductModel> Products {get;set;}
public Customer CurrentCustomer {get;set;}

public class ProductListModel.ProductModel {
public string Name {get;set;}
}
}

So we can't simply do return AutoMapView<Product,
ProductListModel>(...);

instead we end up with something like:

var model = new ProductListModel {
Products = Mapper.Map<...>,
Customer = context.CurrentCustomer
};

But for Create/Edit models I think the AutoMapView is a nice approach.
After a few days of using it, I'm quite happy with my IModelEnricher
approach.

So let's say we have a model like:

public class Product {
public string Name {get;set;}
public string Category {get;set;}
}

public class ProductEditModel {
public string Name {get;set;}
public string Category {get;set;}
public IEnumerable<SelectList> Categories {get;set;}
}

When we call AutoMapView<Product, ProductEditModel> AutoMapper will
map our Name and Category properties. But we need to also pass our
list of categories.

To do this we just create a model enricher:

public interface IModelEnricher<TModel> {
TModel Enrich(TModel model);
}

public class ProductEditModelEnricher :
IModelEnricher<ProductEditModel> {
public ProductEditModel Enrich(ProductEditModel model) {
model.Categories = from c in categoryService.GetCategories()
select new SelectListItem { Text = c.Title, Value = c.Title }
}
}

The nice thing about this approach is we typically need to perform
this "enriching" both on the initial GET and on a failure (when
ModelState is invalid). By adapting our AutoMapView and our FormView
slightly we can enrich our model before passing it on:

var enricher = ObjectFactory.TryGetInstance<IModelEnricher<T>>();
if (enricher != null) {
form = enricher.Enrich(form);
}

You'll need to call this before returning your FailureResult within
FormViewResult and just before returning your View in
AutoMapViewResult.

I don't particularly love that I'm directly calling ObjectFactory, but
I prefer it to having to doing this within my controller action.
Perhaps there's a way of injecting dependencies into an ActionResult
which will make this approach a bit cleaner?
> > On Wed, Jul 6, 2011 at 9:18 AM, Mathias Stenbom <math...@stenbom.com>wrote:
>
> >> EDIT:
>
> >> Im also very curious as to how others are solving these situations. To me
> >> this is common to any scenario where you have more than one source mapping
> >> to a single destination. Iv tried a few different approaches, and to be
> >> honest *none which im really satisfied with*. Ill post em to see what you
> >> think anyway!
>
> >> One way of doing it is to create a class that contains all the data you
> >> need. Following your example, it might look like this:
> >> class MappingSource {
> >>   Product Product;
> >>   IEnumerable<Category> Categories;
> >> }
> >> And then create a map like so: CreateMap<MappingSource, ViewModel>()...
> >> In the controller you can then create your class and fill it with data
> >> from your services, and then map from that object.
>
> >> But then the next step might be to create a maps like these aswell:
> >> CreateMap<Product, MappingSource>().. and
> >> CreateMap<Product, ViewModel>()...
> >> And then I end up with a 2 step mapping.
>
> >> Also, the 3rd step might even be to let your MappingSource have some
> >> required services (the category service in this case) as dependencies and
> >> let your ioc container resolve that for you.
>
> >> Samples here are far from complete, but I hope you understand what I mean
> >> =)
>
> >> On Wed, Jul 6, 2011 at 4:17 PM, Mathias Stenbom <math...@stenbom.com>wrote:
>
> >>> Im also very curious as to how others are solving these situations. To me
> >>> this is common to any scenario where you have more than one source mapping
> >>> to a single destination. Iv tried a few different approaches, and to be
> >>> honest *none which im really satisfied with*. Ill post em to see what

Jimmy Bogard

unread,
Jul 7, 2011, 9:58:36 PM7/7/11
to automapp...@googlegroups.com
Injecting dependencies into action results, now where have I seen that before...


;)

Ben

unread,
Jul 11, 2011, 3:38:51 PM7/11/11
to AutoMapper-users
Okay, fair enough. Should have googled :p To be honest, I can probably
live with a couple of direct refs to StructureMap

On Jul 8, 2:58 am, Jimmy Bogard <jimmy.bog...@gmail.com> wrote:
> Injecting dependencies into action results, now where have I seen that
> before...
>
> http://lostechies.com/jimmybogard/2009/12/12/enabling-ioc-in-asp-net-...
> ...
>
> read more »

Jimmy Bogard

unread,
Jul 11, 2011, 8:06:27 PM7/11/11
to automapp...@googlegroups.com
lol yeah, that solution was quite complex, and was in our case for some action results doing some really complex compositional behavior. We haven't gone that far in any project since.


--

cortex

unread,
Jul 15, 2011, 5:22:53 PM7/15/11
to AutoMapper-users
I use ActionFilter and ViewBag. This allows me to have custom
EditorTemplates that are not aware of the model. If i change from a
dropdown list to combobox with autocomplete, my view model and action
do not change.
I agree with ulu that this is an orthogonal concern and that trying to
fit everything in a single viewmodel is maybe not the one fit-all
solution.
I have the same problem for some parts of the page that are not
related to current action (e.g. page title, meta tags, footer text)
for which calling a RenderAction seems overkill.
> ...
>
> plus de détails »

ProfK

unread,
Jul 17, 2011, 6:57:07 PM7/17/11
to AutoMapper-users
On Jul 15, 11:22 pm, cortex <corte...@gmail.com> wrote:
> I use ActionFilter and ViewBag.
> ....
> I have the same problem for some parts of the page that are not
> related to current action (e.g. page title, meta tags, footer text)
> for which calling a RenderAction seems overkill.

That's where ViewBag really shines, in passing values, from umpteen
nestings of view, up to the master layout.

Jimmy Bogard

unread,
Sep 12, 2012, 1:20:17 PM9/12/12
to automapp...@googlegroups.com
Yeah, it's basically like that. We can also go off the fact that the destination type is something like Entity[] and use a repository to pull them all out, too.

Does that make sense?

On Tue, Aug 28, 2012 at 9:47 AM, Justin Michaels <justin.bli...@gmail.com> wrote:
Jimmy,

I'm going to start by saying your work on AutoMapper and your blog is just incredible.  I've just watched your video on put your controllers on a diet and of course my feeble brain has questions.  After a couple of google searches I was led to this google group.

I'm probably beating a dead horse here but when you say your EditorTemplate is in charge of pulling the list of states, does that mean you have some code like DependencyResolver.Current.GetService<ICountryService>().GetAllCountries(); to retrieve your states inside of your editor template?  I can see using a custom ActionResult in the scenario where we have parameters to pass in order to retrieve the correct items from a database but you mentioned using a select list provider-type approach which I am not familiar with.  

There's a thousand ways to skin a cat but this topic has had me stopped dead in my tracks for a couple of hours.  Any guidance or recommendation from you or anyone in the group here would be great.
To view this discussion on the web visit https://groups.google.com/d/msg/automapper-users/-/tu2aVLFMpWcJ.
Reply all
Reply to author
Forward
0 new messages