MVC3, Get updated form data from a list - c#

I have a strongly-typed view, with a list of custom objects in the model.
In the view I display textboxes for every object in the list :
#using (Html.BeginForm("SaveData", "Localization", FormMethod.Post))
{
foreach (YB.LocalizationGlobalText m in Model.GlobalTexts)
{
#Html.Label(m.LocalizationGlobal.Name)
#Html.TextBoxFor(model => m.Text)
<br />
}
<input type="submit" value="Save" />
}
Now how would I get the updated data from the textboxes in my model.
I can see in the formcollection the updated data is there:
[HttpPost]
public virtual ActionResult SaveData(FormCollection form)
{
// Get movie to update
return View();
}
form["m.Text"] = "testnewdata1,testnewdata"
But how do I get this mapped to the model, so I have the updated values for each object.
Or how can I get it cleanly from the formcollection, something like this .. form[someid]["m.Text"]
Edit:
I also tried passing the model as a parameter, but the model data is empty.
[HttpPost]
public virtual ActionResult SaveData(LocalizationModel model, FormCollection form)
{
// Get movie to update
return View();
}
When I look into the model: model.GlobalTexts = null

[HttpPost]
public virtual ActionResult SaveData(int movieId, FormCollection form)
{
// Get movie to update
Movie movie = db.Movies.Where(x => x.Id == movieId);
// Update movie object with values from form collection.
TryUpdateModel(movie, form);
// Do model validation
if (!ModelState.IsValid)
return View();
return View("success");
}
Edit See this question I asked a while back: How to use multiple form elements in ASP.NET MVC
Lets say you have a view like this:
#model IEnumerable<CustomObject>
#foreach (CustomObject customObject in Model)
{
<div>
#Html.TextBox(customObject.CustomProperty);
<!-- etc etc etc -->
</div>
}
Refactor it like this:
#model IEnumerable<CustomObject>
#for (int count = 0; count < Model.Count(); count++)
{
<div>
<!-- Add a place for the id to be stored. -->
#Html.HiddenFor(x => x[count].Id);
#Html.TextBoxFor(x => x[count].CustomProperty);
<!-- etc etc etc -->
</div>
}
Now in your action method do this:
public virtual ActionResult SaveData(IEnumerable<CustomObject>)
{
// You now have a list of custom objects with their IDs intact.
}
It's even easier than that if you use editors, but I'll let you figure those out for yourself as they are super simple. The accepted answer in the question I linked shows an example.
NOTE: you can substitute IList for IEnumerable if you need to.

If I understand your question correctly, you can simply use your viewmodel as a parameter of SaveData, and it will map it automatically:
[HttpPost]
public virtual ActionResult SaveData(ViewModelType viewmodel)
{
// Get movie to update
return View();
}

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

How do I display all elements of a ViewModel in a view

I know that you can do #Html.DisplayForModel and ValueForModel, is there anything similar the viewModel? I want to display all the property names and the property values for the elements in my viewmodel
My regualr ValueFor is not working either
Heres what I have so far...although, its not working:
#model BookStore.ViewModels.CheckOutViewModel
#using (Html.BeginForm())
{
<h2>Checkout Summary</h2>
<fieldset>
<legend>Payment Information</legend>
#Html.ValueFor(m => m.CreditCard1)<br />
#Html.ValueFor(m => m.CreditCardType1) <br />
#Html.DisplayNameFor(Model => Model.CreditCard1)
#Html.DisplayFor(Model => Model.CreditCard1)
#Html.
</fieldset>
<fieldset>
<legend>Shipping Information</legend>
#Html.ValueForModel()
</fieldset>
}
Controller Code:
public ActionResult Complete(int? id)
{
return View(id);
}
Edit: I guess my real question is how to get ANY property from my view model to show its value
You're passing an Id of the object (model) to your view instead of the model itself. You should have somthing like this in your controller:
public ActionResult Complete(int? id)
{
CheckOutViewModel model = GetModel(id);
return View(model);
}
Where GetModel is some function where you retrieve the CheckOutViewModel based on the id.
EDIT: Looking at your controller you are just returning an integer not your model
You could do something like this if you really are new to this:
public ActionResult Complete(int? id)
{
if(int != null && int != 0){ //Makes sure int isn't empty or 0
var allModelData = dataContext(); //Get all the data in your dataContext
var myModel = allModelData.FirstOrDefault(x => x.id == id); //This gets the
//model that matches the ID
CheckOutViewModel viewModel = myModel; //Populate it into your viewmodel
return View(viewModel); //Return your viewmodel
}
return View();
}
This isn't the most efficient way of doing things but will get you started with some data.
I would also recommend using Model.PropertyName as ValueFor does a simple render which ignores any templates (Equivalent of calling String.Format)
#Model.CreditCard1<br />
#Model.CreditCardType1 <br />

How to pass id and name of a #Html.CheckBox via form collection in MVC3

This is my html-
<td>
#{
IEnumerable<SelectListItem> Brands = ViewBag.GetBrands;
foreach (var item in Brands)
{
#Html.CheckBox(item.Text, false)
<label>#item.Text</label><br />
}
}
</td>
Im Posting this controller as JSON data (form collection). How can i get checkbox's text and value in form collection data in controller?
How can i get checkbox's text and value in form collection data in controller?
The correct approach is to use a view model instead of this IEnumerable<SelectListItem>. So basically your model could look like this:
public class BrandViewModel
{
public string Text { get; set; }
public bool Checked { get; set; }
}
and then add a property to your main view model (the one your view is strongly typed to) of type IList<BrandViewModel>:
public IList<BrandViewModel> Brands { get; set; }
and then it's pretty easy:
<td>
#for (var i = 0; i < Model.Brands.Count; i++)
{
#Html.CheckBoxFor(x => x.Brands[i].Checked)
#Html.LabelFor(x => x.Brands[i].Checked, Model.Brands[i].Text)
#Html.HiddenFor(x => x.Brands[i].Text)
}
</td>
and finally you can get rid of any weakly typed FormCollection from your controller action and simply take the view model:
[HttpPost]
public ActionResult SomeAction(IList<BrandViewModel> brands)
{
...
}
or if there are also other properties you need to pass your controller action may take the main view model:
[HttpPost]
public ActionResult SomeAction(MainViewModel model)
{
// the model.Brands collection will be automatically bound here
...
}
I managed to get ID by -
#Html.CheckBox(item.Text, false, new {item.Value})
First You have to perform post back to server.
#using (Html.BeginForm("actionname", "controller",
FormMethod.Post))
//your code
#{
IEnumerable<SelectListItem> Brands = ViewBag.GetBrands;
foreach (var item in Brands)
{
#Html.CheckBox(item.Text, false)
<label>#item.Text</label><br />
}
}
<input type="submit" class="k-button" value="Submit" id="btnSubmit" name="btnSubmit" />
}
Now in the controller you will get the values using form collection
[HttpPost]
public ActionResult actionName( FormCollection collection)
{
collection.keys["checkbox"].value ... your code
}

Update a Model and a Collection of Models on a Single View (MVC4)

I've been working on an MVC 4 Application and have run into a problem when attempting to update Models in a ViewModel.
My ViewModel (detailed below) contains one ComplexObjectOne and a List<ComplexObjectTwo>.
My GET ActionResult successfully populates the ViewModel from a database and everything displays correctly on my View.
The problem is encountered when attempting to pass the ComplexObjectOne and List<ComplexObjectTwo> to the POST ActionResult.
The ComplexObject is passed correctly but everything I've tried fails pass the List<ComplexObjectTwo> collection.
My ComplexModelOne Model
public class Test
{
public int Id {get;set;}
public string Result {get;set;}
public virtual ICollection<TestResult> TestResults {get;set;}
}
My ComplexModelTwo Model
public class TestResult
{
public int Id {get;set;}
public string Result {get;set;}
public string Comment {get;set;}
public virtual Test Test{get;set;}
}
My ViewModel
public class TestingViewModel
{
public TestingViewModel()
{
if(TestResults == null)
{
TestResults = new List<TestResult>();
}
}
public Test Test {get;set;}
public IEnumerable<TestResult> TestResults {get;set;}
}
My Edit() GET ActionResult
public ActionResult Edit(int id = 0)
{
var viewModel = new TestingViewModel();
Test test = testRepo.GetTestById(id);
var results = test.TestResults;
viewModel.Test = test;
viewModel.TestResults = results;
return View(viewModel);
}
My Edit() POST ActionResult
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(TestingViewModel model)
{
// do update - left out for brevity
}
My Edit.cshtml View
#model Namespace.Models.ViewModels.TestingViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.Test, "TestHeader")
<table>
<tr>
<th>Test</th>
<th>Result</th>
<th>Comment</th>
</tr>
#Html.EditorFor(model => model.TestResults, "TestResults")
</table>
<input type="submit" value="Update"/>
}
Within my View I do use a couple of EditorTemplates to display the property fields.
Any assistance, comments, or suggestions will be much appreciated. I'd like to be able to accomplish updating these entities on a single page instead of multiple pages which I resorted to in the Create() steps.
Thank you,
Patrick H. (stpatrck)
Replace:
#Html.EditorFor(model => model.TestResults, "TestResults")
with:
#Html.EditorFor(model => model.TestResults)
and then rename your EditorTemplates/TestResults.cshtml editor template to EditorTemplates/TestResult.cshtml (notice the missing s) and inside replace the model declaration from:
#model IEnumerable<TestResult>
to:
#model TestResult
Now obviously this will lead to getting rid of any for or foreach loops you might have written in this editor template because now ASP.NET MVC will automatically invoke the template for each element of the collection.
So for example:
#foreach (var item in Model)
{
#Html.EditorFor(x => item.SomeProperty)
}
will simply become:
#Html.EditorFor(x => x.SomeProperty)
Now look at the generated markup and notice the difference in the names of your input fields. Before you had:
<input type="text" name="item.SomeProperty" value="foo" />
and now you have:
<input type="text" name="TestResults[0].SomeProperty" value="foo" />
Now when you submit the form to the POST action the default model binder will be able to successfully bind the collection because now the naming convention is respected. You can read more about this convention in the following blog post.
Also you have circular references in your object graph which cannot be successfully serialized and model bound. You should use view models in order to break this circular dependency.

Categories

Resources