How to post back a list of object to the controller in MVC [duplicate] - asp.net-mvc

This question already has answers here:
MVC Form not able to post List of objects
(3 answers)
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 6 years ago.
I have an object model and I am passing it to the view so that user can input their comments for some of the object's properties as a part of a survey.
I am able to receive what the user has entered for an object if I am only rendered one single object to the view. However, when I want to render multiple (a list) of objects to the view then I receive a null list of objects when the user click on the submit form.
Please see my code below:
This is my object model
public class SurveyViewModel
{
public string Name { get; set; }
public double PV { get; set; }
public double QtyUsePerMonth { get; set; }
public double TotalPVPerMonth { get; set; }
}
This is my view where I render the list of object
#model IEnumerable<WebApplication4.Models.SurveyViewModel>
#{
ViewBag.Title = "Survey";
}
<h2>Survey</h2>
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "form1" }))
{
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.PV)
</th>
<th>
#Html.DisplayNameFor(model => model.QtyUsePerMonth)
</th>
<th>
#Html.DisplayNameFor(model => model.TotalPVPerMonth)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
#Html.HiddenFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.PV)
#Html.HiddenFor(modelItem => item.PV)
</td>
<td>
#Html.TextBoxFor(modelItem => item.QtyUsePerMonth)
</td>
<td>
#Html.TextBoxFor(modelItem => item.TotalPVPerMonth)
</td>
</tr>
}
</table>
<input type="submit" value="Submit">
}
And this is my HttpPost method
[HttpPost]
public ActionResult Survey(List<Models.SurveyViewModel> model)
{
...
}
When the user click on the Submit button I got a null for model where I am expecting to see a list.
Please let me know what I am doing wrong.
Thank you for your help.

Normally, you can't post the list of objects to the controller in default model binder.
Best solution you can pass the object only based on Index to perform the CRUD operation in POST, and GET request you could get all list of objects. So in POST method it works if pass the object only.
If you want to post the list of object, you can achieve it by overriding the ModelBinder or using the FormCollection.
Here is an example, but to perform this way of operation we need to iterate and convert into the list. Because formcollection contains more number of items and not in the List type. The key of the property varies, because of html helper. Be aware when getting the value from formcollection.
[HttpPost]
public ActionResult Index(FormCollection model)
{
List<SurveyViewModel> obj = new List<SurveyViewModel>();
var name =Request.Form["item.Name"].Split(',').ToArray();
var pv =Request.Form["item.PV"].Split(',').ToArray();
//length must be same
for (var i = 0; i < name.Length; i++)
{
obj.Add(new SurveyViewModel()
{
Name = name[i],
PV = Convert.ToDouble(pv[i])
});
}
return View();
}

Related

Why list of checkbox selections always posted as null in ASP.NET MVC-5 [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 5 years ago.
I am new to ASP .NET MVC. My problem is - I want to 'POST' a collection of the items , so that controller can process it.
My model is collection of -
public class CheckedRentalProperty
{
public bool IsSelected { get; set; }
public int Id { get; set; }
public String Address { get; set; }
}
My controller is defined like this -
public class RentalPropertiesController : Controller
{
public ActionResult Index()
{
List<CheckedRentalProperty> checkHsList = new List<CheckedRentalProperty>();
// Fill the list
return View(checkHsList);
}
[HttpPost]
public ActionResult Save(IEnumerable<CheckedRentalProperty> checkHsList)
{
// why checkHsList is coming as null ??
}
}
And the view is like this -
#model IEnumerable<XXX.Models.CheckedRentalProperty>
#using (Html.BeginForm("Save", "RentalProperties", FormMethod.Post))
{
<div class="form-horizontal">
<div class="form-group">
<table class="table">
<tr>
<th>
</th>
<th>
#Html.DisplayNameFor(model => model.Address)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.CheckBoxFor(modelItem => item.IsSelected)</td>
<td>
#Html.DisplayFor(modelItem => item.Address)
</td>
</tr>
}
</table>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
My expectations was - when I hit the "Save" button, the Model, which is IEnumerable<CheckedRentalProperty> item, will be passed to the Save() action of the controller. However, I find that the passed parameter is "null" all the time. What am I missing?
Model that are solely IEnumerable are not too friendly as MVC Model.
There are many issues arise here, but in a nutshell, MVC webform bindings needs form name requests to be send in the following format: PropertyName[Index].Property
Which is not the case at your example.
It is a good design practice, to create a wrapping ViewModel which will hold the properties you need for the given controller + pages.
ViewModel
public class RentalPropertiesViewModel
{
public List<CheckedRentalProperty> CheckedRentalProperties { get; set; }
}
Controller: Next we will want to use this ViewModel in our controller.
public ActionResult Index()
{
var checkHsList = new List<CheckedRentalProperty>();
checkHsList.Add(new CheckedRentalProperty { Id = 1, Address = "Address1", IsSelected = true });
checkHsList.Add(new CheckedRentalProperty { Id = 2, Address = "Address2", IsSelected = false });
checkHsList.Add(new CheckedRentalProperty { Id = 3, Address = "Address3", IsSelected = true });
var model = new RentalPropertiesViewModel
{
CheckedRentalProperties = checkHsList
};
return View(model);
}
[HttpPost]
public ActionResult Save(RentalPropertiesViewModel model)
{
// why checkHsList is coming as null ??
return null;
}
View: Now in our view we should set the Model as the new ViewModel type we created.
#model TestBindings.Models.RentalPropertiesViewModel
And our view form should be something like:
<table class="table">
<tr>
<th>
Is Selected
</th>
<th>
Address
</th>
</tr>
#for (int i = 0; i < Model.CheckedRentalProperties.Count(); i++)
{
<tr>
#Html.HiddenFor(model => model.CheckedRentalProperties[i].Id);
<td>#Html.CheckBoxFor(model => model.CheckedRentalProperties[i].IsSelected)</td>
<td>#Html.TextBoxFor(model => model.CheckedRentalProperties[i].Address)</td>
</tr>
}
</table>
I've use the following format model => model.CheckedRentalProperties[i].IsSelected and now MVC InputExtensions will bind it correctly. e.g: CheckedRentalProperties[0].IsSelected
Important Note: Notice i'm passing Id property as hidden, so MVC Binder will know to set the Id to the correct item.

Input Tag Helper issue in ASP.NET

In ASP.NET view, shown below, I'm getting the error that is specific to line <input asp-for="StateName" />
Error:
'List<GrantsViewModel>' does not contain a definition for 'StateName'
NOTE: View is supposed to display different State Names in an HTML table column.
Controller:
public class DbTestController : Controller
{
private MyProjContext _context;
public DbTestController(MyProjContext context)
{
_context = context;
}
public IActionResult GrantNumbers()
{
var qryVM = from s in _context.StateNames
join g in _context.AnnualGrants on s.StateNumber equals g.StateNumber into sg
from r in sg.DefaultIfEmpty()
select new GrantsViewModel() { StateNumber = s.StateNumber,StateName= s.State, GrantNo= (r == null ? String.Empty : r.GrantNo), FiscalYear = (r == null ? 1900 : r.FiscalYear) };
return View(qryVM.ToList());
}
}
ViewModel:
public class GrantsViewModel
{
public int GrantNo_Id { get; set; }
public string StateNumber { get; set; }
public string StateName { get; set; }
public string GrantNo { get; set; }
public int FiscalYear { get; set; }
}
View:
#model List<MyProjet.Models.GrantsViewModel>
<form asp-controller="DbTest" asp-action="GrantNumbers" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.First().StateName)
</th>
<th>
#Html.DisplayNameFor(model => model.First().GrantNo)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
<input asp-for="StateName" />
</td>
<td>
#Html.DisplayFor(modelItem => item.GrantNo)
</td>
<td></td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
#Html.DisplayNameFor(model => model.StateName)
In this line you are trying to access the property StateName from the object referenced by model. Except model references an object of type List<T>, which does not have a property StateName.
To access StateName, you need to provide which element of the list you are accessing, such as the following (assuming you don't need to iterate, since you are just getting the column titles).
#Html.DisplayNameFor(model => model[0].StateName)
To reference the element correctly in the asp-for helper, use
<input asp-for="#item.StateName" />
You want to use FirstOrDefault() when referencing names of properties. This will still be able to get the display names using reflection even if your IEnumerable/List Model is empty.
#Html.DisplayNameFor(model => model.FirstOrDefault().StateName)
Instead of the StateName asp-tag, here is the Razor Helper for an input:
#Html.EditorFor(modelItem => item.StateName)
I'm a bit confused as to why you have this table in a form if you're wanting to just display your list of information. If you clarify, I can better answer based on your intent.
If you just want this data displayed, then use a DisplayFor() like you did for GrantNo column:
#Html.DisplayFor(modelItem => item.StateName)

Error with foreach loop when I try to use one model for two different views

I am very new to ASP.NET MVC so this would probably be an easy question. I followed the W3 Schools Movies tutorial and created a small database with fields Naslov ("Heading" in my language) Sadrzaj ("Content" in my language) and Datum ("Date") which I want to use as blog posts and to list on main Index. This is my current code for Aktuelnosti.cs
namespace comp_2000.Models
{
public class Aktuelnosti
{
public int ID { get; set; }
public string Naslov { get; set; }
public string Sadrzaj { get; set; }
public DateTime Datum { get; set; }
}
public class AktuelnostiContext : DbContext
{
public DbSet<Aktuelnosti> Aktuelnosti { get; set; }
}
}
And this is my related view for Index.cshtml in Aktuelnosti, which works
#model IEnumerable<comp_2000.Models.Aktuelnosti>
<table border="1">
<tr>
<th>
#Html.DisplayNameFor(model => model.Naslov)
</th>
<th>
#Html.DisplayNameFor(model => model.Sadrzaj)
</th>
<th>
#Html.DisplayNameFor(model => model.Datum)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td width="20%">
#Html.DisplayFor(modelItem => item.Naslov)
</td>
<td width="60%">
#Html.DisplayFor(modelItem => item.Sadrzaj)
</td>
<td>
#Html.DisplayFor(modelItem => item.Datum)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
#Html.ActionLink("Preview", "Details", new { id=item.ID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
So, when I try the same code for Index.cshtml inside Home, which looks like this
#model IEnumerable<comp_2000.Models.Aktuelnosti>
#foreach (var item in Model)
{
<p>#Html.DisplayFor(modelItem => item.Naslov)</p>
<p>#Html.DisplayFor(modelItem => item.Sadrzaj)</p>
<p>#Html.DisplayFor(modelItem => item.Datum)</p>
<hr>
}
I get this
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 48:
Line 49:
Line 50: #foreach (var item in Model)
Line 51: {
Line 52: <p>#Html.DisplayFor(modelItem => item.Naslov)</p>
It says the problem is in foreach loop. What I am doing wrong? I just want the same data from one database displayed on two different pages.
Edit: There is also something about NullReferenceException that was unhandled by the user code. Hope this is of any use for you.
System.NullReferenceException means that something is null. In this case, most likely the Model is null. Make sure that the controller is passing the Model.

View Models and Check Boxes in View

I have a newbie question, which I have tried to understand for the past few days. Hopefully someone can be kind enough to help me understand the programming flow.
Assuming I have a model, the information is stored in the database:
public class Student
{
public int studentID { get; set; }
public string studentName { get; set; }
public strin studentGrade {get; set; }
}
public class StudentDBContext : DbContext
{
public DbSet<Student> Students { get; set; }
}
and I want to display it into the view, with additional checkbox so I can select which students to be promoted into the next grade. I read that one way to do it is by putting into view model:
public class StudentViewModel
{
public bool promoted { get; set; }
public Student stu { get; set; }
}
But I am stuck on is this the way to do it? and if yes, how do you put into the view where it will display all the students with a checkbox next to it. Afterwards, I want to update all the grade for the students whose checkboxes are ticked. For example:
Student A, Student B, Student D promoted from Grade 1 to Grade 2. So I want to display the students, tick Student A, B and D and submit to update the Grade.
Step by step example will be much appreciated.
Update 1:
Controller:
[HttpGet]
public ViewResult CheckBox()
{
var studentViewModels = db.Students.Select(m => new StudentViewModel()
{
stu = m
}).ToList();
return View(studentViewModels);
}
[HttpPost]
public ActionResult CheckBox(IList<studentViewModel> list)
{
foreach (var stuUpdate in list.Where(m => m.promoted))
{
var stuRow = db.Students.Find(stuUpdate.stu.studentID);
stuRow.studentName = stuRow.studentName + "1";
db.Entry(stuRow).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("CheckBox");
}
return RedirectToAction("CheckBox");
}
View:
#model IList<School.ViewModels.StudentViewModel>
#using (Html.BeginForm())
{
<table>
<tr>
<th>
</th>
<th>
student ID
</th>
<th>
student name
</th>
<th>
student grade
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.promoted)
#Html.HiddenFor(modelItem => item.stu.studentID)
</td>
<td>
#Html.DisplayFor(modelItem => item.stu.studentID)
</td>
<td>
#Html.DisplayFor(modelItem => item.stu.studentName)
</td>
<td>
#Html.DisplayFor(modelItem => item.stu.studentGrade)
</td>
</tr>
}
</table>
<input type="submit" value="save" />
}
However currently hit by the following error:
Value cannot be null.
Parameter name: source
Source Error:
foreach (var stuUpdate in list.Where(m => m.promoted))
A very basic "step by step" (done in SO, so I probably did a few mistakes, but you've got the idea).
You have always a few ways to do these kind of things, so... just really take it as a sample, and find other examples to get other ideas.
well, first, in your controller, you will have a GET action (to see the list).
[HttpGet]
public ViewResult StudentList() {
//retrieve all students. With the Select, we create a new instance of StudentViewModel for each student.
//assuming StudentDbContext being a property of your controller, if it's not, you can instantiate a new one (in a using clause)
var studentViewModels = StudentDbContext.Students
.Select(m => new StudentViewModel() {
stu = m
//we don't say nothing about promoted :
//it will be there as "false" by default, which is probably what we want.
}).ToList();
//now we've got a list of StudentViewModel. This will be the model of our view
return View(studentViewModels);
}
Then we've got a view, StudentList.cshtml
in this view, we will display a table, with a line for each student : the studentId (hidden in this case), the name (display only), the grade (display only), and a checkbox.
We need a for loop (not a foreach) to get fine model binding.
#model IList<StudentViewModel>
#using (Html.BeginForm()) {
<table>
<tr>
<th>Student name</th>
<th>Student grade</th>
<th>Promote</th>
</tr>
#for (var i = 0; i < Model.Count(); i++) {
<tr>
<td>#Html.HiddenFor(m => Model[i].Student.studentID)
#Html.DisplayFor(m => Model[i].Student.studentName)
</td>
<td>#Html.DisplayFor(m => Model[i]Student.studentGrade)</td>
<td>#Html.CheckBoxFor(m => Model[i].promoted)</td>
</tr>
}
</table>
<input type="submit" value="save" />
}
This form will lead to another POST action (same name as the GET one, depending of what you have in your Html.BeginForm)
[HttpPost]
public ActionResult StudentList(IList<StudentViewModel> list) {
//we treat only the lines where checkbox has been checked
foreach (var studentViewModel in list.Where(m => m.promoted) {
var student = StudentDBContext.GetById(studentViewModel.Student.studentID);//get student entity instance from context
student.studentGrade ="<new value>";
StudentDBContext.SaveChanges();//save changes, you must have such a method somewhere.
}
return Action("StudentList");
}
Little detail :
Try to respect some really basic "usual" practices : for example in c#, Properties should begin by an uppercase letter (so StudentGrade, StudentName, Promoted, etc).

MVC 3 - Passing list of check boxes to and from a controller

I have a list of information sources in a database that I need to pass to a view in MVC. I need an end user to be able to tick the sources of information that apply to their course.
I am able to successfully pass the view a list of information sources alongside check boxes using the following code.
public ViewResult CreateUpdateInfoSource(int ProgrammeId)
{
List<ProgInfoSourceModel> viewmodel = new List<ProgInfoSourceModel>();
List<ProgInfoSourceDTO> myProgInfoDTOList = progInfoSourceService.AllInfoSources();
if (myProgInfoDTOList.Count != 0)
{
foreach (var x in myProgInfoDTOList)
{
ProgInfoSourceModel insert = new ProgInfoSourceModel();
insert.Selected = false;
insert.ProgrammeId = ProgrammeId;
insert.InfoSourceId = x.InfoSourceId;
insert.InfoSource = x.InfoSource;
insert.InfoReference = x.InfoReference;
insert.Rank = x.Rank;
viewmodel.Add(insert);
}
}
return View(viewmodel);
}
I am able to unpack this in the view just fine, however I am having real difficulty passing a the list back to my controller. I need to be able to loop through the list in my controller and see which ones do or don't apply so I can update the database.
My model looks like this:
namespace ProgrammeSpec.MVC.Models
{
public class ProgInfoSourceModel
{
[DisplayName("Selected")]
public bool Selected { get; set; }
[DisplayName("Programme Id")]
public int ProgrammeId { get; set; }
[DisplayName("Info Source Id")]
public int InfoSourceId { get; set; }
[DisplayName("Info Source")]
public String InfoSource { get; set; }
[DisplayName("Reference")]
public String InfoReference { get; set; }
[DisplayName("Rank")]
public int? Rank { get; set; }
}
}
My View looks like this:
<html>
<head>
<title>CreateUpdateInfoSource</title>
</head>
<body>
#using (Html.BeginForm())
{
<table>
<tr>
<th>
Selected
</th>
<th>
ProgrammeId
</th>
<th>
InfoSourceId
</th>
<th>
InfoSource
</th>
<th>
InfoReference
</th>
<th>
Rank
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.Selected)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProgrammeId)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoSourceId)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoSource)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoReference)
</td>
<td>
#Html.DisplayFor(modelItem => item.Rank)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</table>
<input type="submit" value="Update" />
}
</body>
</html>
and the controller that the view gets passed to looks like this: (snippet)
[HttpPost]
public ActionResult CreateUpdateInfoSource(List<ProgInfoSourceModel> viewmodel)
{
if (ModelState.IsValid)
{
try
{
The problem is the viewmodel is null. I understand this is probably because I've unpacked the list in the view so it is no longer a list but how can I access the values of the check boxes then?
The added complication is that the number of info sources will vary so I can't use a static form or list and give each one an Id...
This must be a fairly common problem with a simple solution, but I'm an MVC novice and I don't know how to get round this.
Try using an editor template (Here's another SO that answers that question How to create custom editor/display templates in ASP.NET MVC 3?), for your ProgInfoSourceModel and then simply use Html.EditorFor(m => m.Model) on the View.
When you use the foreach loop, each checkbox is getting the same input name - and so is not actually submitting the correct data back.
If you go the editor template route, and making MVC do the hard work of iterating through the IEnumerable - it will create inputs with names like 'item[0].Selected' - which the model binder then correctly deserialized back into a list.
Phil Haack also blogged a fantastic walkthrough of exactly this scenario way back in 2008: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Resources