Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Razor will not render hidden accurate PK in HiddenFor

Running into a weird problem in my ASP MVC 4 site. If the user opens the create form and attempts to add an agent to our database, we naturally run validation on the fields first. If there is an error, we save the agent in incomplete status, and redirect the user back to the create page.

The error comes when the user attempts to re-save the agent. On the first post-back, the agent is saved into the database and a PK is generated. On the second post-back, however, the PK is being sent to the server with a value of 0 instead of what was just auto-generated.

I've added a HiddenFor on the Create view, however this renders with a value of 0 each and every time.

I've also stepped through the code to make sure that the PK is being generated after the .Save is called, is still present when return View is called and also ensured that the Model.ID property contains the same value when the view is being rendered.

Regardless, if I right click the page and view the source, the hidden field renders like so:

<input data-val="false" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="0" />

Model

public partial class AgentTransmission
{
    public int ID { get; set; }
    .
    .
    .
}

View

@model MonetModelFromDb.Models.AgentTransmission

@{
    ViewBag.Title = "Create new Agent";
}

@Html.HiddenFor(model => model.CreatedDate, new { data_val = "false" })
@Html.HiddenFor(model => model.CreatedOperator, new { data_val = "false" })
@Html.HiddenFor(model => model.ReferenceNumber, new { data_val = "false" })
@Html.HiddenFor(model => model.Region, new { data_val = "false" })
@Html.HiddenFor(model => model.INDDist, new { data_val = "false" })
@Html.HiddenFor(model => model.LastChangeDate, new { data_val = "false" })
@Html.HiddenFor(model => model.LastChangeOperator, new { data_val = "false" })
@Html.HiddenFor(model => model.EditTaxId, new { data_val = "false" })
@Html.HiddenFor(model => model.ParentId, new { data_val = "false" })
@Html.HiddenFor(model => model.IsSubstat, new { data_val = "false" })
@Html.HiddenFor(model => model.ID, new { data_val = "false" })

Rendered HiddenFor Section

<input data-val="false" data-val-date="The field CreatedDate must be a date." id="CreatedDate" name="CreatedDate" type="hidden" value="" />
<input data-val="false" id="CreatedOperator" name="CreatedOperator" type="hidden" value="" />
<input data-val="false" id="Region" name="Region" type="hidden" value="NM-834" />
<input data-val="false" id="INDDist" name="INDDist" type="hidden" value="834" />
<input data-val="false" data-val-date="The field LastChangeDate must be a date." data-val-required="The LastChangeDate field is required." id="LastChangeDate" name="LastChangeDate" type="hidden" value="4/8/2015 10:43:30 AM" />
<input data-val="false" id="LastChangeOperator" name="LastChangeOperator" type="hidden" value="TYPCLS" />
<input data-val="false" data-val-required="The EditTaxId field is required." id="EditTaxId" name="EditTaxId" type="hidden" value="False" />
<input data-val="false" data-val-number="The field ParentId must be a number." id="ParentId" name="ParentId" type="hidden" value="" />
<input data-val="false" data-val-required="The IsSubstat field is required." id="IsSubstat" name="IsSubstat" type="hidden" value="False" />
<input data-val="false" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="0" />

Controller

    [HttpPost]
    [MonetAuthorize]
    public ActionResult Create(AgentTransmission agenttransmission, bool andAddAgent = false)
    {
    .
    .
    .
        //Determine if this is first POST or not
        if (agenttransmission.ID > 0)
        {
            db.Entry(agenttransmission).State = EntityState.Modified;
        }
        else
        {
            db.AgentTransmission.Add(agenttransmission);
        }
        db.SaveChanges();

        //Send back to view if errors pressent
        if (!String.IsNullOrWhiteSpace(errorMsg))
        {
            return View(agenttransmission);
        }           
    }

EDIT

If I remove the HiddenFor helper and simply cut/paste the rendered input tag, I'm able to capture the corret PK value. However, this is a little hacky so I was hoping to find a more elegant solution (if possible)

<input data-val="false" id="ID" name="ID" type="hidden" value="@Model.ID" />
like image 967
NealR Avatar asked Apr 08 '15 18:04

NealR


People also ask

What is HiddenFor in MVC?

HiddenFor<TModel,TProperty>(HtmlHelper<TModel>, Expression<Func<TModel,TProperty>>) Returns an HTML hidden input element for each property in the object that is represented by the specified expression.


1 Answers

Variations on this question are asked here frequently. Basically it boils down to the ModelState object and the fact that its values override values on the actual model for the view. When you post the form, 0 is set in the ModelState object for your ID property. In the post action, you save the entity, which causes its ID property to be updated, but 0 is still in ModelState. When you return the view, 0 is set as the value for ID again, because, again, that's what's in ModelState.

The reason why it works this way is best explained by an example. Let's say you have a form where you're editing an existing entity, which has a Name property that's set to "Foo". The user changes this in the form to "Bar" and then posts the form. However, they neglected to fill in a required field, so the error prevents their update from being saved. What should happen at this point? If we use the model value, the Name field will be reset to "Foo". However, if ModelState is used, the Name field retains the user's modification of "Bar". In the latter case, they merely fix the error and post again. In the former, they must remake all the changes they made to the form previously, which would obviously be a very poor user experience.

Now, as to how to fix this. The best way is to follow the PRG pattern (Post-Redirect-Get). If the submission is good, and you saved the changes successfully, then don't return the view, even if you want the user to be able to immediately make additional changes. If you want that, simply redirect back to the same action, but the redirect process is enough to clear the ModelState so that user is now interacting with the updated model pulled fresh from the database.

If that's not doable, then you can simply clear the ModelState. I would advise against clearing it completely, as you can cause some very real user frustration as detailed in the example above. If you really can't do a redirect, then try to only clear the values in ModelState you really need to.

ModelState.Remove("ID");
like image 111
Chris Pratt Avatar answered Nov 02 '22 07:11

Chris Pratt