Pass IEnumerable model to controller through ajax - c#

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>
}

Related

Passing complex type as parameter of action method. MVC

I have a problem with passing data to action method. I created action link in my view in the way shown below:
#model ComplexModel
...
<a href=#Url.Action( "ComplexAction", "ComplexController", new { complex= #Model, str = "string" } )
class="ui-btn">!!!Change all</a>
And action method with to get all passed parameters:
public ActionResult ComplexAction( ComplexModeloAufgabeViewModel complex, string str )
{
return View();
}
String is passed correctly, but complex model is allways null. What is happening here?
You need to post complex types with a form:
using (Html.BeginForm("ComplexAction", "ComplexController")
{
// form fields go here
<input type="submit" value="save" />
}
This will post your complex type as defined by your ViewModel #model ComplexViewModel at the top of your View.
Your action will then read the ViewModel as follows:
public ActionResult ComplexAction(ComplexViewModel model)
{
// do stuff with the form values here
}
You have to use a form in order for the model binder to work
#model SomeComplexViewModel
#using Html.BeginForm("Action", "Controller")
{
// fields for every one of the complex type properties
Html.TextBoxFor(m=>m.FirstProperty)
<input type="submit" value="submit" />
}
And then in the action method you will receive your model

drop down list value is null after posting to controller in ASP.NET MVC

I am new to ASP.NET MVC. I have a simple form with a submit button and an html select element, populated with two items. When the form posts to my controller, all the form values are null. I've even tried doing it with $.POST instead and the id variable I am sending is null when it gets to the controller. Here is my code:
HTML
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
<select id="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(FormCollection form)
{
string option = form["ddlSelect"].ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
It seems nothing in the form is being sent. I also tried this:
JS
$("#btnEnter").click(function (e) {
var optionId = $("#ddlSelect").val(); //this get val correctly
$.post(#Url.Action("SetOptionForUser", "MyController"), optionId);
});
MVC Controller for JS method
**MVC Controller**
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(int optionId) //null
{
string option = optionId.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
What I am doing wrong?
Normal form submit
Your normal form submit should work if your select element name and your http post action method parameter name is same.
<select name="selectedPerson" id="selectedPerson" class="form-control">
<!-- Options goes here -->
</select>
and your action method
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return RedirectToAction("AnotherAction", "AnotherController");
}
You might also consider using the Html.DropDownListFor or Html.DropDownList helper methods to generate the SELECT element from a list of items ( instead of manually writing the loop to render the option items).
Sending data with Ajax
Now if you want this to be ajaxified, you can send the data in either querystring or the request body
$(function(){
$("#btnEnter").click(function (e) {
e.preventDefault();
var optionId = $("#selectedPerson").val();
$.post('#Url.Action("SetOptionForUser", "MyController")', { selectedPerson:optionId});
});
});
Now there is no point in returning a RedirectResult for an ajax call. You might consider returning a json response (indicating whether your action was successful or not) from your action methodo and in the success callback of $.post you can inspect the value and do needed.
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return Json(new {status="success"});
}
And you can check the response in the callback
var optionId = $("#selectedPerson").val();
var url="#Url.Action("SetOptionForUser", "MyController")";
$.post(url, { selectedPerson:optionId}, function(response){
if(response.status==="success")
{
alert("Success");
}
else
{
alert("Some trouble!");
}
});
The only thing that I see missing in your code is the name property of your dropdown.
<select id="ddlSelect" name="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
for your first example your using FormCollection
string option = form["ddlSelect"].ToString();
but your DropDown doesn't have a name property and form collection is using the name property as reference that is why you are getting a null value.
same goes with your second example reference the DropDown name property instead of ID in your Parameter
public ActionResult SetOptionForUser(int myDropDownName) //null
{
string option = myDropDownName.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
The FormCollection in ASP.NET MVC is tied to the name attribute of input elements, add the name attribute to your select HTML element, like this:
<select id="ddlSelect" name="ddlSelect" class="form-control">
Note: The id attribute is how to find things in the DOM and the name attribute is for what gets included/submitted in the form
As for the JavaScript jQuery AJAX POST issue, I believe the issue is that jQuery is making an intelligent guess that your optionId is text. You should create an object literal to hold the JSON data and then stringify that value to send to the controller, like this:
var dataForServer = {
optionId: optionId
};
$.post(#Url.Action("SetOptionForUser", "MyController"),
JSON.stringify(optionId),
null,
json);
The FormCollection in ASP.NET MVC is getting to the name attribute of input elements
so add this attribute name="ddlSelect"
MattoMK,
Please post code for your Model.
Typically, a model will hold all the data required for your webform to run and your post to send the data back. In your view, you should be selecting to a Model field. You seem to have Model.Persons, which is likely an enumerable of a class describing people.
MODEL
You did not describe your model, so a fake one is shown with just two properties, a string to hold the list selection and your list of Persons. Hopefully, your HttpGet Action populates your model correctly.
public class MyModel
{
public string ddSelect { get ; set; }
public List<Person> Persons { get ; set; }
}
HTML (often called the View)
We explicitly tell the world that this view uses this Model.
#model MyModel // Tell MVC what model we are using for this view
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
#Html.DropDownListFor(m => m.ddSelect, Model.PersonList, new {id = "ddlSelect"})
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
Your controller is expecting your view to return a model of type MyModel and thus will try to push the form variables into a copy of your model (if it can). Since your form does not fill out a model property, you get nulls back.
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(MyModel theModel, FormsCollection collection)
{
var selectedName = theModel.ddSelect;
return RedirectToAction("AnotherAction", "AnotherController");
}
This should return the value you selected from the dropdown list. Pay attention to the generated HTML from your site (Press F12 in browser and view source) to see how MVC does things. It's easier to play along than to fight it. In the above POST Action, see the included parameter collection... That holds all of the form variables. It can be gone through to find non model values if required. It is not necessary and can be removed, but poking around in that collection affords a nice view of how data is structured upon POST.
Hope this Helps.

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
}
}

MVC3 : Overriding the Model values in HTTPPost action method

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?

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