Model value wrong in generated view - asp.net-mvc

Ran into a very strange problem here using MVC3 and razor.
The app I'm putting together shows a series of interview questions. I have a view for a question (the content of the question comes from the viewmodel). So after I post back from one question it is possible I will be returning the same question view (but using a different viewmodel instance).
In the viewmodel I have a long property like so:
public long QuestionID { get; set; }
I was trying to use a hidden input field to store the value in the view:
#Html.HiddenFor(m => m.QuestionID)
My problem is that as I go from question to question the hidden field is not changing in the final generated html. It remains the value of the first questionID in all further questions. All the other content is changing just fine and I have a couple other hidden fields that are working fine. I have verified the correct values in the controller where the model is generated. I can set a breakpoint in the razor file and I can see the model is as it should be with the correct QuestionID. I tried using #Html.TextBoxFor as well and I have the same problem. When I manually make a hidden field like below it works fine, and this is what is really bothering me.
<input type="hidden" id="QuestionID" name="QuestionID" value="#Model.QuestionID" />
Any idea why this is happening?

After a form post, MVC keeps track of the posted values in ModelState. Even if you change the values of your model, the HiddenFor helpers will use the ModelState value first. This is due to the fact that binding may have failed, or binding may be a complex object and the string value in ModelState is what they actually entered.
You can solve this by using
ModelState.Remove("QuestionID");
or, if you really just want to treat it as a completely new page,
ModelState.Clear();

Related

Confused by How ASP .NET MVC Populates Fields Based on View Model and Posted Form Values

Not sure if this question has already been asked.
I am developing an ASP .NET MVC 2 site. My view has a drop down where user can select which record to display for editing. When this drop down is changed, the field values for the currently selected record automatically get posted to the server and are saved in the session. In the response the server then returns a view for editing the newly selected record.
The problem I am having is that some of the field values from the previous record are being remembered when the new record is displayed.
In my view I have something like the following which is causing the problem:
<%= Html.TextBox("ActionTypeDetails.DisplayName",
Model.ActionTypeDetails.DisplayName) %>
Here I want the posted data to populate a parameter called ActionTypeDetails which has multiple properties including DisplayName.
My controller actions looks something like this:
public ActionResult EditActionType(Guid PluginEditID,
Guid? SelectedActionTypeID,
ActionTypeViewObj ActionTypeDetails)
I have tried changing my view to this:
<%= Html.TextBox("PostedActionTypeDetails.DisplayName",
Model.ActionTypeDetails.DisplayName) %>
And my controller action to this:
public ActionResult EditActionType(Guid PluginEditID,
Guid? SelectedActionTypeID,
ActionTypeViewObj PostedActionTypeDetails)
Yet it still exhibits the same problem. Can someone please explain the logic to why it works like this? What is the easiest workaround that would still allow me to use the built in model binding? I know I can clear the ModelState but this is not ideal as I want some of the other data on the form to be remembered.
If you post back to the same url, asp.net has mechanisms which preserve field values. More info can be found here.

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.

MVC validator errors disappear by the time control is given to the controller

I have a simple model FilesModel for updating a string Description and the boolean value of a checkbox Archived for a few (already uploaded) files, and FilesModel has a validator FilesModelValidator that gets run when this data is posted. This validator does nothing more than check that each file has a description. I know that it runs and correctly returns an error for empty descriptions based on my debugging so far.
However, when control is given to the Action method in the Controller, ModelState is different from what I expect. There are no errors on the description fields, but there is one error for each checkbox that is checked: "The value 'on' is not valid for Archived."
Validation of this sort works just fine in other areas of the site, so I'm sure there's some minute thing I'm overlooking. Any suggestions as to why this may be happening and how to fix it?
Validator
public FilesModelValidator()
{
RuleFor(f => f.Files)
.Must(AllHaveADescription).WithMessage("Must have a description");
}
public static bool AllHaveADescription(Files files)
{
// This is run on postback, and returns false when any Description is empty
return files.All(f => f.Description != null && f.Description.Length > 0);
}
Controller
[HttpPost]
public virtual ActionResult Update(FilesModel model)
{
// At this point, ModelState contains an error for each checked checkbox
// and no errors for empty descriptions
if (ModelState.IsValid)
{
// Save
}
return View(model);
}
It turns out the checkbox thing was the entire problem. I found a solution to this problem elsewhere in our code, so I used it. It seems kind of hacky, but it works.
The idea is that you need to make sure that the checkbox's value is true and not "on". So do this:
<input type="checkbox" id="myCheckbox" value="true" />
Then add a hidden input with the same id with its value as false immediately after the checkbox:
<input type="hidden" id="myCheckbox" value="false" />
When a checkbox is not checked, the checkbox value does not get posted back to the server. So when the postback occurs, the server sees myCheckbox=false which is exactly what we would want in this case. When the checkbox is checked, both input values get posted to the server. But the server uses only the first value (which is the value of the checkbox itself, since we put it before the hidden field). So the server sees myCheckbox=true.
I used the following in the view rather than manually creating the checkbox:
Html.CheckBox("FieldName")
I had the same issue for items in a drop down that were linked to an Enum. I had stripped out the default value and so when someone didn't select something I got an error.
So, having said that, I think your problem might be kinda linked in that the binder is looking for a true/false when your model is expeting a yes/no or vice versa.
Two ways around this might be to change your view to be True/False or, and this is what I did, to write my own ModelBinder which does the conversion from Yes to True.
I hope this is of some help to you.

ASP.Net MVC DefaultModelBinder not binding properties on POST

I'm facing a really strange problem that has me smoked.
I have a fairly simple scenario where I have a strongly typed view that is correctly populated from the controller on the GET, but then when it POSTS the form to the controller, the Reqeust is full of all the right values and right key names for the default model binder to correctly populate one of my model objects, and the DMB creates the correct opject, but it never populates any of the properties, they're all in their default state.
This was working before, the only changes I can think of were that I tried a custom modelbinder (then removed it; double checked to make sure I'm not still using that), and I refactored the model to have a base class with some of the props.
Any thoughts?
A very similar scenario - that the DefaultModelBinder is - essentially - not binding to your model, arrise if you would give your bound model object the same name as one of its properties:
Model
Public Property ArbitraryName1 As Integer
Public Property Answer As String
Public Property ArbitraryName2 As Boolean
View
<p/> ... #Model.ArbitraryName1
<p/> Answer: #Html.TextBoxFor(Function(model) model.Answer)
<p/> ... #Html.CheckBoxFor(Function(model) model.ArbitraryName2)
Controller
<HttpPost()>
Function Index(answer As Model) As ActionResult
' answer is Nothing
End Function
(Using ASP.NET MVC 3)
Got it. The model had been refactored in a way which naturally affected the ability of the mdoel binder to populate it.
The name of your input param do not have to be equal to some property name of the object. Remember that all data coming as an array of name -> value and the default binding use the names for make the relation work.
I had this behaviour arise by moving two properties from the top of the class to further down. I still can't work out why this stopped the binding of a third property from working (so this isn't a solution so much as a 'watch out for') but I repeated the change multiple times and each time the binding went from working to not working.
I also found that after making this change I sometimes had to 'Clean' the solution for the binding to start working again.

MVC - How to change the value of a textbox in a post?

After a user clicks the submit button of my page, there is a textbox that is validated, and if it's invalid, I show an error message using the ModelState.AddModelError method. And I need to replace the value of this textbox and show the page with the error messages back to the user.
The problem is that I can't change the value of the textbox, I'm trying to do ViewData["textbox"] = "new value"; but it is ignored...
How can I do this?
thanks
You can use ModelState.Remove(nameOfProperty) like:
ModelState.Remove("CustomerId");
model.CustomerId = 123;
return View(model);
This will work.
I didn't know the answer as well, checked around the ModelState object and found:
ModelState.SetModelValue()
My model has a Name property which I check, if it is invalid this happens:
ModelState.AddModelError("Name", "Name is required.");
ModelState.SetModelValue("Name", new ValueProviderResult("Some string",string.Empty,new CultureInfo("en-US")));
This worked for me.
I have a situation where I want to persist a hidden value between POST's to the controller. The hidden value is modified as other values are changed. I couldn't get the hidden element to update without updating the value manually in ModelState.
I didn't like this approach as it felt odd to not be using a strongly typed reference to Model value.
I found that calling ModelState.Clear directly before returning the View result worked for me. It seemed to then pick the value up from the Model rather than the values that were submitted in the previous POST.
I think there will likely be a problem with this approach for situations when using Errors within the ModelState, but my scenario does not use Model Errors.

Resources