New entries in nested ViewModel are not recreated on ModelState.Merge() - asp.net-mvc

We are using the well known Post-Redirect-Get pattern to prevent double posting in our MVC application. Like most implementations we store the Model-State inside TempData in the Http-Post-Action on errors and restore the Model-State with ModelState.Merge() inside the action we are redirected to. This works very well in most cases but breaks in the following case where we are adding new elements to a nested View-Model.
A editor template on the page is refreshed and contains an additional nested view-model element.
In another field on the surrounding view that incorporates the editor template another field is invalid and will prevent the commit into the db and forces the PRG into the error path (redirect to GET).
When the page gets submitted the Model-State contains the new nested element Property[n+1].Value and its value and is stored into TempData. After the Merge() the model-state in filterContext of the redirected to Get-Method contains the additional element.
Now when the view gets rendered the new element does not recreate because it is only in the model-state but not in the model. View creation loops over the model (that doesn't have the new nested object) and the EditorFor<> gets the value from the model-state which is as expected. Is there a generic solution to this problem?

Apparently there is no quick solution to the problem. We switched from a redirect on the error path in the controller (strict PRG) to a implementation described in this blog. It works very well and did even speed up the application as the returned json on the controllers error path is handled much faster than a full ViewResult.

Related

Why are my dotnet 5.0 mvc form fields retaining their input values on post?

I have an dot net 5.0 mvc page which takes a model object (ClassFoo) which when constructed generates an instance of (classBar). ClassBar has various properties (FieldA, FieldB, FieldC) all are strings.
The Views content is all in a form and the form's input fields are all from ClassFoo.ClassBar's various properties. Initially, when the page is accessed, all form input values are empty. However when I put data into them and then submit the form, the form values are still there when the page loads. However I don't understand why this is because I'm explicitly creating a new model during the controller operation but I am not actually populating the Model.ClassBar with the content from the post before I return model to the View for generation.
What I would expect is that all of the form fields would be empty however that is not the case. I know if asp.net the form values are stored and restored automatically but I didn't think that happened in mvc.
After looking into ModelState recommended by Nick Albrech in the comments I reviewed the hint associated w/ the HtmlHelper.TextBoxFor() which states the following:
... Adds a "value" attribute to the element containing the first non-null value found in: the ActionContext.ModelState entry with full name, or the expression evaluated against ViewDataDictionary.Model. See [IHtmlHelper.NameFor] for more information about a "full name".
So effectively what's happening is similar to what I thought asp.net mvc wasn't doing in that it populates the ModelState from a get/post request with the name and values of the form being submitted. Then based on the use of these helper functions (and also asp-for attributes used in razor views views), it either provides values from the saved model state form values OR the model passed to the view. Note: this does not seem to work if you set the value of an input element = #Model.[someProperty]
The take away from this is that you do not necessarily need to update your model object with content from the previous form submit in order to have the page populate the form contents back to the screen. Allow asp.net mvc to do the manual tasks by making use of these razor helpers.
Shoutout to Nick for the assist on this one. A solid member of the stackOverflow community.

How page specific should a viewmodel in MVC be?

I've understood that a viewmodel in MVC is supposed to reflect data on a single page rather than objects in the model. But should the viewmodel correspond to the data you want to show on that page or to the data you want back from that page? If we for example look at a login page then I just want username and password back in the post, but I might need more variables than that when displaying the login page (previous error messages etc).
Should the viewmodel then just contain username and password as parameters and the rest of the variables end up in viewbags. Or should the viewmodel contain all values I want to show even though I'm only interested in a few of them in the response.
What is best practice when using viewmodels?
All data that somehow interacts between the html and your server should be in a ViewModel.
This allows you to perform formatting and such outside your html and inside your ViewModel properties.
However, if your page contains a lot of controls or data, you may want to split it into multiple ViewModels (example one for the Get and one for the Post).
The post model may contain only data that you entered and needs to be validated.
I think it's best to put everything in the view-model. This keeps the code cleaner and makes discovery and maintenance easier as well. The view-model should be your primary mechanism here.
I would say only properties you need, in your case username and password. If you want to display error messages then that's what ModelState is for. You can always append any error messages to your ModelState:
ModelState.AddModelError("PropertyName", "Error Text")
Beyond that let's say you have a form that contains a list of categories that you need to pick one category from a drop down. In this case I usually attach that list to my model even though the only thing being submitted is the actual selected value. But this is a matter of preference, meaning I could also set a ViewBag to contain this SelectList of categories and then bind that to your DropDownList. I suppose it's better to place this in a model because ViewBag is dynamic and you will have to cast anything in the ViewBag into it's underlying type on your views.

Why isnt my EditorTemplate binding a List on [HttpPost], but renders it fine on [HttpGet]?

I have uploaded my code to pastebin, this is the link:
http://pastebin.com/wBu9PP2x
When i submit a form, the Lists that i use are not bound to my ViewModel.
But when i send the ViewModel to the view, it renders fine using EditorFor. I have read that when using EditorTemplates, it is supposed to name the List appropriately so that they are bound to the ViewModel automatically upon postback.
The HTML output can be seen here:
http://pastebin.com/5KeyNXWC
Notice that the ViewModel derives from ShowQuestionViewModel, which contains some strings. These strings get bound perfectly.
This is the tutorial i have been following:
http://jarrettmeyer.com/post/2995732471/nested-collection-models-in-asp-net-mvc-3
In the tutorial, the MVC framework knows how to bind lists inside of a ViewModel.
Here are some debugger outputs:
Controller takes ShowQuestionViewModel as parameter:
http://imageshack.us/photo/my-images/803/debug.jpg
Controller takes FormCollection as parameter:
http://imageshack.us/photo/my-images/542/formcollection.png
Different Controller that takes a List and FormCollection as parameter:
http://imageshack.us/photo/my-images/685/listtest.png
Dont give up on me guys!!
Thanks!
Solution
I have found this solutin myself. I forgot to use Properties for the rows and columns list in the ShowMatrixQuestionViewModel. Also, the ActionController wont bind without TryUpdateModel() so thanks to #Adam Tuliper as well as the rest.
Since you mentioned lists are you sure your model Upon postback contains all of the expected items? Also remembe the HTML helpers will use modelstate to bind data from as well if you are showing data after a post and not redirecting.

Editing collections and disabling form persistance

Is there anyway to disable MVC "refilling" from form data for a particular request?
I'm developing an MVC sample that edits a "shopping cart", which contains of a simple list of items and their quantities. So far, so typical.
I'm using editor templates so that I can use EditorFor on the list of items and MVC will generate the Items[x] field prefix and provide me with basically free model binding on the post back.
Part of the sample is to remove items with a quantity of zero between posts. Unfortunately, since the HTML helper methods prioritise form values over model values, this is resulting in the number of items being reduced but the form data of the previous item that was posted at that position. Non-posted data for the row obviously remains correct.
NB. I realise that one would usually utilise the PRG pattern here, but since this is a sample there is no persistance layer (I'm relying on form data + a static product repository implementation).
Edit: To be clear I'm not arguing against the PRG pattern, the sample is simply highlighting model binders and server side validation.
OK, it seems to be the problem I encountered: Strange behaviour in ASP.NET MVC: removing item from a list in a nested structure always removes the last item
The solution I found was to clear the ModelState and then populate with what you need.
Also PRG is possible, you have to store the model and modelstate temporarily in the session and on the GET, retrieve from session and remove. This is exactly what ASP NET MVC Contrib 2 was doing in ActionFilter PassParametersDuringRedirect.

ASP.NET MVC auto-binds a refreshed model when ModelState is invalid on HttpPost

I'm working on an ASP.NET MVC2 app. I've come to realize a very surprising, yet amazing thing that MVC does behind the scenes having to do with the ModelState and model binding. I have a ViewModel which has a whole bunch of data - some fields being part of a form while others are simply part of the UI. On HttpPost, my Action method uses the DefaultModelBinder which attempts to bind the whole model, but only fields which were part of the form are successfully deserialized - all others remain null. That's fine and understandable. If the ModelState is invalid, I need to refresh the model from the db and bind those particular form fields before returning to the same edit view to display those associated ModelState validation errors.
Here's where my amazement and curiosity comes. It was my assumption that in order for me to bind the form fields with the refreshed model, I needed to make a call to either UpdateModel() or TryUpdateModel<>(), passing in the newly refreshed model. For example:
[HttpPost]
public ActionResult EditDetail(EditDetailItemModel model)
{
if (model.IsValid)
{
// Save the results to the db
return RedirectToAction(...)
}
// Can't simply "return View(model)". Not all fields in EditDetailItemModel
// were part of the form - thus they returned null. Have to refresh
// model from the db.
var refreshedModel = RefreshModelFromDB();
// Is this line necessary?????
TryUpdateModel<EditDetailItemModel>(refreshedModel);
return View(refreshedModel);
}
But, what I found was that if I simply returned refreshedModel to the view WITHOUT making a call to TryUpdateModel<>(), the refreshed model was automatically bound with the form field values posted!! Hence, the TryUpdateModel<>() is not needed here!
The only way I can make any sense of it is that since the ModelState is in an invalid state, once I returned the view with the refreshed model, the "MVC rendering engine" looped through the ModelState errors and bound those property values with my refreshed model. That is simply AWESOME! But, I want proof as to this assumption. I can't find documentation regarding this anywhere on the web. Can anyone either confirm my hypothesis of WHY/HOW this AWESOME auto binding behavior is occuring and/or educate me as to why/how it's happening, hopefully backed up with some online documentation links so I understand more fully what's going on under the covers?
public ActionResult EditDetail(EditDetailItemModel model)
That line will perform model binding. Think of ActionMethod parameters as always being populated by a call to UpdateModel.
You are not seeing refreshedModel's values in the view, you are seeing the ModelState entries and values from EditDetailItemModel.

Resources