views:

300

answers:

2

This behavior is making me wonder about my sanity..

I have a form that has two places that accept input, let's call them ValueA and ValueB. The user can enter a value in either one and the form submits.

<div id="MyUpdateTarget">
 <% using (Ajax.BeginForm("MyControllerAction", new AjaxOptions { UpdateTargetId = "MyUpdateTarget" })) { %>
  <%=Html.TextBox("ValueA", Model.ValueA, new Dictionary<string, object> {
                                                    { "onchange", "$('#SubmitButton').click(); return false;" },
       }) %>
  <%=Html.TextBox("ValueB", Model.ValueB, new Dictionary<string, object> {
                                                    { "onchange", "$('#SubmitButton').click(); return false;" },
       }) %>
  <input id="SubmitButton" type="submit" value="Save" style="display: none;" />
 <% } %>
</div>

The Controller Action looks like this:

public ActionResult MyControllerAction(MyViewModel viewModel)
{

// do some other stuff ...

 return PartialView("MyPartialView", viewModel);
}

The ViewModel is simply this:

public class MyViewModel
{
 private int _valueA;
 private int _valueB;

 public int ValueA 
 { 
  get
  {
   return _valueA;
  }
  set
  {
   if (value > 0)
   {
    ValueB = 0;
   }
   _valueA = value;
  } 
 }
 public int ValueB 
 {
  get
  {
   return _valueB;
  }
  set
  {
   if (value > 0)
   {
    ValueA = 0;
   }
   _valueB = value;
  }
 }
}

Now, the unexpected piece. Say the page initially loads and ValueB has a value of 7. The user changes ValueA to 5 and the form submits. I can put a breakpoint in the controller action and see both values in the viewModel parameter. At this point, ValueA is 5 and ValueB is 0 (due to the setting of ValueA). The action returns the viewModel as part of the PartialView. Back in the partial, I can put a breakpoint on the Html.TextBox("ValueB", Model.ValueB, ...) line and see that ValueB is indeed 0. But when the form renders to the browser, ValueB still has a value of 7. And this is where I am stuck. I have even changed the Update target to a different div, so that the partial just spits out the form someplace completely different, but it still has the original value of 7, even though I saw through debugging that the value was 0 coming back from the controller.

Is there something I am missing?

+3  A: 

Here is the code from the MVC Source for the textbox:

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

And the Code for GetModelStateValue()

    internal object GetModelStateValue(string key, Type destinationType) {
        ModelState modelState;
        if (ViewData.ModelState.TryGetValue(key, out modelState)) {
            if (modelState.Value != null) {
                return modelState.Value.ConvertTo(destinationType, null /* culture */);
            }
        }
        return null;
    }

So what happens is the Html "Helper" looks for the text box value, by matching the name, in your ViewData.ModalState, if its in the ModelState dictionary, it completely ignores the value you provided.

So all that if (value > 0) { ValueA = 0; } doesn't matter because its going to use the posted values in ModelState if the names match.

The way I've fixed this is to blow away the ModalState before the view renders for certain values that I want to mess with in my view models. This is some code I've used:

    public static void SanitizeWildcards( Controller controller, params string[] filterStrings )
    {
        foreach( var filterString in filterStrings )
        {
            var modelState = controller.ModelState;

            ModelState modelStateValue;
            if( modelState.TryGetValue(filterString,out 
                    controller.ModelState.SetModelValue(filterString, new ValueProviderResult("","", null));
        }
    }
jfar
Thanks for the insight jfar! Based on your response, I chucked the textbox helpers used html inputs instead. There were only two inputs on my form that needed this behavior, so I went for simplicity over having to walk through the ModelState dictionary. Your post was very insightful and led me to the solution though, so thanks again!
Jonathan M
A: 

thanks jfar.. this is the vb code:

Sub CleanForm(ByVal ParamArray Fields() As String)
    Dim modelStateValue As ModelState = Nothing
    For Each Field In Fields
        If ModelState.TryGetValue(Field, modelStateValue) Then
            ModelState.SetModelValue(Field, New ValueProviderResult(Nothing, Nothing, Nothing))
        End If
    Next
End Sub
bortao