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" />
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.
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");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With