views:

3938

answers:

6

I have an issue cropping up in a form I'm trying to post. In the scenario where the form doesn't validate, I'm taking the standard route of calling ModelState.AddModelError() then returning a View result.

The thing is, the HTML.* helpers are supposed to pick up the posted value when rendering and I'm noticing that my text fields ONLY do so if I include them in the parameter list of the postback action, which shouldn't be required seeing as some forms have way too many fields to want to list them all as parameters.

My action code is roughly:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditDataDefinition(long? id, string name)
{
    var dataDefinition = ...

    // do some validation stuff
    if (!ModelState.IsValid)
    {
        // manually set checkbox fields via ViewData seeing as this STILL doesn't work in MC 1.0 :P
        // ...
        return View(dataDefinition);
    }

}

Now, dataDefinition (which is a LINQ to SQL entity) has a field MinVolume, is handled in the view by this line:

Minimum: <%= Html.TextBox("MinVolume", null, new { size = 5 })%>

Yet when the view is rendered after a failed ModelState validation, the value typed into it on the original page we posted is not preserved UNLESS I include it as a parameter in the postback method. Literally, I can "solve the problem" by doing this:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditDataDefinition(long? id, string name, string minVolume)

For some reason that will force the field value to be preserved. This seems stupid to me because my form has way more values than just that and I shouldn't have to add a parameter for just that field.

Any ideas?

+3  A: 

What is the key you are using when you set the value in the ModelState on error? The code that sets the value parameter for a TextBox looks like:

Relevant portion of the downloaded framework code.

string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);

As you can see if the attempt value exists, it will use it -- but only if the same key is available.

I know that this works because I have an action that takes no parameters and gets the values directly from the ValueProvider and it uses AddModelError to indicate validation errors. I'm sure that the values in my TextBoxes are retained.

EDIT: In order for the values to be retained, they need to be associated with the model in some way. One way to do this is to add them to the parameter list. Another way is to use UpdateModel (with the parameter names in the whitelist or no whitelist). A third way is to add the parameter explicitly to the model as in @Jenea's answer. Since the helper only pulls from the model state, they must be in there for the values to be retained. It does not look at the request's Form property.

tvanfosson
That field is not validated at all- it doesn't need to be at this stage. Hence it is just an "innocent bystander" in the form and expects to have its new value preserved if one of the other fields does not behave itself and thus causes the form to render a second time.
Nathan Ridley
I am wondering all of a sudden if I am incorrectly making the assumption that ModelState should be trying to preserve all fields that have names that match the Model. Perhaps it only preserves fields that are passed in via the parameter list or referenced via AddModelError? If that is the case, it seems kind of stupid seeing as of course I want to preserve everything in the form, not just specific cases. Then again, MVC still doesn't properly preserve checkbox fields anyway...
Nathan Ridley
+3  A: 

Could it be that your code:

<%= Html.TextBox("MinVolume", null, new { size = 5 })%>

..has the null for the default value param? Maybe if you change the null to Model.MinVolume it will persist the value. Like this:

<%= Html.TextBox("MinVolume", Model.MinVolume, new { size = 5 })%>

I'm not sure if your action returns the value MinVolume in the model tho. If it does, the above should work. Else, you may need to refactor the action slightly.

cottsak
No, specifying null is supposed to specify that the existing model context should be used for querying the value. Specifying MinVolume is explicit and would force that value to be used instead of what was previously submitted.
Nathan Ridley
Incorrect. Specifying MinVolume looks up the ViewDataDoctionary and then the ModelStateDictionary for subsequent posts. So if Model.MinVolume is say 5 becore edit, then specifying MinVolume will place 5 into the TextBox. Then if a post is made (say value 3) but found invalid then the TextBox will show 3 the 2nd time round. Have you tried it? You generally specify null on a 'New' or 'Create' view so that the initial post shows empty in the field, and subsequent posts persist the entered value.
cottsak
This is exactly the way I do it too and works every time. I never saw anywhere that you have to specify null. A link to where you saw that would be good.
sirrocco
I have based much of my code from this: http://blog.wekeroad.com/mvc-storefront
cottsak
A: 

As I understand the solution is:

[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditDataDefinition(int id, FormCollection form)
{
    T itemToUpdate = repository.Get(id);
    UpdateModel(itemToUpdate, form.ToValueProvider());

    if (itemToUpdate.IsValid())
    {
        repository.SaveOrUpdate(itemToUpdate);
        return Json(ValidationResultToJson(itemToUpdate.ValidationResults()));
    }

    repository.DbContext.RollbackTransaction();
    return Json(ValidationResultToJson(itemToUpdate.ValidationResults()));
}

good luck!

diadiora
+6  A: 

Oh man I've just improved my application design. The problem occurs because you have custom validation (I have too). You have to add after

ModelState.AddModelError()

this

ModelState.SetModelValue("MinVolume", ValueProvider["MinVolume"]);

In view it has to be

Mimum:<%=Html.Textbox("MinVolume")%>

Still not sure why it works but it worked for me.

Jenea
oooh... this could be the answer! will check in the morning when i get to work. thanks.
Nathan Ridley
It works because it adds the value to the model, from which the helper pulls the values. Using UpdateModel would also work, which I what I use and why my textbox values are being preserved.
tvanfosson
A: 

Another solution is if you type in view:

<%var minVolume=Request["MinVolume"]??"";%>
<%=Html.Textbox("MinVolume",minVolume,new {size=5})%>
Jenea
A: 

Please refer to this article to see different ways to handle postback in ASP.NET MVC

link text

Nice article for the starter.