I must be missing something about how this works, because I can't figure this out. Maybe someone here can help.
My Address object has a property called ValidationStatus. It is not visible on the screen, but has a hidden field:
@Html.HiddenFor(model => model.ValidationStatus)
So, I run the program, open an existing address, which has a ValidationStatus of "OK", and change the address so that it is invalid. I then posts the form to the Controller. The object's Validate method calls the 3rd-party service, which returns an error. The code sets ValidationStatus to "Invalid" and return the View with a validation message.
When the View loads, ValidationStatus is properly set to "Invalid" as I can see by debugging the following statement in the View:
@if (Model.ValidationStatus == "Invalid") //show an additional field.
So I enters data in the new field and again post the form to the Controller. In the first line in the controller, I put a breakpoint and check collection["ValidationStatus"] in the immediate window. It is "OK" instead of "Invalid".
What am I missing here? Why didn't the value stick? There is nothing on the client side that can change that value.
Here's the controller code (pretty basic, really):
[HttpPost]
public ActionResult Index(FormCollection collection, string destinationControllerName)
{
PrepareSecondaryData(); // loads drop-down lists in case the View needs to be returned
try
{
if (!TryUpdateModel(_policy))
return View(_policy);
if (!_services.PolicyEditor.SavePolicy(_policy))
return View(_policy);
}
catch (Exception exp)
{
UIHelper.Log(UIHelper.LogLevel.Error, this, "Error during Save", exp);
ViewBag.Error = UIHelper.GenericErrorMessage();
return View(_policy);
}
return RedirectToAction("Index", destinationControllerName);
}
When rendering view to client, ModelState has the highest priority in providing values of model. That is the case in your situation. When the view is first time sent to client, ValidationStatus
corresponding ModelState["ValidationStatus"]
has not got value, so it takes model's value - "OK". When it is posted to server, ModelState["ValidationStatus"]
is populated with "OK" - sent from hidden field from client. And when validated by 3rd party and returned back again, even though model.ValidationStatus == "Invalid"
, the ModelState["ValidationStatus"] == "OK"
, so according to higher priority of latter, ModelState sets value "OK" for the model. And the client gets value "OK" in the hidden field. To fix it, do something like
ModelState["ValidationStatus"].Value = new ValueProviderResult("Invalid", "Invalid", CultureInfo.CurrentCulture);
General idea is that corresponding record in ModelState array should have correct value for the model.
UPDATE:
Or alternatively, clear value from modelstate, to make MVC use value from model. ModelState.Remove("ValidationStatus")
There's more than you think in the code you posted :)
First of all, your code isn't the same all the time that you render the view. The first time, you'll be coming from this method, I presume:
[HttpGet]
public ActionResult Index()
Note the HttpGet
. You didn't include the code of this method, but I think you'll be getting the data to display, pass it to ViewData.Model and then render the view.
After the first post, this method is entirely left out. You're rendering the view, in case of error, directly from your HttpPost
method, and this is a very serious design flaw. It doesn't directly affect your problem, but solving this will give you both working code and a better design overall.
As a premise, I recommend you to google for PRG PATTERN and then continue on reading here.
Some time later...
Now that you're an expert and a true fan of the PRG PATTERN (I'm sure you love it) I will give you some hints on how to implement it and the common issues you could encounter.
First of all, your HttpGet
method will not vary. Why is that? There's already an answer here from @archil, but I'll explain it anyway. The values contained in the ModelState have a priority over the ViewData.Model. This is done to preserve user input when redisplaying the form.
I know what is the next thing that will come to your mind. With the PRG pattern, the ModelState is lost when doing the redirect. That's true. That's why the guys at MvcContrib did this little magic ActionFilter that makes everything work. ModelStateToTempData
attribute, when applied to the POST method, or even to the whole controller, will make any ModelState in a POST method be serialized in TempData and deserialized back in the first GET method called after that.
So you will have: POST -> Handling of data / error -> Serialization of ModelState -> Redirect to GET method to display the form -> Deserialization of TempData back to ModelState -> Profit.
Italic stuff is what is automatically executed by the ModelStateToTempData
attribute.
I'm sure that, after you refactor your code followin the PRG PATTERN, your error will be gone, and you'll have added a very useful skill to your set, and prevented those nasty "form resubmission" messages that you'll happen to meet in poorly designed forms.
Hope this helps you.