Why is my mvc3 viewmodel not updating correctly? - asp.net-mvc

I am writing an image upload form using ASP.NET MVC 3.
In the view, please notice that I am displaying #Model.ImagePath as text and as a #Html.TextBoxFor(model => model.ImagePath).
View:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype="multipart/form-data" }))
{
#Html.HiddenFor(model => model.BannerSlideId)
#Html.HiddenFor(model => model.Key)
<p>
Image Path:#(Model.ImagePath)<br />
Image Path in TextBoxFor: #Html.TextBoxFor(model => model.ImagePath)
</p>
<p>
<label class="styled">Upload Slide Image</label>
<input type="file" name="image" />
</p>
<p>
<button type="submit">Save</button> #Html.ActionLink("Back to List", "Index")
</p>
}
I then select an image using the file input, and I submit the form to the Controller.
Controller action:
[HttpPost]
public ActionResult Create(BannerSlide model, HttpPostedFileBase image)
{
if (image != null)
model.ImagePath = image.FileName;
return View("Edit", model);
}
When I debug with a breakpoint, the image.FileName string is assigned to model.ImagePath. However, when I get back to the View I get Two different values from ImagePath.
Results
Image Path:#(Model.ImagePath)<br />
Correctly returns the image filename that was assigned. But,
Image Path in TextBoxFor: #Html.TextBoxFor(model => model.ImagePath)
Incorrectly returns blank!
Any ideas for why this is happening?

You should remove the ImagePath property from ModelState if you intend to modify it in your POST controller action or HTML helpers such as TextBoxFor will first look for a value inside ModelState when binding and then in the model:
if (image != null)
{
ModelState.Remove("ImagePath");
model.ImagePath = image.FileName;
}

The TextBoxFor HtmlHelper method, and all input helper methods for that matter, set the value of the input control to the value held in ModelState rather than immediately binding to the property of the ViewModel. Since the ImagePath present in the POST to your Create method, it is displayed as a blank value on the subsequent Response.

I would suggest to use following attribute on such actions
public class ModelStateFixAttribute : ActionFilterAttribute
{
public ModelStateFixAttribute()
{
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.ViewData.ModelState;
String[] arrKeys = new string[modelState.Keys.Count];
modelState.Keys.CopyTo(arrKeys, 0);
foreach (string key in arrKeys)
{
if (modelState.IsValidField(key))
modelState.Remove(key);
}
}
}

Related

Why the controls are not getting cleared after i pass empty object to the view in MVC?

suppose i have a form like this
#using (Html.BeginForm("submit", "home", FormMethod.Post))
{
#Html.TextBoxFor(x => x.firstname, new { placeholder = "firstname" })
#Html.TextBoxFor(x => x.lastname, new { placeholder = "lastname" })
<input type="submit" value="submit" />
}
and i have the method as follows
public ActionResult Index()
{
return View(new student());
}
[HttpPost]
public ActionResult submit(student _student) {
return View("index", new student());
}
my question is when i make a post request to the same method ie. index by passing an empty object, the controls are getting cleared but when i make a post request to the submit method the value of the controls is not getting cleared. why is that happening? why do i have to clear it using
ModelState.Clear() but not by passing any empty object?

Update a field in view based on the model in ASP.NET MVC

I need to do a calculator in ASP.NET MVC.
For the beginning I want to receive the value from the input field in controller and prefix it with the string "123". At the end I will process the expresion received and return the result.
I have the following model:
namespace CalculatorCloud.Models {
public class Calculator
{
public string nr { get; set; }
} }
In the view I am using the model:
#model CalculatorCloud.Models.Calculator
#{
ViewBag.Title = "Calculator";
}
#using (Html.BeginForm("Index", "Home"))
{
<div>
<div class="header">
#Html.TextBoxFor(m => m.nr, new { #id = "nr"})
<input type="button" id="C" name="C" value="C" />
<input type="button" id="back" name="back" value="<-" />
[...]
<div class="sum">
<input type="submit" value="=" />
</div>
</div>
}
The controller is like this:
namespace CalculatorCloud.Controllers
{
public class HomeController : Controller
{
Calculator model = new Calculator();
public ActionResult Index(string nr)
{
model.nr = "123" + nr;
return View(model);
}
}
}
I have the following problem: when pressing on submit button I am expecting to be displayed on the textbox the value from that was previously in the textbox, prefixed with the string "123".
But now it is kept the value from the textbox without the string "123".
Can someone help me with this?
Thank you! :)
If you want to modify the value of a model property in a postback action you will need to remove it from the ModelState:
public ActionResult Index(string nr)
{
ModelState.Remove("nr");
model.nr = "123" + nr;
return View(model);
}
The reason for this is that Html helpers such as TextBoxFor will first look at the value present in the ModelState and then in your view model property when rendering the value. This is by design.

How can i maintain objects between postbacks?

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>
The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.
You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata
Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");
You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.
Put your entire HTML code under:
#using(Html.BeginForm())
tag.

Get value of view element in MVC that not relevant to model

When I have a DropDownList that relevant to Model of view like this:
#Html.DropDownListFor(model => model.Group.Name, selectList)
I Can retrieve Values in controller as follow:
string SelectedGroupName = collection.GetValue("Group.Name").AttemptedValue;
But now I have a DropDownList that not relevant to model but I need the value of that, this is my new DropDown:
#Html.DropDownList("DDName", selectList)
How can I retrieve the selected value of that in controller? is there any hiddenfield or other thing to pass value from view to controller?
Edit
This is my view:
#model PhoneBook.Models.Numbers
#{
ViewBag.Title = "Delete";
}
<h2>
Move And Delete</h2>
<fieldset>
<legend>Label of Numbers</legend>
<div class="display-label">
Delete Label And Move All Numbers with: #Html.DisplayFor(model =>
model.Title)</div>
<div class="display-field">
To #Html.DropDownList("DDName", selectlist)
</div>
</fieldset>
#using (Html.BeginForm()) {
<p>
<input type="submit" value="Move Numbers And Delete Label" name="MDbtn" />
</p>
}
This is my Controller:
[HttpPost]
public ActionResult Delete(int id, FormCollection collection) {
var result = Request["DDName"];
//Use result
return RedirectToAction("Index");
}
but result set to null, why?
I think this must be work:
[HttpPost]
public ActionResult Delete(int id, FormCollection collection)
{
var dd = collection.GetValue("DDName");
.....
}
I think all you have to do is
In your view:
put #using (Html.BeginForm()) { above the <fieldset>, so the #Html.DropDownList("DDName", selectlist) is inside it.
In your controller:
public ActionResult Delete(int id, FormCollection collection, string DDName)
{ [...] }
And I'm fairly sure MVC3 will automagically give you the selected value as parameter to your controller.
If that does not work, try object DDName in your controller instead.
Think the ddl has to be in your form if you want to pass the value in through the form collection
Your problem is, that your dropdown isn't contained inside the form in your view.
You have to put it after BeginForm:
#using (Html.BeginForm()) {
<div class="display-field">
To #Html.DropDownList("DDName", selectlist)
</div>
<p>
<input type="submit" value="Move Numbers And Delete Label" name="MDbtn" />
</p>
}
Then you have can use FormCollection or a designated parameter. The default Modelbinder will work with both approaches:
ActionResult Action (FormCollection collection, string DDName)
You can easily check those issues with fiddler.

Html Helper to get values from Model State

Asp.Net MVC 3
I seem to have a similar problem as this post answered by Darin Dimitrov. So, Darin if you are reading this, please help :)
asp.net-mvc2 - Strongly typed helpers not using Model?
The problem I have is I am looking for an html helper that will contain the posted value in the modelstate.
For example, if I use an editor for like this:
#Html.EditorFor(model => model.SelectedTags)
I can see the value that was posted. The problem is I need a way to get this value without creating a textbox, I just want the string because I need it in some javascript.
I've tried DisplayFor, but that doesn't contain the posted value:
#Html.DisplayFor(model => model.SelectedTags)
By the way, I don't find this behavior intuitive AT ALL. I spent a few hours debugging ModelStateToTempDataAttribute from MVCContrib thinking it was a bug in their code to Import/Export Model State.
Thanks for any help!
Edit - Added Repro Code
Take these steps to reproduce:
Start project. Property1 should be blank (required), Property2 should have "abc"
Change Property2 to "xxx"
Submit Form (notice ClientValidationEnabled is False)
Form is posted, redirect, load (PRG). Property2 textbox has"xxx" and right below you will see "abc" from DisplayFor.
Controller
[ModelStateToTempData] //From MVCContrib
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//simulate load from db
var model = new FormModel() { MyProperty2 = "abc" };
return View(model);
}
[HttpGet]
public ActionResult Success()
{
return View();
}
[HttpPost]
public ActionResult Index(FormModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Success");
}
else
{
return RedirectToAction("Index");
}
}
}
Model
public class FormModel
{
[Required]
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
}
View
#model MvcApplication4.Models.FormModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>FormModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.MyProperty1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MyProperty1)
#Html.ValidationMessageFor(model => model.MyProperty1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MyProperty2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MyProperty2)
#Html.ValidationMessageFor(model => model.MyProperty2)
</div>
#Html.DisplayFor(model => model.MyProperty2)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Config:
<add key="ClientValidationEnabled" value="false" />
ModelStateToTempData (MVCContrib):
public class ModelStateToTempDataAttribute : ActionFilterAttribute
{
public const string TempDataKey = "__MvcContrib_ValidationFailures__";
/// <summary>
/// When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData.
/// When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var controller = filterContext.Controller;
if(filterContext.Result is ViewResultBase)
{
//If there are failures in tempdata, copy them to the modelstate
CopyTempDataToModelState(controller.ViewData.ModelState, controller.TempData);
return;
}
//If we're redirecting and there are errors, put them in tempdata instead (so they can later be copied back to modelstate)
if((filterContext.Result is RedirectToRouteResult || filterContext.Result is RedirectResult) && !modelState.IsValid)
{
CopyModelStateToTempData(controller.ViewData.ModelState, controller.TempData);
}
}
private void CopyTempDataToModelState(ModelStateDictionary modelState, TempDataDictionary tempData)
{
if(!tempData.ContainsKey(TempDataKey)) return;
var fromTempData = tempData[TempDataKey] as ModelStateDictionary;
if(fromTempData == null) return;
foreach(var pair in fromTempData)
{
if (modelState.ContainsKey(pair.Key))
{
modelState[pair.Key].Value = pair.Value.Value;
foreach(var error in pair.Value.Errors)
{
modelState[pair.Key].Errors.Add(error);
}
}
else
{
modelState.Add(pair.Key, pair.Value);
}
}
}
private static void CopyModelStateToTempData(ModelStateDictionary modelState, TempDataDictionary tempData)
{
tempData[TempDataKey] = modelState;
}
}
you can read these values from modelstate dictionary like
<%:Html.ViewData.ModelState["key"] %>
However, it seems to me that SelectedTags is an enumeration of objects that is displayed for editing when you call EditorFor(model=>model.SelectedTags). in this scenario it is highly unlikely that you get anything by calling Html.ViewData.ModelState["SelectedTags"]. you will have to iterate over the keys in ModelState dictionary instead and check if key begins with SelectedTags prefix and then u can read its value accordinly.
In your Views -> Shared -> DisplayTemplates, create SelectedTags.cshtml
This will be your display template. Inside write something on the lines of
#model YourProject.WebUI.Models.SelectedTags
#for(int i = 0; i < Model.Tags.Count(); i++){
// Assuming that selected tags contains a list of tags.
// Replace <p> with whatever feels suitable
<p>Model.Tags[i]</p>
}
You can then use this display template in your views:
#Html.DisplayFor(model => model.SelectedTags,"SelectedTags")
This should also work:
#Html.DisplayFor(model => model.SelectedTags)

Resources