views:

87

answers:

3

This is working, but how???

I have a controller action for a post:

[AcceptVerbs(HttpVerbs.Post )]
public ActionResult Edit(Person person)
{
   bool isvalid = ModelState.IsValid;
   etc.

The Person object has a property BirthDate, type DateTime. When i enter some invalid data in the form, say 'blabla' which is obvious not a valid Datetime, it fills all the (other) Person properties with the correct data and the BirthDate property with a new blank DateTime. The bool isvalid has the value 'false'. So far so good.

Then i do this:

return View(p);

and in the view i have this:

<%= Html.TextBox("BirthDate", String.Format("{0:g}", Model.BirthDate)) %>
<%= Html.ValidationMessage("BirthDate", "*") %>

Ant there it comes: i EXPECTED the model to contain the new, blank DateTime because i didn't put any new data in. Second, when the View displays something, it must be a DateTime, because Model.BirthDate can't hold anything but a DateTime. But to my surprise, it shows a textbox with the 'blabla' value! (and the red * behind it)

Which ofcourse is nice because the user can seee what he typed wrong, but how can that (blabla)string be transferred to the View in a DateTime field?

EDIT: The ModelState info helped me a lot here. I also notices that in MVC 2, that when you create your own Template for Html.EditorFor() you have to implement this behaviour yourself. I created a

DateTime.ascx 

in the /views/shared/EditorTemplates folder, and in there i had to check if there was a modelstate error for this property value, and if so, show the invalid data in stead of the Model data.

So in the view i use this:

 <%= Html.LabelFor(model => model.DateOfBirth) %>

and in the DateTime.ascx i use this:

<%
bool invalidData = false;
string propertyName = ViewData.ModelMetadata.PropertyName;

ModelState ms = ViewData.ModelState[propertyName];
if (ms != null)
{
   invalidData = ms.Errors.Count > 0;
}
string valueToshow = invalidData ? ViewData.ModelState[propertyName].Value.AttemptedValue : String.Format("{0:g}", Model);
%>
<input class="text-box single-line" id="<%= propertyName %>" name="<%= propertyName %>" type="text" value="<%= valueToshow %>" />
+5  A: 

ModelState holds KeyValuePairs for every form element with the key being the field name and the value is what you put in the field. Then the Html Helpers look in ModelState and if you don't explicitly specify a value, they will pull the value from ModelState.

Keith Rousseau
Thanks, didn't now the helpers did more then parsing the value in the model.
Michel
+3  A: 

The previously entered value is stored in the ModelState. When you have an error on the form, the helper pulls the value from the ModelState instead of using either values from the Model or the ones specifically supplied (under the hood, if you don't supply an explicit value it will default to the model value if there is one when there are no errors).

tvanfosson
+1 for the answer, Keith was slightly faster, hope you don't mind marking his as answer
Michel
+1  A: 

I had some code in the 'postback' that would remove invalid promo-codes and capitalize the response. Even if you update the model with the new value it won't be displayed because the ModelState value takes precedence (as others have already answered).

  <%= Html.TextBox("PromoCode", Model.PromoCodes) %>

But if you have a case where this was happening and you don't want the old value persisted you need to do this:

ModelState.Remove("PromoCode"); 

or explicitly set the new value into modelstate (probably the better approach) :

ModelState.SetModelValue("PromoCode", 
      new ValueProviderResult(newValue, newValue, CultureInfo.CurrentCulture));
Simon_Weaver
+1 for the addition.
Michel