views:

2469

answers:

6

I appear to be having a problem with ASP.NET MVC in that, if I have more than one form on a page which uses the same name in each one, but as different types (radio/hidden/etc), then, when the first form posts (I choose the 'Date' radio button for instance), if the form is re-rendered (say as part of the results page), I seem to have the issue that the hidden value of the SearchType on the other forms is changed to the last radio button value (in this case, SearchType.Name).

Below is an example form for reduction purposes.

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.RadioButton("SearchType", SearchType.Date, true) %>
  <%= Html.RadioButton("SearchType", SearchType.Name) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.Hidden("SearchType", SearchType.Colour) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.Hidden("SearchType", SearchType.Reference) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

Resulting page source (this would be part of the results page)

<form action="/Search/Search" method="post">
  <input type="radio" name="SearchType" value="Date" />
  <input type="radio" name="SearchType" value="Name" />
  <input type="submit" name="submitForm" value="Submit" />
</form>

<form action="/Search/Search" method="post">
  <input type="hidden" name="SearchType" value="Name" /> <!-- Should be Colour -->
  <input type="submit" name="submitForm" value="Submit" />
</form>

<form action="/Search/Search" method="post">
  <input type="hidden" name="SearchType" value="Name" /> <!-- Should be Reference -->
  <input type="submit" name="submitForm" value="Submit" />
</form>

Please can anyone else with RC1 confirm this?

Maybe it's because I'm using an enum. I don't know. I should add that I can circumvent this issue by using 'manual' input () tags for the hidden fields, but if I use MVC tags (<%= Html.Hidden(...) %>), .NET MVC replaces them every time.

Many thanks.

Update:

I've seen this bug again today. It seems that this crops its head when you return a posted page and use MVC set hidden form tags with the Html helper. I've contacted Phil Haack about this, because I don't know where else to turn, and I don't believe that this should be expected behaviour as specified by David.

+3  A: 

This would be the expected behavoir - MVC doesn't use a viewstate or other behind your back tricks to pass extra information in the form, so it has no idea which form you submitted (the form name is not part of the data submitted, only a list of name/value pairs).

When MVC renders the form back, it is simply checking to see if a submitted value with the same name exists - again, it has no way of knowing which form a named value came from, or even what type of control it was (whether you use a radio, text or hidden, it's all just name=value when its submitted through HTTP).

David
I would agree with what you're saying, except that I'm explicitly requesting the hidden form field values, and MVC shouldn't have any problem setting these value to what I'm setting them to. When the form has been posted, these value shouldn't change, but they are.
Dan Atkinson
Put another way, if I wrote that form in simple HTML, it would work as expected, and set the hidden form fields as I requested in my ascx file. If I render them through the Html.Hidden(), .NET MVC messes them up.
Dan Atkinson
+4  A: 

Yes, this behavior is currently by design. Even though you're explicitly setting values, if you post back to the same URL, we look in model state and use the value there. In general, this allows us to display the value you submitted on postback, rather than the original value.

There's two possible solutions. #1, use unique names for each of the fields. Note that we by default use the name you specify as the id of the HTML element. It's invalid HTML to have multiple elements have the same id. So using unique names is good practice.

Another solution is to not use the Hidden helper. It seems like you really don't need it. Instead, you could do this:

<input type="hidden" name="the-name" 
  value="<%= Html.AttributeEncode(Model.Value) %>" />

Of course, as I think about this more, changing the value based on a postback makes sense for Textboxes, but makes less sense for hidden inputs. We can't change this for v1.0, but I'll consider it for v2. But we need to think through carefully the implications of such a change.

Haacked
Yes, I think it does make more sense for textboxes to be changed, but not so much for hidden form fields. I can't imagine a situation where, if I had more than one form on a page (like the example in my reduction), I would want it to be anything other than the value I set it to.
Dan Atkinson
Ack... Damn these character limits. :) I think that I might make an extension for Html.Hidden, enforcing the requirement of an id (rather than just use the htmlAttribute object), as I don't like the idea of tag soup. That should effectively be solution 1. :)Thanks for your reply!
Dan Atkinson
Another way to set the id is: <%= Html.Hidden("name", value, new {id="my-id"}) %> (or something like that)
Haacked
This is true, but I believe that developers might accidentally forget to do this. Although I can't help but feel that this is coding around a bug! :)
Dan Atkinson
With v2 in development (and preview release) do you know if there is any update to this 'bug'?
Dan Atkinson
still is "broken" in mvc3 preview 1.
thekaido
@Haacked, This is very bad, design decision and not documented as such!
Pop Catalin
A: 

This may be 'by design' but it's not what is documented:

Public Shared Function Hidden(ByVal htmlHelper As System.Web.Mvc.HtmlHelper, ByVal name As String, ByVal value As Object) As String Member of System.Web.Mvc.Html.InputExtensions Summary: Returns a hidden input tag.

Parameters: htmlHelper: The HTML helper. name: The form field name and System.Web.Mvc.ViewDataDictionary key used to look up the value. value: The value of the hidden input. If null, looks at the System.Web.Mvc.ViewDataDictionary and then System.Web.Mvc.ModelStateDictionary for the value.

This would seem to suggest that ONLY when the value parameter is null (or not specified) would the HtmlHelper look elsewhere for a value.

In my app, I've got a form where: html.Hidden("remote", True) is rendering as

Note the value is getting over-ridden by what is in the ViewData.ModelState dictionary.

Or am I missing something?

Darragh
+1  A: 

I just ran into same issue. Html helpers like TextBox() precedence for passed values appear to behave exactly opposite what I inferred from the documentation http://msdn.microsoft.com/en-us/library/dd492984(VS.100).aspx where it says "The value of the text input element. If this value is null reference (Nothing in Visual Basic), the value of the element is retrieved from the ViewDataDictionary object. If no value exists there, the value is retrieved from the ModelStateDictionary object." To me, I read that the value, if passed is used. But reading TextBox() source:

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

seems to indicate that the actual order is the exact opposite of what is documented. Actual order seems to be:

1) ModelState 2) ViewData 3) Value (passed into TextBox() by caller)

A: 

Example to reproduce the "design problem", and a possible workaroud. There is no workaround for the 3 hours lost trying to find the "bug" though ... Note that this "design" is still in ASP.NET MVC 2.0 RTM.

    [HttpPost]
    public ActionResult ProductEditSave(ProductModel product)
    {
        //Change product name from what was submitted by the form
        product.Name += " (user set)";

        //MVC Helpers are using, to find the value to render, these dictionnaries in this order: 
        //1) ModelState 2) ViewData 3) Value
        //This means MVC won't render values modified by this code, but the original values posted to this controller.
        //Here we simply don't want to render ModelState values.
        ModelState.Clear(); //Possible workaround which works. You loose binding errors information though...  => Instead you could replace HtmlHelpers by HTML input for the specific inputs you are modifying in this method.
        return View("ProductEditForm", product);
    }

If your form originally contains this: <%= Html.HiddenFor( m => m.ProductId ) %>

If the original value of "Name" (when the form was rendered) is "dummy", after the form is submitted you expect to see "dummy (user set)" rendered. Without ModelState.Clear() you'll still see "dummy" !!!!!!

Correct workaround: <input type="hidden" name="Name" value="<%= Html.AttributeEncode(Model.Name) %>" />

I feel this is not a good design at all, as every mvc form developer needs to keep that in mind.

Softion
I would look at creating your own hidden helper instead.
Dan Atkinson
A: 

Hi Guys i am still experiencing this bug. I have all the latest MVC 2.0 assemblies. VS 2010 all latest.

I am spend 3 hours around my code trying to find any bugs I also was trying all browsers - no way. Then i came across idea to google "MVC Hidden field bug" and here it is.

It amazing. Is there any thoughts when it will be fixed?

Juk
Best bet is to just create your own hidden helper which sets the passed integer value to string.
Dan Atkinson