MVC3 : Overriding the Model values in HTTPPost action method - c#

I am trying to learn MVC3. I have used TextBoxFor HTML helper control to persist the value over multiple post backs.
Surprisingly the value is persisting, but not reflecting in the view.
My Model class looks like this :
public class FileViewModel
{
public string FileName { get; set; }
public string ValidationMsg { get; set; }
}
My Actions methods in controller looks something like this :
public ActionResult DemoFormElements()
{
return View();
}
[HttpPost]
public ActionResult DemoFormElements(FileViewModel fVM)
{
fVM.FileName = "Overridden Text" + fVM.FileName ;
return View(fVM);
}
My View looks like this :
#using (#Html.BeginForm())
{
if (Model != null)
{
#Html.LabelFor(b => b.FileName)
#Html.TextBoxFor(n => n.FileName, Model.FileName)
}
else
{
#Html.LabelFor(b => b.FileName)
#Html.TextBoxFor(n => n.FileName)
}
<input type="submit" id="SubmitBtn" value="Ok" />
}
When I post back by clicking the OK button, I am able to get the value what I entered in the
textbox, but in the controller I am trying to append that value to "Hi" & expecting the appended value in my view, which is not happening...
I am seeing the value of the control is persisting (whatever I ve entered), but not changing :(
Please help me with giving some clue if its an expected behavior or I m doing any mistake here?

There are a couple of problems with this. You can't overwrite the values in the model during a postback because MVC will override that and retain the old values in order to redisplay them in the case of an error. You also should not, generally speaking, be returning a view from the POST handler, you should use a PRG (Post - Redirect - Get) pattern. That's done so that if the user clicks Refresh in their browser it doesn't POST again. So, having said all that, I would change the controller as follows:
public ActionResult DemoFormElements()
{
var viewModel = new FileViewModel();
if( TempData.ContainsKey( "UpdatedFilename" )
{
viewModel = TempData["UpdatedFilename"];
}
return View( viewModel );
}
[HttpPost]
public ActionResult DemoFormElements(FileViewModel fVM)
{
TempData["UpdatedFilename"] = "Overridden Text" + fVM.FileName;
return RedirectToAction( "DemoFormElements" );
}
This will also simplify your View because you don't have to do that null check for model, you will always have a model.
#using (#Html.BeginForm())
{
#Html.LabelFor(model => model.FileName)
#Html.TextBoxFor(model => model.FileName)
<input type="submit" id="SubmitBtn" value="Ok" />
}

The straight forward solutions to your problem could be:
In controller Post method, use ModelState.Remove("[Mode's Property Name]") before it is assigned new value with the Controller. Here it should be : ModelState.Remove("FileName");
Or Use ModelState.Clear(): this forces the page to forget all previous values & clears all the previous entries.
Or on the view page, change Html.TextBoxFor() to Html.TextBox() for the particular model property.
Or use PRG pattern : (Post-Redirect-Get pattern) as suggested by Craig above.
Choose one among these alternative solutions which fits to your need.

I would return a viewModel for the get action
public ActionResult Index()
{
return View(new FileViewModel());
}
I would simplify the view to this
<span>#Model.FileName</span>
#using (#Html.BeginForm())
{
#Html.LabelFor(m => m.FileName)
#Html.TextBoxFor(m => m.FileName)
<input type="submit" id="SubmitBtn" value="Ok" />
}
Please also read this
MVC2 TextBoxFor value not updating after submit?

Related

How to populate a Model in MVC

So I am trying to populate a model, which needs a userId. So right now, my ActionResult Index method just returns a view which prompts for the user to enter their userId. I need to grab this value and then create the viewmodel and then pass it in back to the view so that way I can do stuff like #Model.blah. I was wondering how I can do that, whether it'd be having two different action result methods, or in general how to populate models when the information you need must be queried first before constructing the viewmodel.
Here is my controller:
public ActionResult Index()
{
// Ask for UserID
return View("~/Views/FingerprintTool/Index.cshtml");
}
public ActionResult Index(int userId)
{
var response = _driver.ListFingerprints(userId);
var model = new FingerprintToolModel()
{
Fingerprints = response.Fingerprints
};
return View(model);
}
And here is my html:
model Models.Tools.FingerprintToolModel
<head>
<script type="text/javascript" src="~/Scripts/FingerprintTool.js"></script>
</head>
<body>
<h1>Fingerprint Tool</h1>
<form id="userIdForm" method="post">
Type in your UserId: <input name="userId" type="number" id="formUserId"/>
<input type="submit"/>
</form>
#if (Model != null)
{
<h1>SUCCESS</h1>
}
</body>
I also have a Javascript file that deals with the submit button be clicked and whatnot.
Here is the Js:
window.onload = function() {
$("#userIdForm")
.submit(function(e) {
e.preventDefault();
if ($("#formUserId").length == 0) {
alert("Invalid UserId");
} else {
listUserFingerprints();
}
});
}
function listUserFingerprints() {
// what to do here
}
Form:
First, update your form. You can use a simple HTML form, or the #Html.BeginForm() helper, like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
By default, your Html.BeginForm creates all the necessary elements and form action etc. If you prefer, you can still use standard HTML. Both do the same job.
Note the AntiForgeryToken. This is so useful when using POST form data (and even get, if you really must).
In your Controller, you can then check against this AntiForgeryToken, to protect against malicious posts or data being injected - see below.
Controller:
Create another method in your Controller, with the same name as your existing one; decorate it with the [HttpPost] attribute.
If you are using an AntiForgeryToken, you need to decorate the method with [ValidateAntiForgeryToken] also.
Like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
// Then return this model to the view:
return View(model);
}
Notice the parameter we are looking for in the method signature matches the name attribute of the input in your form.
MVC's DefaultModelBinder is able to make the connection between the two and map the value(s) of any parameters to form values.
You can also check if your model is null (for example, that userId doesn't exist) and then return an error to the page if so.
I like to use validation errors, but you can also use ViewBag or any other kind of method.
You can do a check and add an error, like this:
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
This will add a "generic" model error to the view data, which you can then process in the view. More on that, below.
View:
In order to support your view displaying your model, you need to insert an #model statement at the top of your cshtml file.
Like so:
#model MyNameSpace.Models.User
This tells the view engine what Model type to expect from the Controller. In this case I have used User, but it would be whatever your class is called.
Be sure to use the fully-qualified namespace of your class in order to access it.
Then, in your HTML code, you can access the properties of your model using #Model.YourProperty.
Like this:
...
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints){
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
...
As you can see, this loops through the FingerPrints (or whatever the property is called on your model object) and prints them out in a <ul>. This is to give you an idea of how to access the data from your model.
It is a good idea to create strongly-typed views like this - as this is the WHOLE idea of MVC in the first place :)
Don't forget to add an if check around the part of the page you're access the #Model data (otherwise you will get a NullReferenceException):
#if (Model != null){
<div id="modelDataInHere">
... // Display the data from your model, nice and pretty-like
</div>
}
Note the casing difference between the declaration and printing the values to the HTML output. I won't go into detail, but this is correct and necessary. Don't mix the two up.
Over to that "Validation Error" (AddModelError) we added in the Controller.
If you're using the Html.BeginForm() helper - like in the example - then you can add a Summary in the form, somewhere. When ModelState.AddModelError() is called in your controller, this populates the view data and this error can be displayed on the page.
Like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
// This puts any "generic" error messages at the top of the form
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Original form fields
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
No need for any Ajax or any JavaScript in here. You're simply using MVC for what it was designed in the first place.
You can use Ajax, however.
I would encourage you to read into it. There is also a similar helper: Ajax.BeginForm(), which renders the form as an Ajax one.
A bit of JS set up is required. And your controller action could return a PartialView, rather than a full-blown one.
Have a read up on using Ajax form posts here: http://eliot-jones.com/2014/09/mvc-ajax
The finished article
Time is marching on, and this post is getting longer.
But for the sake of clarity, your view and controller should look something like below. (I've stuck in a made-up class, so you can see the where the properties come from):
View
#model YourNameSpace.Models.User
... all your other code in here...
<div>
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
</div>
#if (Model != null)
{
<!-- If there is a model present, display the data for it: -->
<div>
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints)
{
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
</div>
}
Controller
public ActionResult Index()
{
// This is your standard "GET" request for "Index"
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
}
Sample model
public class User
{
public string Username { get; set; }
public string FullName { get; set; }
public List<FingerPrint> FingerPrints { get; set; }
}
Sincerely hope this helps you, and I wish you the best in your project.
Any questions, please feel free to ask :)
Seems like you need to work with json and AJAX method:
[HttpPost]
public ActionResult BasePage(int userId)
{
// user ID is binded to userId variable, based on the input name
var model = populate(userId);
// Remember to return the model to the view
return Json(model);
}
The Javascript for calling this would be:
function listUserFingerprints() {
$.post("/FingerprintTool/BasePage", {userId: $("#formUserId").val() }, function(model) {
console.log(model); // do whatever you want with model here
}
}

Pass IEnumerable model to controller through ajax

I have a view where set of Images relating to certain album will be edited to change their description and to make them cover photo.
EditImageViewModel.cs
public class EditImageViewModel
{
public int ImageId{get;set;}
public string ImageUrl{get;set;}
public string ImageDes{get;set;}
public bool IsCoverPic{get;set;}
}
From one of the controller ActionResult, I return model to view as
return PartialView("_ImageEditView",model);.
model returned above is List<EditImageViewModel>
Now in the view I display it as below:
_ImageEditView.cshtml
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#foreach(var image in Model)
{
<img src="#image.ImageUrl"/>
#Html.TextAreaFor(m=>image.ImageDes)
#Html.RadioButtonFor(m=>image.IsCoverPic)
}
<button type="submit" class="update" value="Update"></button>
}
I have an ajax piece of code which calls ActionResult as below:
$('.update').on('click',function(e){
e.preventDefault();
var url=$(this).closest('form').attr('action');
var formdata=$(this).closest('form').serialize();
$.ajax({
url:url,
data:formdata,
type:'POST',
dataType:'JSON',
success:function(resp){
},
error:function(resp){
}
})
});
My Controller ActionResult goes like this:
[HttpPost]
public ActionResult UpdateImage(List<EditImageViewModel> model)
{
//actions to be performed
}
My problem here is no matter what, the model will always be null in the controller, when the post occurs. The formdata with have the data when checked in browser console. But it isn't get passed to controller method.
After going through few posts I learnt that it creates duplicate ids for multiple records in List when foreach is used. So I changed it to for loop as below:
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#for(int i=0;i<Model.Count();i++)
{
<img src="#Model[i].ImageUrl"/>
#Html.TextAreaFor(m=>m[i].ImageDes)
#Html.RadioButtonFor(m=>m[i].IsCoverPic)
}
<button type="submit" class="update" value="Update"></button>
}
But still the model is null when received in controller. I also tried using serializeArray instead of serialize, but it did not help much. I referred few posts like Post 1, Post 2 etc., but none of them solved this problem.
Ultimately, how can I pass this list of Model from ajax to
controller?
If you want to post a collection to the controller, you will need to add indexes to your name. Otherwise MVC does not know how to bind the model.
For example
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#for(int i=0;i<Model.Count();i++)
{
<img src="#Model[i].ImageUrl"/>
<textarea name="#string.Format("ImageDes[{0}]", i)">#Model[i].imageDes</textarea>
<input type="radio" name="#string.Format("IsCoverPic[{0}]", i)" value="#Model[i].IsCoverPic" >
}
<button type="submit" class="update" value="Update"></button>
}

ViewModel does not update Model correctly on submit

I am trying to use a strongly-typed ViewModel and find that when I replace the Model class with a ViewModel, changes are not submitted correctly from the Edit template. Whereas just using the straight Model class in the View, edits happen successfully. My model class is MaterialDefinition and the ViewModel class is MaterialDefinitionViewModel as shown below. I've updated the edit template correctly to reference the ViewModel but as I say editing just does not work. I'm using VS2013 and MVC4. Any ideas, someone?
First the ViewModel class...
public class MaterialDefinitionViewModel
{
// Properties
public MaterialDefinition Definition { get; private set; }
// Constructor
public MaterialDefinitionViewModel(MaterialDefinition def)
{
Definition = def;
}
}
and now some code from the View...
<div class="editor-field">
#Html.EditorFor(model => model.Definition.mdDescription)
#Html.ValidationMessageFor(model => model.Definition.mdDescription)
</div>
<p>
<input type="submit" value="Save" />
</p>
Finally discovered the answer to this after much searching...
[HttpPost]
public ActionResult Edit(string id, FormCollection collection)
{
MaterialDefinition def = repository.GetMaterialDefinition(id);
UpdateModel(def, "Definition");
//UpdateModel(def);
repository.Save();
return RedirectToAction("Details", new { id = def.mdID });
}
Turns out there is a not-so-obvious overload for the UpdateModel method that takes a prefix "Name" property. This is the name of the original encapsulated model class inside the ViewModel. Corrected code fragment from the Controller's Edit Post method is shown above.

TextBoxFor is not refreshing after postback

I'm probably making a stupid mistake somewhere. I would appreciate your help in the following.
I have sample MVC3 application with a single editable field that is displayed to user with TextBoxFor method. In the Index(POST) action I change the value but it still remains the same. What am I doing wrong?
My code:
Model:
public class TestModel
{
public string Name { get; set; }
}
View:
using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Name)
<input type="submit" />
}
Controller:
public ActionResult Index()
{
return View("Index", new TestModel() { Name = "Before post" });
}
[HttpPost]
public ActionResult Index(TestModel model)
{
model.Name = "After post";
return View("Index", model);
}
If I replace TextBoxFor with TextBox or DisplayTextFor then it works correctly.
I believe you must call ModelState.Clear() inside your [HttpPost] action, before you set the new value.
According to this answer, which has a very good explanation: How to update the textbox value #Html.TextBoxFor(m => m.MvcGridModel.Rows[j].Id)
See this too: ASP.NET MVC 3 Ajax.BeginForm and Html.TextBoxFor does not reflect changes done on the server
Although it seems you're not using Ajax.BeginForm, the behavior is the same.
Including an example as suggested by #Scheien:
[HttpPost]
public ActionResult Index(TestModel model)
{
ModelState.Clear();
model.Name = "After post";
return View("Index", model);
}

sending a model in mvc3 using html.beginform

I have an HttpPost and HttpGet version of the action method Rate() :
http://pastebin.com/embed_js.php?i=6x0kTdK0
public ActionResult Rate(User user, Classified classified)
{
var model = new RatingModel
{
CurrentUser = user,
RatedClassified = classified,
};
return View(model);
}
[HttpPost]
public ActionResult Rate(RatingModel model)
{
model.RatedClassified.AddRating(model.CurrentUser, model.Rating);
return RedirectToAction("List");
}
The view that HttpGet Rate() returns:
#model WebUI.Models.RatingModel
#{
ViewBag.Title = "Rate";
}
Rate #Model.RatedClassified.Title
#using(Html.BeginForm("Rate","Classified", FormMethod.Post))
{
for (int i = 1; i < 6; i++)
{
Model.Rating = i;
<input type="submit" value="#i" model="#Model"></input>
}
}
I'm trying to find out to send a Model through the Form to the Post method, and my thinking was that the value "model" in the submit button's tag would be the parameter to do so, however null is being passed through if i breakpoint inside of the Post method. The for loop is trying to create 5 buttons to send the proper rating.
Thanks
Them model binding works on the name attribute as #Ragesh suggested you need to specify the name attributes matching the RatingModel properties in the view. Also note that the submit button values dont get posted to the server, there are hacks through which you can achieve that, one way is to include a hidden field.
Also in your provided code the loop runs six times and at the end Model.Rating will be equal to 5 always... what are you trying to achieve?. Say for example you have a model like
public class MyRating{
public string foo{get;set;}
}
in your view
#using(Html.BeginForm("Rate","Classified", FormMethod.Post))
#Html.TextBoxFor(x=>x.foo) //use html helpers to render the markup
<input type="submit" value="Submit"/>
}
now your controller will look like
[HttpPost]
public ActionResult Rate(MyRating model)
{
model.foo // will have what ever you supplied in the view
//return RedirectToAction("List");
}
hope you will get the idea
I think there are two things you need to fix:
The input tag needs a name attribute
The name attribute should be set to model.Rating

Categories

Resources