I don't understand why a variable that I am setting on the model is coming down to the view, but not coming back up. Its 'feels' like a strange bug in HiddenFor()
I've written a simple example that reproduces the problem:
Model:
public class SampleModel
{
public string SpecialString { get; set; }
public string FileString { get; set; }
}
View:
@model FormResubmitTest.Test.SampleModel
....
@using (Html.BeginForm())
{
@Html.ValidationSummary(false)
if (@Model.FileString != null)
{
<p>@Model.FileString file exists</p>
}
<div>
@(Html.Kendo().Upload()
.Name("uploadDocument")
.Multiple(false)
.ShowFileList(true)
.Messages(o => o.Select("Select File To upload"))
)
</div>
@Html.HiddenFor(model => model.FileString)
@Html.TextBoxFor(model => model.SpecialString)
<input type="submit" name="submit" value="Submit" />
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new SampleModel(){});
}
[HttpPost]
public ActionResult Index(SampleModel model, HttpPostedFileBase uploadDocument)
{
if (uploadDocument != null)
{
model.FileString = SaveToTemporaryFile(uploadDocument);
}
if (model.SpecialString != "Special")
{
ModelState.AddModelError("SpecialString", "Special string was not special");
}
if (uploadDocument == null && model.FileString == null)
{
ModelState.AddModelError("FileString", "You have not uploaded a file");
}
if (ModelState.IsValid)
{
return RedirectToAction("Success");
}
return View(model);
}
public string SaveToTemporaryFile(HttpPostedFileBase file)
{
if (file == null)
{
return null;
}
var path = Path.GetTempPath();
var folder = Path.Combine(path, Guid.NewGuid().ToString());
Directory.CreateDirectory(folder);
var fileName = file.FileName;
fileName = System.IO.Path.GetFileName(fileName) ?? "file.txt";
var fullFileName = Path.Combine(folder, fileName);
using (FileStream fileStream = System.IO.File.Create(fullFileName, (int)file.InputStream.Length))
{
byte[] bytesInStream = new byte[file.InputStream.Length];
file.InputStream.Read(bytesInStream, 0, bytesInStream.Length);
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
}
return fullFileName;
}
}
To see error:
- Upload a file
- Enter a string that is not "Special"
- Hit Submit
- It will now display the FileString correctly
- Enter "Special"
- Hit Submit
- The function Index will have a model with a blank FileString
I dont undertand why on the second call, the model has a blank filestring. Now if I look at the hidden for generated code its quite clear, the value is blank!
<input id="FileString" name="FileString" type="hidden" value="" />
I've put the full below, but why on earth is its value blank!!? We can see from the generated message; that the server is aware of it at generation.... I am so confused!
---EDIT---
I can make this work by doing this:
<input id="FileString" name="FileString" type="hidden" value="@Model.FileString" />
instead of
@Html.HiddenFor(model => model.FileString)
but it seems wrong that I have to do it like this
Does MVC (or the Html library I should say) somehow remember the original posted values? and uses them in the "Fors"
The behavior your seeing is by design. All the
HtmlHelper
methods that generate form controls (exceptPasswordFor()
) use the value fromModelState
(if they exist), then from theViewDataDictionary
, and then from the model property.When you first generate the view, no values have been added to
ModelState
, and the value ofFileString
isnull
so it generates<input ... value="" />
When you submit the form, the values of each property in your model are added to
ModelState
by theDefaultModelBinder
(in the case ofFileString
, its value isnull
). You then modify the value ofFileString
and return the view.The
HiddenFor()
method now finds a value ofnull
inModelState
and again generates<input ... value="" />
(setting the value in the POST method does not override the value inModelState
).If you want to return a different view, then you should be following the PRG pattern and redirecting, however you can solve this by removing the value from
ModelState
so theHiddenFor()
method uses the value from the model.You can clear all values from
ModelState
usingor remove
ModelState
for just one property using