Does ASP.NET MVC form post autopopulate fields? - c#

I have a form like ...
#using (Html.BeginForm("create", "Account", FormMethod.Post, new { id = "accountform_form" }))
{
#Html.TextBoxFor(e => e.ShipFirstName)
...
}
while testing, I was surprised to see the field retained its value on postback even without me assigning it to the view-model. Using the debugger, the value for ShipFirstName is null right at the end of the action when returning the view, so why would it show the value that was in the field? Have I been unnecessarily assigning posted values to view-model properties all this time? Or is there something else going on?
Update: the action is like so...
[HttpPost]
public ViewResult Create(AccountFormModel postModel)
{
var model = new AccountFormModel(postModel, stuff, stuff); //I use posted values and paramters to create the actual view model
return view(model);
}
So, I see the GET form, enter values, say I enter a field and leave a required field blank, submit, the resulting page has the value I entered in the other field, who's putting it there when in the model it's null?

I ran into something similar earlier today (a checkbox was always checked). Have a look at Changing ViewModel properties in POST action and see if this is similar.
Basically calling ModelState.Clear() fixed it for me.

As you're passing the model back to the view after it has been POSTed, MVC is taking the stance that you're doing so because the form contains errors. So, rather than making the user fill out the form again, it repopulates it using the ModelState collection. In this case, the values in the ModelState collection take precedence over the changes you make in the action (which does feel a bit weird).
You can get around this either by calling ModelState.Clear() or using ModelState.Remove(string key), where key is the name of the property.
If you'd like a full explanation of why this is the case, see ASP.NET MVC’s Html Helpers Render the Wrong Value!. Excerpt:
Why?
ASP.NET MVC assumes that if you’re rendering a View in response to an HTTP POST, and you’re using the Html Helpers, then you are most likely to be redisplaying a form that has failed validation. Therefore, the Html Helpers actually check in ModelState for the value to display in a field before they look in the Model. This enables them to redisplay erroneous data that was entered by the user, and a matching error message if needed.

Related

Model values overridden by Request.Form when using EditorFor

I am building a wizard - a series of form steps.
Each step of the wizard are submitted to the same action method (it needs to be highly extendable), and all step viewmodels inherit from the same base model. They are bound to their concrete type by a custom model binder. The submit action saves the form fields and returns the next step (or the same in case of errors).
It works well. However, at one point the user needs to supply work information. The step immediately following primary work is secondary work information - same form, same properties. The viewmodel for the second part inherits from the primary viewmodel without implementing any individual properties (the class definition is empty).
After the primary form is submitted, the secondary viewmodel is returned with values reset. However, for some reason, the form is shown with the first viewmodels values.
And the really strange thing is, the model values are overridden by Request.Form values for all EditorFor calls, but not for specific calls:
// This displays correctly, showing the current model values
<div>Model says: #Model.AuthorizationNumber</div>
<div><input type="text" value="#Model.AuthorizationNumber"/></div>
// This displays the Request.Form value from the previous step.
#Html.EditorFor(x => x.AuthorizationNumber)
In the above, the authorization number is shown correctly for the first two lines (constructor values), but the third is incorrect (showing the Request.Form value).
What could be the problem?
EDIT 1
On request, the controller method handling the submit.
public ActionResult Index(StepPathState model = null)
{
var isPreviousSubmit = Request.Form.AllKeys.Contains("submit-previous");
if (model == null || model.Step == null) return View(Steps.GetFirstPathResult());
// submit the step data
var result = Steps.SubmitStepData(model, isPreviousSubmit);
if (result.Status is StepSuccessAndFinish)
{
return Finish(result);
}
var failureStatus = result.Status as StepActionStatusFailure;
if (failureStatus != null)
{
foreach (var error in failureStatus.Errors)
{
ModelState.AddModelError(error.Property, error.ErrorMessage);
}
}
return View(result);
}
I may have found the answer though: http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx
Solution: The good and the ugly
As the article linked above describes, it seems that MVC favors the modelstate values when returning from a POST. This is because re-displaying the form in a POST should always indicate a validation failure. So the correct solution is to save, redirect to a GET request and display the new form. This would fix the problem, and is what I am going to do when I have time to refactor.
A temporary solution is to clear the modelstate. This will cause it to fallback back to the model. This is my temporary fix:
if (failureStatus == null && ModelState.IsValid)
{
ModelState.Clear();
}
return View(result);
The danger here is that I have cleared the modelstate. And what might that entail in the long run? New bug adventures to come. But I am on the clock.

How to make Html.TextBox(string name) render sanitized input?

I am using ASP.NET MVC4 with .NET Framework 4.5. I have a controller action that accepts a model of one type with a property named 'Name' but renders a view using a model of another type. I am still able to use #Html.TextBox("Name") and #Html.ValidationMessage("Name").
I want the textbox to display the sanitized input, that is, the input without leading/trailing/extra spaces the user may have entered. The setter for my model sanitizes the value for me, and I am successfully obtaining the sanitized value using the getter within the controller action. It's just that upon submitting the form, the textbox still displays the unclean input.
Is there some mechanism I am missing? Is the #Html.TextBox(string name) helper looking at the raw request data and not the model? If so, how come the validation message is working?
Update
I have just tried defining a new view model that includes my textbox field so I could hopefully just use the #Html.TextBoxFor helper. Everything is still working as it was after a re-build, I am still not getting sanitized input appearing in the textbox. I still don't know a solution for this.
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult MyAction(MyViewModel model)
{
if (this.ModelState.IsValid)
{
using (var service = new MyService())
{
model.MyResults = service.DoSomething(model.MySanitizedProperty);
}
}
return this.View("MyView", model);
}
Then, in "MyView":
#Html.TextBoxFor(m => m.MySanitizedProperty)
<input type="submit" value="Go" />
#Html.ValidationMessageFor(m => m.MySanitizedProperty)
In the controller, invoking model.MySanitizedProperty returns the sanitized value while the textbox goes on to display the unsanitized data.
It sounds like a problem with the models; make sure you are properly accessing the value from the model you wish to populate it with, i.e., possibly discretely specifying the model "Name" is coming from.
Also, check to see that the setter has a chance to operate on the value - if the controller is activating before the setter function is used, then you'll only get the original input value.
Realize you have to go to the server for the setter to work, possibly you need a async postback or such, and the value reloaded.

Submit form with GET method

Alright, I lost the entire morning on this and I'm getting nowhere.
I have a good experience with MVC, been using it since the first betas back in 2008 but I can' t really figure this out.
I substantially have 2 GET methods: the first one renders a form. The second one is the method the form points to. I'm submitting using GET because it's a search form, and I want to have a bookmarkable URL with the parameters.
Something like this
[HttpGet]
public ActionResult DisplayForm()
{
Contract.Ensures(Contract.Result<ActionResult>() != null);
Contract.Ensures(Contract.Result<ActionResult>() is ViewResult);
return this.View();
}
[HttpGet]
public ActionResult Search(MyViewModel viewModel)
{
Contract.Requires<ArgumentNullException>(viewModel != null);
Contract.Ensures(Contract.Result<ActionResult>() != null);
Contract.Ensures(Contract.Result<ActionResult>() is ViewResult || Contract.Result<ActionResult>() is RedirectToRouteResult);
var result = this.validator.Validate(viewModel); //FluentValidation validation
if (!result.IsValid)
{
result.FillModelState(this.ModelState); //extension method that uses AddModelError underneath for ValidationMessageFor helpers on search form
return this.RedirectToAction(c => c.DisplayForm()); //MvcContrib redirect to action
}
ViewData.Model = viewModel;
return View();
}
The first time I submit the form, the viewData in the target method gets populated correctly.
If I go back and do another search, it's like the modelbinder "cached" the data I submitted the first time: the viewData always has the values from the first search. It resets only restarting the application.
I tried checking both ModelState and HttpContext.Request and they DO have the new data in them (not stale) but still the viewData gets populated with old data. I also tried overriding OnActionExecuting and OnActionExecuted simply to put a breakpoint in there and check the ModelState in those steps, and found nothing weird.
I also tried to call the Search method directly via the browser bar, since it's in GET and I can do it. Still, ModelState and Request contain the data I input, but the viewData has old data.
This is really getting to my nerves as I can't really understand what's going on.
Any help would really be appreciated.
Have you tried
ModelState.Clear()
When you're done with the search call?
I experimented a lot and I found out that the problem was on an actionfilter on a base class which I was unaware of. The "PassParametersDurignRedirect" filter of MvcContrib. Without it everything works fine.

How do I keep field values in the ASP.NET MVC form if it has been invalidated?

I have a form that is going through some validation before sending an e-mail.
I have tried using this for validation, where the method ValidateInput sets the ModelState depending on the input:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
ValidateInput(collection);
if (ModelState.IsValid == false) return View(collection);
This clears all the entered fields if something is invalid. I want to keep all the entered data in the field. How can I do that?
Are you using Html helpers for input field in your form or regular html tags?
<%: Html.TextBoxFor(x => x.Message) %> - original value will be kept
<input type="text" name="Message" /> - original value will be lost after postback
Also you could benefit from using build-in validation (with data annotation attributes) instead of using your own validation method.
AFAIK you wouldn't need to call ValidateInput yourself in the Create method - the framework has already set ModelState for you, so just remove the first line.
If you can't change the ValidateInput method to not wipe your properties in this scenario then you'll really need two copies of the form data, one to pass to this method and one in reserve to pass to the view if validation fails.
You may be better off using a specific view model for the actions view rather than relying on the FormCollection object. That way you can use model binding to retrieve the data.
I think the code below should work but I haven't had a chance to test it so beware!
[HttpPost]
public ActionResult Create(MyCreateViewModel collection)
{
MyCreateViewModel myCVM = new MyCreateViewModel();
TryUpdateModel(myCVM);
ValidateInput(myCVM);
if (ModelState.IsValid == false) return View(collection);

MVC View POST calls new constructor instead of passing back model

I've got a view which is adding a bunch of data into a bound EditModel. However, when it is POSTed, for some reason a parameterless constructor is called before calling the POST Action, instead of just passing back the model that was bound to the view.
I thought all I had to do was to make sure I reference the model in the "Inherits" tag at the top of the view, but for some reason all of the filled in data just gets thrown away on POST and a new instance of the model is instantiated. The View certainly seems to be able to see the model, as I can use Visual Studio Intellisense to access the model fields.
I'm sure it is something simple I am missing, so any pointers as to where to look would help. I can paste in some code if it helps.
Thanks!
As Omu says it will always call the parameterless constructor with a default modelbinder. Basically the modelbinder populate public memberes from the form collection based on the element names (plus maybe a prefix if defined). You need to make sure your element names (not Ids) match the public members you want populating on the model.
This is not how MVC works. When form is posted, MVC constructs new EditModel object and populates it with values provided in form. It doesn't preserve EditModel with was used to populate view in GET action. You have to make sure that every value that you want to use in POST method is set in html inputs in GET.
The constructor it is called, because that's what it should happen, now to see why is your model not filled with data it would be very helpful if would post the code of the view and post action.

Categories

Resources