Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes


:P
On this page:

So here's a binding behavior in ASP.NET MVC that I didn't really get until today: HtmlHelpers controls (like .TextBoxFor() etc.) don't bind to model values on Postback, but rather get their value directly out of the POST buffer from ModelState. Effectively it looks like you can't change the display value of a control via model value updates on a Postback operation.

To demonstrate here's an example. I have a small section in a document where I display an editable email address:

DisplayModel

This is what the form displays on a GET operation and as expected I get the email value displayed in both the textbox and plain value display below, which reflects the value in the mode. I added a plain text value to demonstrate the model value compared to what's rendered in the textbox.

The relevant markup is the email address which needs to be manipulated via the model in the Controller code. Here's the Razor markup:

        <div class="fieldcontainer">
            <label>
                Email: &nbsp; <small>(username and <a href="http://gravatar.com">Gravatar</a> image)</small>
            </label>
            <div>
                @Html.TextBoxFor( mod=> mod.User.Email, new {type="email",@class="inputfield"})                         @Model.User.Email 
            </div>
        </div>

 

So, I have this form and the user can change their email address. On postback the Post controller code then asks the business layer whether the change is allowed. If it's not I want to reset the email address back to the old value which exists in the database and was previously store. The obvious thing to do would be to modify the model. Here's the Controller logic block that deals with that:

// did user change email?
if (!string.IsNullOrEmpty(oldEmail) && user.Email != oldEmail)
{
    if (userBus.DoesEmailExist(user.Email))
    {
        userBus.ValidationErrors.Add("New email address exists already. Please…");
        user.Email = oldEmail;
    }
    else
        // allow email change but require verification by forcing a login
        user.IsVerified = false;
}… 
model.user = user;
return View(model);

The logic is straight forward - if the new email address is not valid because it already exists I don't want to display the new email address the user entered, but rather the old one. To do this I change the value on the model which effectively does this:

model.user.Email = oldEmail;
return View(model);

So when I press the Save button after entering in my new email address (rickstrahl@hotmail.com) here's what comes back in the rendered view:

AfterPostbackDisplayView

Notice that the textbox value and the raw displayed model value are different. The TextBox displays the POST value, the raw value displays the actual model value which are different.

This means that MVC renders the textbox value from the POST data rather than from the view data when an Http POST is active.

Now I don't know about you but this is not the behavior I expected - initially. This behavior effectively means that I cannot modify the contents of the textbox from the Controller code if using HtmlHelpers for binding. Updating the model for display purposes in a POST has in effect - no effect.

(Apr. 25, 2012 - edited the post heavily based on comments and more experimentation)

What should the behavior be?

After getting quite a few comments on this post I quickly realized that the behavior I described above is actually the behavior you'd want in 99% of the binding scenarios. You do want to get the POST values back into your input controls at all times, so that the data displayed on a form for the user matches what they typed. So if an error occurs, the error doesn't mysteriously disappear getting replaced either with a default value or some value that you changed on the model on your own. Makes sense.

Still it is a little non-obvious because the way you create the UI elements with MVC, it certainly looks like your are binding to the model value:

@Html.TextBoxFor( mod=> mod.User.Email, new {type="email",@class="inputfield",required="required" })

and so unless one understands a little bit about how the model binder works this is easy to trip up. At least it was for me. Even though I'm telling the control which model value to bind to, that model value is only used initially on GET operations. After that ModelState/POST values provide the display value.

Workarounds

The default behavior should be fine for 99% of binding scenarios. But if you do need fix up values based on your model rather than the default POST values, there are a number of ways that you can work around this.

Initially when I ran into this, I couldn't figure out how to set the value using code and so the simplest solution to me was simply to not use the MVC Html Helper for the specific control and explicitly bind the model via HTML markup and @Razor expression:

<input type="text" name="User.Email" id="User_Email" value="@Model.User.Email" />

And this produces the right result. This is easy enough to create, but feels a little out of place when using the @Html helpers for everything else. As you can see by the difference in the name and id values, you also are forced to remember the naming conventions that MVC imposes in order for ModelBinding to work properly which is a pain to remember and set manually (name is the same as the property with . syntax, id replaces dots with underlines).

Use the ModelState

Some of my original confusion came because I didn't understand how the model binder works. The model binder basically maintains ModelState on a postback, which holds a value and binding errors for each of the Post back value submitted on the page that can be mapped to the model. In other words there's one ModelState entry for each bound property of the model. Each ModelState entry contains a value property that holds AttemptedValue and RawValue properties. The AttemptedValue is essentially the POST value retrieved from the form. The RawValue is the value that the model holds.

When MVC binds controls like @Html.TextBoxFor() or @Html.TextBox(), it always binds values on a GET operation. On a POST operation however, it'll always used the AttemptedValue to display the control. MVC binds using the ModelState on a POST operation, not the model's value.

So, if you want the behavior that I was expecting originally you can actually get it by clearing the ModelState in the controller code:

ModelState.Clear();

This clears out all the captured ModelState values, and effectively binds to the model. Note this will produce very similar results - in fact if there are no binding errors you see exactly the same behavior as if binding from ModelState, because the model has been updated from the ModelState already and binding to the updated values most likely produces the same values you would get with POST back values.

The big difference though is that any values that couldn't bind - like say putting a string into a numeric field - will now not display back the value the user typed, but the default field value or whatever you changed the model value to.

This is the behavior I was actually expecting previously. But - clearing out all values might be a bit heavy handed. You might want to fix up one or two values in a model but rarely would you want the entire model to update from the model.

So, you can also clear out individual values on an as needed basis:

if (userBus.DoesEmailExist(user.Email))
{
    userBus.ValidationErrors.Add("New email address exists already. Please…");
    user.Email = oldEmail;
ModelState.Remove("User.Email"); }

This allows you to remove a single value from the ModelState and effectively allows you to replace that value for display from the model.

Why?

While researching this I came across a post from Microsoft's Brad Wilson who describes the default binding behavior best in a forum post:

The reason we use the posted value for editors rather than the model value is that the model may not be able to contain the value that the user typed. Imagine in your "int" editor the user had typed "dog". You want to display an error message which says "dog is not valid", and leave "dog" in the editor field. However, your model is an int: there's no way it can store "dog". So we keep the old value.

If you don't want the old values in the editor, clear out the Model State. That's where the old value is stored and pulled from the HTML helpers.

There you have it. It's not the most intuitive behavior, but in hindsight this behavior does make some sense even if at first glance it looks like you should be able to update values from the model. The solution of clearing ModelState works and is a reasonable one but you have to know about some of the innards of ModelState and how it actually works to figure that out.

Posted in ASP.NET  MVC  

The Voices of Reason


 

Andrei Mazoulnitsyn
April 20, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Hi Rick,

I experienced the same issue a couple of months ago. I looked at asp.net mvc source code and was also very disappointed. You have to write your own TextBoxFor extension method which won't use posted data (model only)

Betty
April 20, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I believe you can remove the model from modelstate to get around this behaviour. However I find the behaviour is normally desirable so haven't done it often.

Matt
April 20, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Rick, FYI you can use ModelState.Clear() to clear all values, and also the UpdateModel methods to force a value into the viewstate.

The default behaviour is nice and convenient - you don't have to do anything to keep your form populated and it just works out of the box. Use the above methods when you want to override it.

mateus
April 20, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I just ran across this yesterday, quite frustrating. I also came across the ModelState.Clear() trick. However, the recommended way is to do a redirect to the same page. I found post by Brad Wilson that explains why this is by design: http://forums.asp.net/post/3688022.aspx.

Rick Strahl
April 20, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Thanks all. Didn't know about ModelState.Clear() - makes sense and I've updated the post to reflect some of the comments here. Thanks!

Richard P
April 21, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I've never come across this because I've always adopted the POST-Redirect-GET on a successful update, which avoids the "F5 issue" as well, but the reasoning above makes sense.

Rick Strahl
April 21, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Richard - really? Don't you redisplay the page if a validation error occurs?

Richard P
April 21, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Rick a validation error isnt what I would call a successful update ;)

Rick Strahl
April 22, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Richard - I understand. But what do you do when you have a validation error? Don't you re-display the data the user entered in that case and redisplay the HttpPost View? In which case you do need to deal with the ModelState update scenario I describe above.

Richard P
April 24, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Rick I'm sorry but I might be missing something - the default according to your article is to redisplay the invalid data, which is 100% fine with me as I don't want to confuse the end user by returning a valid form (because ive fixed it) and also telling them "x is invalid, please fix". Redisplaying the invalid values is what I want.

Am I misinterpreting your article?

Rick Strahl
April 24, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Richard - no you're not missing anything. If you read the whole thing I explain why I assumed that I could modify the model output rendered, but couldn't and why sometimes that may be useful and why it works the way it does.

Richard P
April 25, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Rick then it was your last comment that confused me, because I just redisplay the form without touching the model at all - I want the duff values to be redisplayed, because it makes more sense!

Your last comment makes it sound like I have to deal with the ModelState update scenario when i don't at all.

Ed
April 26, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Does ModelState.Clear() remove everything the user typed? If so, that is using a steam roller where an iron would suffice.

I agree with Richard. KISS => If we stop the over engineering *magic* - the confusion and problems will go away. For both us and our users.

Where's my tool to determine I'm building the right thing vs. building the thing right? sigh - we all make these time sink design decisions with the best of intentions way too often.

Rick Strahl
April 26, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

@Ed - ModelState.Clear() clears the model state :-) Which means it clears the captured input values. By calling .Clear() you clear out those values, but remember the model is already updated from the values that didn't fail. So when you .Clear() the model will then bind from the actual model values which will have been updated by the model binder.

So, to answer your question: No the user input is not thrown out - for all values that are valid, since they still exist on the model which will then bind. The only values where the value won't be valid then is when a modelbinding error occurs (such as typing a string value into numeric field) - those values will revert to their model defaults - or whatever you assign the value to.

In the end I think MVC does a pretty good job of giving us all the options. We can use the default behavior which always displays the POST data input by the user, or we can call ModelState.Clear() to use the model values always, which puts the onus on us to decide what to put into the model (which can include the actual ModelState value since that's still there until you clear).

Hmmm... also wonder if you clear out individual values from ModelState. That would be the ultimate control since that would in effect allow you to do the one of overriding.

magellings
May 08, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Yeah I ran into the same problem....took a while to figure out.

Jason K
September 07, 2012

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Total life saver!! I struggled with what the heck was going on for about an hour before I googled and found your post. Thanks for taking the time to explain whats going on and now I have a better understanding in general of some of the inner workings (which is always a nice bonus when you search for a quick fix)!

Calibre
September 22, 2013

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Nicely explained !!! I was facing a similar problem , thanks a lot for the post !!!

om
July 12, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

very nicely explained and helped me figure out actual problem.
Thanks

Carlos Cysneiros
August 07, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Life Saver! I spent an entire day...thinking I was crazy...whenever I updated a property in the model on a POST, the new value would not be editable (It would be okay for DisplayTextFor). Thank You

Jason
August 14, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

The real problem for me is Presentation (or re-presentation) of the View. Say for example my user enters their name in the Name field, but decides to put 10 spaces first, or say 5 spaces before First Name and Last Name (assuming 1 field here). Then on validation error and re-presentation of the View, according to the way the Helpers work now, that user would see their name exactly as entered (spaces and all). While this is sure to be a rare instance, it's something I like to "clean-up" on re-presentation (I like that control. It also isn't breaking validation). The other concern is the clearing of a field on validation error, making the user re-enter that data.

This article also makes the argument for updating the View with necessarily corrected information.

I think Microsoft's implementation here should be made more flexible. Thanks Rick, I was struggling with this myself for the past few days. Hard to wrap your head around when coming from other languages such as Java or PHP.

santosh
August 15, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Damn, I'm from ASP.NET background slowly transitioning to MVC. I was really caught up with this anomaly(from the perspective of webform developer) until I landed your page. Thanks for this very useful information.

Simon Papworth
August 24, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Thank you, 24 elapsed hours of a bank holiday weekend up in smoke because of this badly explained behaviour, I found the ModelState being different to the Model shortly before finding your article.

Thank you, save my Bank Holiday Monday.

I hate 5 minute enhancements!

Bill Larkin
September 25, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I puzzled and fought this for a whole afternoon. I didn't even know how to describe the problem and search the Internet for it. But then I stumbled on your write up that explained the problem. Thank you very much for a solution to a problem that was ruining my day!

John Hawkins
November 17, 2014

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

None of the above worked, but UpdateModel did

Ben Gichamba
March 31, 2015

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Thanks for this post. Had spent a couple of hours wondering what I am doing wrong.

Serge
April 28, 2015

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Thanks Rick, spend half a day guessing where does Html.TextBoxFor getting its values as my model is null! I started to suspect it takes it from POST when I found this article.
So non intuitive and leads to lazy coding when people would skip updating properties of the model before returning it and relying on ASP.NET to do plumbing.

Elmar
June 12, 2015

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Flip, just ran into the same behavior which took me by surprise.
Applied the
ModelState.Remove("key")
option which successfully solved the problem.
thx

Jeff
March 27, 2017

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Rick - thank you for posting this!! It was driving me crazy that the model wasn't binding and now I know why. ModelState.Clear() was the answer.


Gary Smith
September 20, 2017

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I have spent a good 10 hours on finding the solution to this. My google history of search terms apply is crazy. I'm using MVC in conjunction with Kendo controls so I've spent my time looking at Kendo's Window as the primary cause. ModelState. How simple.

In my case I'm validating addresses, and if the address matches (but a different case) I wanted to replace it with the proper validate UPS address. I was able to see it in all of the data, it just wanted reflecting in the controls. With just a few lines of code I'm able to reset the 6 controls that I care about.


Eric
December 11, 2017

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

This was SO helpful. I figured out this must be happening by hours and hours of experimentation but couldn't figure out how to confirm it. Thanks to Matt as ModelState.Clear did the trick.


Brian Wells
May 29, 2018

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Why is it I keep finding myself on this same website getting great answers to questions and issues that are driving me to distraction? Thanks again, Rick!

As an aside for the next newbie like myself who might be poking around for ways to clear values on postback, be aware that for passwords you'll likely to use PasswordFor(), and not TextBoxFor(). The latter will return those passwords as plain text! Yikes. If I had known that an hour ago, I might not have stumbled upon this (still) very helpful post.


Jeremy
March 19, 2020

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Oh man, I just wasted hours on this and another quirk where you have to be careful what you name your action parameters. This model binder is a pain sometimes! Mahalo for this


Aditya Gupta
September 30, 2020

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

I really owe you Rick, you have saved me, is struggled for almost 2 days and was manually setting the values from model but got stuck in case of dropdowns and other controls. Finally came across your blog and it helped me understand what might be going wrong. Thanks a ton!


Brian Wells
October 25, 2021

# re: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

On nearly the tenth anniversary of this post, I returned to it for nearly the fifth time, and probably not for the first time I note a typo which (this post proving to have some serious longevity) you may want to correct.

"..reflects the value in the mode." That should be "...reflects the value in the model."

Thanks again, Rick. Hopefully I won't need to refresh my mind on this topic again. But if I do, I know where to find this post, and I hope it will still be here. 😃


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024