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

33,606

Solution 1

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 !

Solution 2

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.

Solution 3

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.

Solution 4

I think you should be using them like this instead:

@Html.HiddenFor(x => x.Err)
@Html.HiddenFor(x => x.ErrField)
@Html.HiddenFor(x => x.ErrMessage)
@Html.HiddenFor(x => x.IsMove)

Without seeing your model, I am assuming it looks something like this:

public class ErroViewModel
{
  public string Err { get; set; }
  public string ErrField { get; set; }
  public string ErrMessage { get; set; }
  public bool IsMove { get; set; }
}

If not it should be similar with public properties as above.

Update

In your get do you have the following?

public ActionResult Index(HomePageModel model)
{
   var model = new HomePageModel();
   return View(model);
}

I would also change your form from this:

 <form id="formData" method="post" action="/Home/Index">

To this:

@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
  // rest of form
}
Share:
33,606

Related videos on Youtube

user799301
Author by

user799301

Updated on July 09, 2022

Comments

  • user799301
    user799301 almost 2 years

    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; }
    
  • user799301
    user799301 over 10 years
    My Model is using public properties. In terms of the expression, I've tried everything including what you suggest, and it doesn't seem to have any impact.
  • user799301
    user799301 over 10 years
    Yes, in my get am creating an instance of HomePageModel.. as for the form.. I am doing some form manipulation with jquery so I've coded it like that.
  • hutchonoid
    hutchonoid over 10 years
    Hmmm, not sure what the problem could be as it all looks good for me. Still an issue?
  • Mat J
    Mat J about 10 years
    Its the ModelState that is causing it. See the other answer.
  • 321X
    321X almost 8 years
    This is very dirty because all the Validation messages are cleared then as well, so be aware!
  • agrath
    agrath over 7 years
    I thought I was going crazy.
  • Marc Roussel
    Marc Roussel over 6 years
    I don't get it. There's no Required attribute on any properties of the model. Why returning the view was getting the HiddenFor empty for ONE property ? After doing the ModelState.Clear I was getting the value all right !!!
  • Kiquenet
    Kiquenet over 5 years
    WHY _ clear the ModelState.Clear() prior to returning from the HttpPost ActionMethod_ ?
  • Kiquenet
    Kiquenet over 5 years
    Why not use [HttpPost] attribute ?
  • hutchonoid
    hutchonoid over 5 years
    @Kiquenet I would yes, it was just missing from the answer.
  • Rob10e
    Rob10e almost 5 years
    This worked like a charm, thanks!
  • Nick
    Nick over 4 years
    Odd behavior (still persists in Razor Pages). It seems to occur because By default, the validation system treats non-nullable parameters or properties as if they had a [Required] attribute. according to the docs (docs.microsoft.com/en-us/aspnet/core/mvc/models/…), but even a single string type (nullable) model property used as hidden-input (via asp-for) is affected by this behavior, and its value is silently reverted to the previous value.
  • KyleMit
    KyleMit almost 4 years
    This looks like it was already covered it maxscan's answer posted two years earlier