Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 4 @HTML.HiddenFor are not updating on a postback [duplicate]

Having issues with the view state on a series of page views -- On the initial view of a page in Razor I am using Html.HiddenFor as follows:

    @Html.HiddenFor(x => Model.err)
    @Html.HiddenFor(x => Model.errField)
    @Html.HiddenFor(x => Model.errMessage)
    @Html.HiddenFor(x => Model.IsMove)

which seems to work fine. My hidden input tags contain the correct values. However when I submit the form [HTTPPost] and update the model in my controller action with..

       model.err = transHelper.err;
       model.errField = transHelper.errField;
       model.errMessage = transHelper.errMessage;
       return View(model);

The hidden fields do not seem to update, they contain the original values from the initial view. However When I use these fields in another context within the same razor view like this...

     @*      
        this seems to not update correctly...

    @Html.HiddenFor(x => Model.err)
    @Html.HiddenFor(x => Model.errField)
    @Html.HiddenFor(x => Model.errMessage)
    @Html.HiddenFor(x => Model.IsMove)

        *@
        <input type="hidden" id="err" value="@Model.err" />
        <input type="hidden" id="errField" value="@Model.errField" />
        <input type="hidden" id="errMessage" value="@Model.errMessage" />
        <input type="hidden" id="IsMove" value="@Model.IsMove" />

    </div>

Then the input fields update correctly. I even created a View Helper to help debug, and in all cases, the Model seems to have correct data in HtmlHelper<TModel> -- I even returned the Model as return Json(model); and the data was fine.

At this point I am running with the work around, but does anybody know why @Html.HiddenFor is dirty.

Update: here is my controller actions

  [HttpPost]
   public ActionResult Index(HomePageModel model)
   {


       // process transaction
       Transactionr transr = new Transactionr();
       transr.Process(model);

       model.err = transr.err;
       model.errField = transr.errField;
       model.errMessage = transr.errMessage;

       return View(model);
   }

Here is my view:

        @model App.Models.HomePageModel
    @{
        ViewBag.Title = "Product Categorizer";
    }
    <form id="formData" method="post" action="/Home/Index">
        @Html.AntiForgeryToken()
        <fieldset>
            <div>

            @Html.HiddenFor(model => model.err)
            @Html.HiddenFor(model => model.errField)
            @Html.HiddenFor(model => model.errMessage)
            @Html.HiddenFor(model => model.IsMove)

            <input type="hidden" id="myerr" value="@Model.err" />
            <input type="hidden" id="myerrField" value="@Model.errField" />

            </div>

           <div class="section group">
                <div class="col span_2_of_2">
                     <div class="message" id ="message">
                         @if (Model.err < 0)
                         {
                             <span style="color: purple;">@Model.errMessage (@Model.err) - (@Model.errField)</span>
                         }
                         else if (Model.err > 0)
                         {
                             <span style="color:red;">@Model.errMessage (@Model.err) (@Model.errField)</span>
                         } else {
                            <span>@Model.errMessage (@Model.err) (@Model.errField)</span>
                         }
                         </div>
                     </div>
            </div>

            <div class="section group" id="workspace">
                  @Html.Partial("_WorkspacePartial", Model)
            </div>
              <div class="section group" id="details">
                  @Html.Partial("_DetailPartial", Model)
              </div>


        </fieldset>
        </form>

Here is my model:

 public class HomePageModel
 {
    public int FromStore { get; set; }

    //  the "To" part of the copy/move transaction
    public int ToStore { get; set; }

    // a list of the copy/move transaction
    public List<int> Details { get; set; }


    // true is move false is copy
    public bool IsMove { get; set; }

    // current message
    public int err { get; set; }
    public int errField { get; set; }
    public string errMessage { get; set; }
like image 859
user799301 Avatar asked Dec 18 '13 12:12

user799301


3 Answers

The default HtmlHelpers behavior (@Html.HiddenFor, etc) is to behave exactly as you have described.

i.e. any changes you make to the ViewModel on a post are actioned, any changes you return from the Post are received by the view, but on re-rendering WITH HTMLHELPERS, the previous Post-values take precedence over the changed ViewModel values.

Want to "fix" this behavior in a quick + dirty way, clear the ModelState.Clear() prior to returning from the HttpPost ActionMethod !

like image 81
joedotnot Avatar answered Nov 20 '22 02:11

joedotnot


As mentioned by joedotnot this is intended behaviour. Another 'quick fix' for this is to code the html for the hidden field and update only the value from the model eg:

<input type="hidden" id="ErrMessage" name="ErrMessage" value="@Model.ErrMessage">

Use the same id and name as your model property and the updated value will be rendered after postback.

like image 28
maxscan Avatar answered Nov 20 '22 03:11

maxscan


I've faced similar problem recently and ended up with writing new simple helper method + 2 overloads. I'm sharing it here in case anybody is still looking for some workaround cause this "feature" is sometimes annoying.

public static class CustomExtensions
{
    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        string text = ExpressionHelper.GetExpressionText(expression);
        string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
        ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;

        if (modelState.ContainsKey(fullName))
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            ValueProviderResult currentValue = modelState[fullName].Value;
            modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
        }
    }
}

Then you just use it as usual from within you view:

@Html.HiddenFor2(m => m.Id)

It worth to mention it works with collections too.

like image 7
Ruslan Georgievskiy Avatar answered Nov 20 '22 04:11

Ruslan Georgievskiy