Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.net MVC - Separate ViewModel for POST Action

In my MVC application I have a View Model that looks similar to this:

public class ComplexViewModel
{
    public ComplexDetailsViewModel Details1 { get; set; }
    public ComplexDetailsViewModel Details2 { get; set; }
}

public class ComplexDetailsViewModel 
{
    public int Id { get; set; }
    public string DisplayValue1 { get; set; }
    public string DisplayValue2 { get; set; }
    // ...
}

I was originally doing the following in my view:

@Html.HiddenFor(model => model.Details1.Id)
@Html.HiddenFor(model => model.Details2.Id)

@Html.DisplayFor(model => model.Details1.DisplayValue1)
...

I would POST the full model to the controller:

 public ActionResult Post(ComplexViewModel model)

I don't actually need anything from ComplexViewModel except for the Id values, so I decided to create another view model used specifically for POSTing the data:

public class PostViewModel
{
    public int Details1Id { get; set; }
    public int Details2Id { get; set; }   
}

public ActionResult Post(PostViewModel model)

The problem is that now my @HiddenFor(model => model.Details1.Id) does not map to my POST model so nothing actually gets POSTed.

Is there a way to have the separate structure for my POST model and my GET model while still using the HiddenFor helper?

like image 544
Dismissile Avatar asked May 05 '26 23:05

Dismissile


2 Answers

Just because you don't use all of the data in the POST version doesn't mean you have to make another model. Why not keep it simple?

This is how it should work:

Your post details view should be strongly typed to a specific view model. Then in your controller you have two action results named Post for instance, one is decorated with the [HTTPGET] attribute, and the action you want to post to is decorated with the [HTTPPOST] attribute. Additionally, your get method should take a parameter such as post id, and your post method should take the model as parameter.

To enforce server side validation correctly you can decorate your class properties like so:

public class ComplexDetailsViewModel 
{
    [Required]//Works for just the Id property
    public int Id { get; set; }
    public string DisplayValue1 { get; set; }
    public string DisplayValue2 { get; set; }
    // ...
}

Now in your controller you can use this bool: ModelState.IsValid. Basically if they had JavaScript turned off and the model was posted with no Id then the model would be invalid.

This pattern is extremely powerful, and makes client side and server side validation quick to implement. And of course client side validation uses jQuery out of the box so we can easily extend the validators. You can even do AJAX validation very quickly. When I build my forms I don't sacrifice anywhere when it comes to validation.. as it takes no time all to do it correctly.

To answer your original question:

A view can only be strongly typed to one model. You can't load the view with one model, and post it with another (As far as I know). I think if you're trying to do that, your problem is in the way you've built your model.

like image 184
The Muffin Man Avatar answered May 08 '26 11:05

The Muffin Man


Just write the HTML for the hidden inputs by hand instead of using the Html Helpers.

<input type="hidden" id="Details1Id" value="@Model.Details1.Id"/>
<input type="hidden" id="Details2Id" value="@Model.Details1.Id"/>

Update

I had issues doing something similar. I ended up flattening out the form related properties on my views. Automapper makes it really easy to map from other objects to your view and can flatten out hierarchies. Doing this, your new view might end up looking similar to this.

public class ComplexViewModel        
{        
    public long Details1Id { get; set; }        
    public string Details1Name { get; set; }
    public long Details2Id { get; set; }    
    public string Details2Name { get; set; }    
}  
like image 33
Brian Cauthon Avatar answered May 08 '26 12:05

Brian Cauthon