How can I do model binding and data display at the same time using the same model.list? - asp.net-mvc-3

I have a code similar to this:
My View
#model MyCode.Models.RemarksModel
foreach (var row in Model.List)
{
<tr>
<td align="center"><font class="font_table_content">#row.Id</font></td>
<td align="center"><font class="font_table_content">#row.Full_Name</font></td>
<td align="center"><font class="font_table_content">#Html.TextBox("row.Remarks")</font></td>
}
My Model
public class RemarksModel
{
public IList<UserRemarks> List { get; set; }
}
My UserRemarks Object
public class UserRemarks
{
public virtual string Id { get; set; }
public virtual string Full_Name { get; set; }
public virtual string Remarks { get; set; }
}
Next in my controller, I will have some code that will load the records into a IList from the DB via Nhibernate, and then return the view with the list inside the model, something like this:
[HttpGet]
public ActionResult RemarksTest()
{
RemarksModel model = new RemarksModel();
model.List = LoadTheList();
return View(model);
}
Now, what I want to ask is, how am I able to receive the list back, i.e. get the remarks value back?
[HttpPost]
public ActionResult RemarksTest(RemarksModel model)
{
var list = model.list;
foreach (var remarks in list)
{
//do something to save the input data into code.
}
return View(model);
}
The actual code is more complex, and I've read about those IDictionary methods to receive the values. However implementing them, will cause the values not to be displayed instead, since the code no longer refers to the model.list.
Any ideas how can I display, and yet also, receive the data using the same 'list' inside my model above?

I think you need change your view as follows:
#model IEnumerable<UserRemarks>
#using(Html.BeginForm())
{
for(int i=0; i<#Model.Count; i++)
{
<table>
<tr>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Id)</font></td>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Full_Name</font></td>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Remarks")</font></td>
</tr>
</table>
}
<input type="submit" value="submit"/>
}
Then the get action should be changed to:
[HttpGet]
public ActionResult RemarksTest()
{
List<UserRermarks> model = LoadTheList();
return View(model);
}
And the post action changed to:
[HttpPost]
public ActionResult RemarksTest(IEnumerable<UserRemarks> model)
{
foreach (var remarks in model)
{
//do something to save the input data into code.
}
return View(model);
}
Unfortunately I don't have visual studio on the computer I am working on so I am unable to test the above but the logic should be correct so if a problem occurs it may just be that I have typed something slightly a miss.
Hope this works for you.

In your GET controller action you could store the list in TempData:
[HttpGet]
public ActionResult RemarksTest()
{
RemarksModel model = new RemarksModel();
model.List = LoadTheList();
TempData['List'] = model.List;
return View(model);
}
Then in your POST action retrieve it with:
var myList = TempData['List']
If your app is stateless (i.e. no sessions), then you can use a cookie-based TempData provider.

The problem is you are just displaying the values.
To post values to server back, you need to have some inputs (like, textbox,checkbox,radiobutton,hiddenfields). In your case you can define hidden fields ,so that model binder will bind them to List of UserRemarks.
Follow http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx to bind a list to model.

Related

MVC How to pass a list of objects with List Items POST action method

I want to post a List of items to controller from Razor view , but i am getting a List of objects as null
My class structre is
Model:
List<Subjects> modelItem
class Subjects
{
int SubId{get;set;}
string Name{get;set;}
List<Students> StudentEntires{get;set;}
}
class StudentEntires
{
int StudId{get;set;}
string Name{get;set;}
int Mark{get;set;}
}
The model itself is a list of items and every items contain List of child items as well. Example model is a list of Subjects and every subject contains a List of Students, and i want to input mark for every student
My View is like
#model IList<Subjects>
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Count > 0)
{
#for (int item = 0; item < Model.Count(); item++)
{
<b>#Model[item].Name</b><br />
#foreach (StudentEntires markItem in Model[item].StudentEntires)
{
#Html.TextBoxFor(modelItem => markItem.Mark)
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
And in controller
[HttpPost]
public ActionResult OptionalMarks(int Id,ICollection<Subjects> model)
{
//BUt my model is null. Any idea about this?
}
You're finding this difficult because you're not utilising the full power of the MVC framework, so allow me to provide a working example.
First up, let's create a view model to encapsulate your view's data requirements:
public class SubjectGradesViewModel
{
public SubjectGradesViewModel()
{
Subjects = new List<Subject>();
}
public List<Subject> Subjects { get; set; }
}
Next, create a class to represent your subject model:
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> StudentEntries { get; set; }
}
Finally, a class to represent a student:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Grade { get; set; }
}
At this point, you have all the classes you need to represent your data. Now let's create two controller actions, including some sample data so you can see how this works:
public ActionResult Index()
{
var model = new SubjectGradesViewModel();
// This sample data would normally be fetched
// from your database
var compsci = new Subject
{
Id = 1,
Name = "Computer Science",
StudentEntries = new List<Student>()
{
new Student { Id = 1, Name = "CompSci 1" },
new Student { Id = 2, Name = "CompSci 2" },
}
};
var maths = new Subject
{
Id = 2,
Name = "Mathematics",
StudentEntries = new List<Student>()
{
new Student { Id = 3, Name = "Maths 1" },
new Student { Id = 4, Name = "Maths 2" },
}
};
model.Subjects.Add(compsci);
model.Subjects.Add(maths);
return View(model);
}
[HttpPost]
public ActionResult Index(SubjectGradesViewModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Success");
}
// There were validation errors
// so redisplay the form
return View(model);
}
Now it's time to construct the views, and this part is particularly important when it comes to sending data back to a controller. First up is the Index view:
#model SubjectGradesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Subjects) <br />
<input type="submit" />
}
You'll notice I'm simply using Html.EditorFor, whilst passing Subjects as the parameter. The reason I'm doing this is because we're going to create an EditorTemplate to represent a Subject. I'll explain more later on. For now, just know that EditorTemplates and DisplayTemplates are special folder names in MVC, so their names, and locations, are important.
We're actually going to create two templates: one for Subject and one for Student. To do that, follow these steps:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model (i.e. in this case you would make two views, which would be called Subject.cshtml and Student.cshtml, respectively (again, the naming is important)).
Subject.cshtml should look like this:
#model Subject
<b>#Model.Name</b><br />
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.StudentEntries)
Student.cshtml should look like this:
#model Student
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.DisplayFor(m => m.Name): #Html.EditorFor(m => m.Grade)
<br />
That's it. If you now build and run this application, putting a breakpoint on the POST index action, you'll see the model is correctly populated.
So, what are EditorTemplates, and their counterparts, DisplayTemplates? They allow you to create reusable portions of views, allowing you to organise your views a little more.
The great thing about them is the templated helpers, that is Html.EditorFor and Html.DisplayFor, are smart enough to know when they're dealing with a template for a collection. That means you no longer have to loop over the items, manually invoking a template each time. You also don't have to perform any null or Count() checking, because the helpers will handle that all for you. You're left with views which are clean and free of logic.
EditorTemplates also generate appropriate names when you want to POST collections to a controller action. That makes model binding to a list much, much simpler than generating those names yourself. There are times where you'd still have to do that, but this is not one of them.
Change the action method signature to
public ActionResult OptionalMarks(ICollection<Subjects> model)
Since in your HTML, it does not look like there is anything named Id in there. This isn't your main issue though.
Next, do the following with the foor loop
#for(int idx = 0; idx < Model[item].StudentEntires.Count();idx++)
{
#Html.TextBoxFor(_ => Model[item].StudentEntries[idx])
}
Possibly due to the use of a foreach loop for the StudentEntries, the model binder is having trouble piecing everything together, and thus a NULL is returned.
EDIT:
Here's an example:
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var viewModel = new IndexViewModel();
var subjects = new List<Subject>();
var subject1 = new Subject();
subject1.Name = "History";
subject1.StudentEntires.Add(new Student { Mark = 50 });
subjects.Add(subject1);
viewModel.Subjects = subjects;
return View(viewModel);
}
[HttpPost]
public ActionResult Index(IndexViewModel viewModel)
{
return new EmptyResult();
}
}
View
#model SOWorkbench.Controllers.IndexViewModel
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Subjects.Any())
{
int subjectsCount = Model.Subjects.Count();
for (int item = 0; item < subjectsCount; item++)
{
<b>#Model.Subjects[item].Name</b><br />
int studentEntriesCount = Model.Subjects[item].StudentEntires.Count();
for(int idx = 0;idx < studentEntriesCount;idx++)
{
#Html.TextBoxFor(_ => Model.Subjects[item].StudentEntires[idx].Mark);
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
When you post the form, you should see the data come back in the viewModel object.

PartialView or enumerate using foreach in View Model for an inner class using Entity Framework?

I am creating (my first) real simple ASP.NET MVC blog site where I am wondering what is the best approach to sort my comments by the the latest DateTime and how to go about injecting the query results using LINQ into the View or do I need an IQueryable<T> PartialView?
I have been reading up on IEnumerable<T> vs IQueryable<T>, and I would like to think that I wouldn't want the comments in-memory until I have them filtered and sorted.
I was thinking a PartialView using #model Queryable<Models.Blogsite.Comment> where I pass the inner class to the [ChildAction] using the ViewBag or can I just use a foreach loop in the View?
My Article class looks a little like this:
public class Article
{
//...more properties
public virtual IQueryable<Comment> Comments { get; set; }
}
My Comment class looks a little like this:
public class Comment
{
[Key]
public int CommentId { get; set; }
[ForeignKey("Article")]
public int ArticleId { get; set; }
[DataType(DataType.DateTime), Timestamp,ScaffoldColumn(false)]
public DateTime Timestamp { get; set; }
//...more properties
public virtual Article Article { get; set; }
}
And then if I did implement a PartialView it would look a little like this:
#model IQueryable<BlogSite.Models.Comment>
<table>
<tbody>
#foreach (BlogSite.Models.Comment comment in ViewBag.Comments)
{
<tr>
<td>
#...
I changed my public class Article to this:
public class Article
{
//...more properties
public virtual ICollection<Comment> Comments { get; set; }
}
And then I created a method in my ArticleRepository where I call it from UnitOfWOrk which is instantiated within the controller:
public IEnumerable<Comment> GetCommentsByArticleId(int id)
{
List<Comment> tempList = new List<Comment>();
var article = GetArticleById(id);
var comments = article.Comments.Where(c => c.ParentCommentId == null).OrderByDescending(c => c.Timestamp);
foreach (var c in comments)
{
if (c.isRoot)
{
tempList.Add(c);
// Replies
foreach (var r in article.Comments.Where(x => x.ParentCommentId == c.CommentId).OrderBy(x => x.Timestamp))
{
tempList.Add(r);
}
}
}
return tempList;
}
I have a property called bool isRoot in my comment class so I can render all comments from the property int ParentCommentId so users can respond to these comments. My question now is that if my web application has thousands of articles and the querying is done in-memory and not at the database will this eventually turn into a performance issue?
Won't the temporary list go into garbage collection once it is out of scope? What is the advantage of using IQueryable in this scenario?
Using ViewBag is a bad practice. If you need a sorted list - do it in a controller:
var comments = context.GetComments().ToList();
comments.Sort((x, y) => y.Timestamp.CompareTo(x.Timestamp));
return View(comments)
And pass a sorted list in the view:
#model IEnumerable<BlogSite.Models.Comment>
<table>
....

To show Create and detail view in one view using mvc3

Hi i want to show a page where i'm allowing user to create a new record and show the other related records of same table below .....
i need to add data in Hobbydetail class:
public class HobbyDetail
{
public virtual HobbyMasters Hobbymaster { get; set; }
public virtual Course course { get; set; }
public virtual StudyMedium StudyMedium { get; set; }
public virtual decimal Fees { get; set; }
}
I want my view "Create" to let the user create a new record and to also to show existing record below it...
I Dont want to use a viewmodel...
Can sumbody help me
Thanx in advance
One way to accomplish this is: In your controller, create a child action that renders the list, then render that action in your "Create" view using Html.RenderAction (also see this). I have included some code below (I have not tested this, but it should give you the basic idea). Please note this is not the only way to accomplish this - You could use a partial view see this. Please also understand the difference between html.RenderAction and html.Action, see this.
//In HobbyDetail Controller
[HTTPGet]
public ActionResult Create()
{
var model = new HobbyDetail ();
return View(model);
}
[HTTPPost]
public ActionResult Create(HobbyDetail model)
{
if(ModelState.isValid)
{
//logic to persist model
}
else
{
//logic when validation fails...
}
}
[ChildActionOnly]
public ActionResult ListAll()
{
List<Hobbydetail> model = //query to DB, or Data store to get Hobbydetails
return View(model);
}
//View for ListAll
#model List<HobbyDetail>
{
Layout = null; //No layout here...
}
<ul>
#foreach(var h in Model)
{
<li>#h.HobbyMasters.Name</li> //for example...
}
</ul>
//View for Create
#model HobbyDetail
...
#{html.renderAction("ListAll");}

Add to model in form, then redisplay form to add more

I'm new to MVC3, but so far I have managed to get along with my code just great.
Now, I would like to make a simple form, that allows the user to input a text string, representing the name of an employee. I would then like this form to be submitted and stored in my model, in a sort of list. The form should then re-display, with a for-each loop writing out my already added names. When I'm done and moving on, I need to store this information to my database.
What I can't figure out, is how to store this temporary information, until i push it to my database. Pushing everytime I submit I can do, but this has cause me alot of headaches.
Hope you guys see what I'm trying to do, and have an awesome solution for it. :)
This is a simplified version of what I've been trying to do:
Model
public class OrderModel
{
public virtual ICollection<Employees> EmployeesList { get; set; }
public virtual Employees Employees { get; set; }
}
public class Employees
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
View
#model OrderModel
#{
if (Model.EmployeesList != null)
{
foreach (var c in Model.EmployeesList)
{
#c.Name<br />
}
}
}
#using(Html.BeginForm())
{
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
Controller
[HttpPost]
public ActionResult Index(OrderModel model)
{
model.EmployeesList.Add(model.Employees);
// This line gives me the error: "System.NullReferenceException: Object reference not set to an instance of an object."
return View(model);
}
I think you should handle this by burning the employee list into the page. Right now, you're not giving your form any way of recognizing the list.
In an EditorTemplates file named Employees:
#model Employees
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.Name);
In your view:
#using(Html.BeginForm())
{
#Html.EditorFor(m => m.EmployeesList)
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
[HttpPost]
public ActionResult Index(OrderModel model)
{
if (model.EmployeesList == null)
model.EmployeesList = new List<Employees>();
model.EmployeesList.Add(model.Employees);
return View(model);
}
As an added bonus to this method, it would be easy to add ajax so the user never has to leave the page when they add new employees (You might be able to just insert a new hidden value with javascript and avoid ajax. It would depend on if you do anything other than add to your list in your post).
I think this would be a good use for TempData. You can store anything in there, kind of like the cache, but unlike the cache it only lasts until the next request. To implement this, change the action method like this (example only):
[HttpPost]
public ActionResult Index(OrderModel model)
{
dynamic existingItems = TempData["existing"];
if (existingItems != null)
{
foreach (Employee empl in existingItems)
model.EmployeesList.Add(empl );
}
model.EmployeesList.Add(model.Employees);
TempData["existing"] = model.EmployeesList;
return View(model);
}

Sending data from hardcoded dropdwonlist to controller

Migrating from textboxe to dropdownlist – Need to send value from a hard-coded dropdownlist to controller
The code below is used in the controller
var list = new SelectList(new[]
{
new{ID="1",Name="20012"},
new{ID="2",Name="20011"},
new{ID="3",Name="20010"},
new {ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"},
},
"ID", "Name", 1);
ViewData["list"] = listYear;
The code below is used in the view
#using (Html.BeginForm()){
<p>
Title: #Html.TextBox("SearchString")
#Html.DropDownList("list",ViewData["list"] as SelectList)
Genre: #Html.DropDownList("Towns", "All")
<input type="submit" value="Filter" /></p>
}
Below is the code which was used for the textbox
if (!String.IsNullOrEmpty(year))
{
car = Cars.Where(s => s.Year.Contains(year));
}
Looks like you are trying to select a value from a selectlist and send that value to the controller. First off, I'd suggest you use a ViewModel instead of magic strings. You should modify your View to accept the new ViewModel and then post the model to your action. It's easy, cleaner and more maintainable.
Here is what your model would look like
public class VehicleYearsViewModel {
public SelectList VehicleYears { get; set; }
public int SelectedYear { get; set; }
public VehicleYearsViewModel() {
VehicleYears = new SelectList(new[]
{
new{ID="1",Name="2012"},
new{ID="2",Name="2011"},
new{ID="3",Name="2010"},
new{ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"}
}
}
}
Your View then would look like so:
#YourAppName.Models.VehicleYearsViewModel
#using (Html.BeginForm()){
#Html.ValidationSummary(true)
#Html.DropDownListFor(model => model.SelectedYear, Model.VehicleYears, "ID", "Name", 1))
<input type="submit" value="OK" />
}
Your controller action would accept the model and can make use of the selected value as an int datatype.
I'm just guessing since your controller action isn't posted but this is pretty much what it would look like:
public class HomeController : Controller {
public ActionResult Index() {
var model = new VehicleYearsViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(VehicleYearsViewModel model) {
if(ModelState.IsValid) {
// you can get selected year like so
int selectedYear = model.SelectedYear;
// ... your code here to do whatever with selectedYear
}
return View(model);
}
}
Hope this helps

Resources