devermind.com
Adrian Grigore’s software development weblog. Motto: I will not waste my time looking for a clever motto.
  • Home
  • About
  • Contact me
  • Privacy Policy

ASP.NET MVC Tip #1: Using Custom ViewModels with POST action methods

ASP.NET MVC, LINQ Add comments

One of the top good practices for ASP.NET MVC is not to use the ViewData Dictionary, but to put your data in a strongly typed ViewModel instead. Many people seem to be using Linq to SQL entities as a ViewModel, because it’s a very comfortable approach. But what do you do if your view should contain data that is not included in any of your linq entities? Scott Gu’s chapter 1 preview of his upcoming ASP.NET MVC book recommends using a custom-shaped ViewModel for those cases.

One thing that still puzzled me after reading and trying to follow the chapter was how to create complex custom ViewModels (as opposed to putting all data in the ViewData dictionary or using vanilla Linq to SQL entities). Scott mentions on page 107 of the book that you “might have the action method update a ViewModel object with the form-posted data, and then use the ViewModel instance to map or retrieve an actual domain model object”, but there is no actual example of how this would look in source code.

For example, let’s assume you have an edit action method with the following view:

EditForm

So your edit form displays data coming from a Linq entity representing a customer (FirstName, LastName, Email, Country, AccountType), but you also need SelectLists to populate the Country and AccountType DropDownLists.

The simple, but untyped approach: the ViewData dictionary

The simplest approach is to use the Customer Linq entity as ViewModel and to put the two SelectLists in the ViewData dictionary. This works fine, but I dislike the fact that there is no type-safety for the two SelectLists. Instead you have to use a type cast from object to SelectList:

<label for="CountryCode">Country</label><br />
<%= Html.DropDownList("CountryCode",ViewData["Countries"] as SelectList)%>

<%= Html.ValidationMessage("CountryCode", "*")%>

The fully typed approach: Custom ViewModels

So you want fully typed model data, but the linq entity does not hold all the data you need to render the view. Since you don’t want to clutter my Customer Linq to SQL entity with two properties that return the two SelectLists, the only alternative to get a fully typed view is to use a custom ViewModel:

public class CustomersFormViewModel
{
public SelectList AccountTypes { get; set; }
public SelectList Countries { get; set; }
public Customer Customer { get; set; }
}

Note that the ViewModel is not a mere Linq entity anymore. Instead, it contains the one Customer Linq entity instance, plus the two SelectLists for my DropDownLists.

Now you can implement your view without using any typecasts:


<p>

<label for="Customer.FirstName">

FirstName:</label><br />

<%= Html.TextBox("Customer.FirstName")%>

<%= Html.ValidationMessage("Customer.FirstName", "*")%>

</p>

<p>

<label for="Customer.LastName">

LastName:</label><br />

<%= Html.TextBox("Customer.LastName")%>

<%= Html.ValidationMessage("Customer.LastName", "*")%>

</p>

<p>

<label for="Customer.Email">

Email:</label><br />

<%= Html.TextBox("Customer.Email")%>

<%= Html.ValidationMessage("Customer.Email", "*")%>

</p>

<p>

<label for="Customer.CountryCode">

Country</label><br />

<%= Html.DropDownList("Customer.CountryCode",Model.Countries)%>

<%= Html.ValidationMessage("Customer.CountryCode", "*")%>

</p>

<p>

<label for="Customer.AccountType">

AccountType:</label><br />

<%= Html.DropDownList("Customer.AccountType",Model.AccountTypes)%>

<%= Html.ValidationMessage("Customer.AccountType", "*")%>

</p>

I was wondering if the MVC framework can automatically map the form values and reconstruct a CustomersFormViewModel. In other words, can I simply implement a strongly typed POST edit action method and receive a fully populated ViewModel as a parameter like this:

//POST: /CustomersController/Edit

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(CustomersFormViewModel model)
{
//validate data, save customer, handle validation errors...
}

The answer is yes! The MVC framework even maps object hierarchies of any depth as ViewModels.

How not to do it

This is all really easy, but still there were few things that threw me off at the beginning.

The ViewModelBinder only maps properties, not public fields. A ViewModel like this cannot be mapped:

public class CustomersFormViewModel
{
public electList AccountTypes;
public SelectList Countries;
public Customer Customer;
}

The names of your POSTed form values must match the object hiearchy you want them to be mapped to. Something like this will not work:

<p></p></p></p></p></p></p></p></p></p></p>

<label for="FirstName">

FirstName:</label><br />

<%= Html.TextBox("FirstName",Model,Customer,FirstName)%>

<%= Html.ValidationMessage("FirstName", "*")%>

</p>

In hindsight both restrictions are perfectly logic and reasonable, but it’s easy enough to do it wrong nevertheless and also quite difficult to figure out what went wrong if binding does not work. Hopefully this article will help you avoid any problems with using Custom ViewModels with POST action methods.

kick it on DotNetKicks.com


Shout it

Tags: ASP.NET MVC, binding, custom, mvc, POST, viewmodel


April 18th, 2009 |

Tags: ASP.NET MVC, binding, custom, mvc, POST, viewmodel


21 Responses to “ASP.NET MVC Tip #1: Using Custom ViewModels with POST action methods”

  1. Simone
    April 19th, 2009 at 3:45 pm

    Good post, I’d add that you should never use you linq to sql objects in your views, but always convert them to something that is in related to you data access strategy
    This will give you a few more benefits:
    1 – you are not tied to a specific implementation of your dal
    2 – you can have a different hierarchy of objects, instead of being tied to the table approach
    3 – you can just create your objects when you are testing (LINQtoSQL objects are not POCO objects, or, at least, this is difficult to accomplish)

    And then, you rarely have to use the same objects both in your views and in your BL.


  2. Andrew
    April 20th, 2009 at 5:17 am

    Great Post. I too think strongly typing is the way to go… however for this simple example with a select list or two, I don’t necessarily see the benefit of writing more code to handle this.


  3. Anonymous
    June 24th, 2009 at 12:08 am

    I tried the drop down list and the model tries to map it to an enumerable type when I was hoping it would map the selected value to the property name given to the drop down list as in your example.


  4. Philippe
    June 25th, 2009 at 4:07 pm

    Hello Adrian,

    Interesting post. I tried to implement the Custom View Model on my project in a similar context and it does not work. I don’t know what i do wrong.

    If you can help me these are below the lines of code :

    The CustomView Model :
    ————————————————–
    public class MeditationsFormModel : IFormModel
    {
    public Meditation Meditation { get; set; }
    public SelectList Types { get; set; }
    }

    The post action in the controller :
    ———————————————

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult ValiderEdit(MeditationsFormModel m)
    {
    //
    }

    The dropdownlist in the view :
    ——————————————————

    Type

    Thanks.


  5. Adrian Grigore
    June 25th, 2009 at 5:10 pm

    @Anonymous: I’m sorry, but I can’t follow your explanation. Perhaps you could post some code?

    @Philippe: The code you posted looks incomplete. Also, what exactly is going wrong?


  6. BSvanVeen
    August 7th, 2009 at 11:45 pm

    great article, does this stuff also works with a MultiSelectList ?


  7. Adrian Grigore
    August 8th, 2009 at 1:34 am

    @BSvanVeen: I haven’t used MultiSelectList yet, but I’m sure it does. Basically you can put whatever you want into your ViewModel. Even your own custom classes, which could be evaluated by your own custom HtmlHelper extension methods.


  8. asp.net
    August 16th, 2009 at 6:38 pm

    I tried it on a multislect and it worked well thanks


  9. Mike
    September 17th, 2009 at 12:46 am

    All my objects within my model was null also. Tried everything.


  10. X
    October 2nd, 2009 at 10:11 pm

    What if I want to save all options in the Select? I.E. A user drag and drop a bunch of objects into a basket. I need to save all these objects into database in order.


  11. Adrian Grigore
    October 2nd, 2009 at 11:03 pm

    @X: Phil Haack has written an article about this: http://haacked.com/archive/0001/01/01/model-binding-to-a-list.aspx


  12. Al Dass
    February 11th, 2010 at 12:00 am

    FYI:

    That link to Phil’s site in Adrian’s previous post is invalid… here is the correct link:

    “Model Binding To A List”
    http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx


  13. Ben
    February 25th, 2010 at 1:22 pm

    I have found that Html.EditorForModel() doesn’t work in this case; the SelectLists don’t render. If I manually bind the SelectLists using Html.DropdownListFor, it does work. Have you found a way to use EditorForModel() in this case?


  14. Adrian Grigore
    February 25th, 2010 at 1:27 pm

    Sorry, I haven’t had the time to have a look at MVC2’s templates yet. I’ve only just migrated my current MVC project to the MVC 2 codebase this week.


  15. madonionmad
    March 28th, 2010 at 6:00 pm

    To Philippe:
    You should code like this:

    Not

    because the ViewModelBinder will bind the form to a object Customer, not CustomersFormViewModel.


  16. madonionmad
    March 28th, 2010 at 6:03 pm

    To Philippe:
    Sorry, the code i post before invisible
    you should use “FirstName” , not “Custom.FirstName”


  17. JBC
    March 29th, 2010 at 9:10 pm

    @Philippe: It doesnt appear as though youre setting your properties. Try this:

    public class MeditationsFormModel : IFormModel
    {
    public Meditation Meditation { get; private set; }
    public SelectList Types { get; private set; }

    public MeditationsFormModel(Meditation meditation)
    {
    Meditation = meditation;
    Types = new SelectList( ::whatever your IEnumerable type is:: , ::whatever your selected value should be:: );
    }
    }


  18. Omu
    June 11th, 2010 at 9:51 am

    you can use ValueInjecter http://valueinjecter.codeplex.com/ for mapping ViewModel to/from Entities, it’s very good for this purpose


  19. Adrian Grigore
    June 25th, 2010 at 12:33 pm

    Thanks for the hint! So far I’ve been using Automapper for that, it seems quite similar to ValueInjector


  20. Ross McLoughlin
    July 15th, 2010 at 4:28 pm

    @Adrian:

    Nice article. I was stuck on this for a while this morning. Just a quick point regarding your last post. I have looked at automapper, and it does indeed look nice. But does TryUpdateModel(,) in MVC 2 not do the same thing?

    For example, I could easily map a FormCollection passed in as a parameter to an Action and map it to a Entity using this TryUpdateModel.


  21. Adrian Grigore
    July 16th, 2010 at 9:47 am

    @Ross: Yes, but TryUpdateModel will not update a viewmodel from an DTO when constructing the view (GET request).

    Also, I don’t like working with FormCollections for POST requests, because it makes unit testing less intuitive. Even though I’ve writting a helper method that creates a fake http context from a model for unit testing, I still prefer custom ViewModels as POST action method parameters


Leave a Reply

  • Feeds

    • RSS feed iconAll Entries
    • RSS feed iconAll Comments
    • RSS feed iconThis Post's Comments
  • About Adrian Grigore

    Adrian Grigore Adrian is a software developer from Fulda, Germany. Adrian has been programming C++ applications since 1998. Recently he has been implementing a Web 2.0 SaaS website, so his current development-related interests are ASP.NET MVC, C#, and jQuery.


  • Adrian's (German language) book

    XSLT XUpdate BuchXUpdate mittels XSLT - Ein XUpdate-Prozessor auf XSLT-Basis


  • Pages

    • About
    • Contact me
    • Privacy Policy
  • Links

    • Lobstersoft
    • SonicWeasel
  • Tags

    ASP.NET ASP.NET MVC binding Business client-side form validation client-side validation codedui selenium testing test CSharp custom DAL dataannotationsmodelbinder form validation games guide jQuery jQuery.validate LINQ MSBuild multi-tier mvc POST remote validation shareware tutorial viewmodel visual-studio asp.net xVal
  • Archive

    • May 2010 (2)
    • January 2010 (2)
    • June 2009 (1)
    • May 2009 (1)
    • April 2009 (1)
    • March 2009 (2)
    • February 2009 (1)
    • October 2008 (2)
Copyright © 2010 devermind.com All Rights Reserved
RSS XHTML CSS Log in
Wp Theme by n Graphic Design
Powered by Wordpress