ASP.NET MVC 3 PagedList. The method 'Skip' is only supported for sorted input in LINQ to Entities. - asp.net-mvc-3

Why is it it gives error in return View(contacts.ToPagedList(pageNumber, pageSize)); statement the error in the Index method :
The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PhoneBook.Models;
using PagedList;
namespace PhoneBook.Controllers
{
public class ContactsController : Controller
{
private PhoneDBContext db = new PhoneDBContext();
//
// GET: /Contacts/
public ViewResult Index(string searchString, string sortOrder, Contact model, string currentFilter, int? page)
{
ViewBag.CurrentSort = sortOrder;
ViewBag.FNameSortParm = sortOrder == "FName asc"? "FName desc" : "FName asc";
ViewBag.DateSortParm = sortOrder == "Date asc" ? "Date desc" : "Date asc";
ViewBag.LNameSortParm = sortOrder == "LName asc" ? "LName desc" : "LName asc";
ViewBag.CompSortParm = sortOrder == "Company asc" ? "Company desc" : "Company asc";
ViewBag.MobSortParm = sortOrder == "Mob asc" ? "Mob desc" : "Mob asc";
ViewBag.TelSortParm = sortOrder == "Tel asc" ? "Tel desc" : "Tel asc";
if (Request.HttpMethod == "GET") { searchString = currentFilter; }
else {page = 1;}
ViewBag.CurrentFilter = searchString;
var contacts = from m in db.Contacts
select m;
switch (sortOrder)
{
case "FName desc":
contacts = contacts.OrderByDescending(s => s.FirstName);
break;
case "FName asc":
contacts = contacts.OrderBy(s => s.FirstName);
break;
case "LName desc":
contacts = contacts.OrderByDescending(s => s.LastName);
break;
case "LName asc":
contacts = contacts.OrderBy(s => s.LastName);
break;
case "Company desc":
contacts = contacts.OrderByDescending(s => s.Company);
break;
case "Company asc":
contacts = contacts.OrderBy(s => s.Company);
break;
case "Date desc":
contacts = contacts.OrderByDescending(s => s.DateAdded);
break;
case "Date asc":
contacts = contacts.OrderBy(s => s.DateAdded);
break;
case "Mob desc":
contacts = contacts.OrderByDescending(s => s.MobileNumber);
break;
case "Mob asc":
contacts = contacts.OrderBy(s => s.MobileNumber);
break;
case "Tel desc":
contacts = contacts.OrderByDescending(s => s.TelephoneNumber);
break;
case "Tel asc":
contacts = contacts.OrderBy(s => s.TelephoneNumber);
break;
}
if (!String.IsNullOrEmpty(searchString))
{
contacts = contacts.Where(s => s.LastName.ToUpper().Contains(searchString)||s.FirstName.ToUpper().Contains(searchString)||s.Company.ToUpper().Contains(searchString));
}
int pageSize = 3;
int pageNumber = (page ?? 1);
return View(contacts.ToPagedList(pageNumber, pageSize));
}
//
// GET: /Contacts/Details/5
public ViewResult Details(int id)
{
Contact contact = db.Contacts.Find(id);
return View(contact);
}
//
// GET: /Contacts/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Contacts/Create
[HttpPost]
public ActionResult Create(Contact contact)
{
if (ModelState.IsValid)
{
db.Contacts.Add(contact);
contact.DateAdded = DateTime.Now;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(contact);
}
//
// GET: /Contacts/Edit/5
public ActionResult Edit(int id=0)
{
Contact contact = db.Contacts.Find(id);
if (contact == null) { return HttpNotFound(); } // returns blank page if id is not valid
return View(contact);
}
//
// POST: /Contacts/Edit/5
[HttpPost]
public ActionResult Edit(Contact contact)
{
if (ModelState.IsValid)
{
db.Entry(contact).State = EntityState.Modified;
contact.DateAdded = DateTime.Now;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(contact);
}
//
// GET: /Contacts/Delete/5
public ActionResult Delete(int id)
{
Contact contact = db.Contacts.Find(id);
if (contact == null) { return HttpNotFound(); }
return View(contact);
}
//
// POST: /Contacts/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Contact contact = db.Contacts.Find(id);
if (contact == null) { return HttpNotFound(); }
db.Contacts.Remove(contact);
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult SearchIndex(string searchString)
{
var contacts = from m in db.Contacts
select m;
if (!String.IsNullOrEmpty(searchString))
{
contacts = contacts.Where(s => s.LastName.Contains(searchString));
}
return View(contacts);
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
below is the Index.cshtml code:
#model PagedList.IPagedList<PhoneBook.Models.Contact>
#{
ViewBag.Title = "Phone Book";
}
<p>
#using (Html.BeginForm()){
<p> Search: #Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
<input type="submit" value="Go" /></p>
}
</p>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
#Html.ActionLink("First Name", "Index", new { sortOrder=ViewBag.FNameSortParm, currentFilter=ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.LNameSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Mobile Num", "Index", new { sortOrder = ViewBag.MobSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Tel Num", "Index", new { sortOrder = ViewBag.TelSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Company", "Index", new { sortOrder = ViewBag.CompSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
<th>
#Html.ActionLink("Date Added/Updated", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter = ViewBag.CurrentFilter })
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.MobileNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.TelephoneNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.Company)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateAdded)
</td>
<td>
#Html.ActionLink("Details", "Details", new { id=item.ID })
#Html.ActionLink("Edit", "Edit", new { id=item.ID })
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
<div>
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
of #Model.PageCount
#if (Model.HasPreviousPage)
{
#Html.ActionLink("<<", "Index", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
#Html.Raw(" ");
#Html.ActionLink("< Prev", "Index", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
}
else
{
#:<<
#Html.Raw(" ");
#:< Prev
}
#if (Model.HasNextPage)
{
#Html.ActionLink("Next >", "Index", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
#Html.Raw(" ");
#Html.ActionLink(">>", "Index", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
}
else
{
#:Next >
#Html.Raw(" ")
#:>>
}
</div>

Try the following three changes.
Put this code before the switch statement:
if (!String.IsNullOrEmpty(searchString))
{
contacts = contacts.Where(s => s.LastName.ToUpper().Contains(searchString)||s.FirstName.ToUpper().Contains(searchString)||s.Company.ToUpper().Contains(searchString));
}
Add a default case in your switch statement, and make it throw.
switch (sortOrder) {
case ...:
...
default:
throw new ArgumentException("Bad sort order specified", "sortOrder");
}
Use the type IOrderedQueryable<T>.
IOrderedQueryable<T> orderedContacts;
switch (sortOrder)
{
case "FName desc":
orderedContacts = contacts.OrderByDescending(s => s.FirstName);
break;
...
}
...
return View(orderedContacts.ToPagedList(pageNumber, pageSize));

return View(orderedContacts.ToList().ToPagedList(pageNumber, pageSize));

Related

Search by dropdown value using MVC 3

I am trying to create a search function that allows the user to search a column that is specified by a dropdown, I have been able to get the search to work but only by using a specific column and a contains method. Can anyone help?
Below I have included my controller and my view, please let me know if you need any other code. Thanks for your time and help!
I am almost positive that this is the code that I have to alter to use the dropdown to search instead of a specific column.
if (!String.IsNullOrEmpty(searchString))
{
IAMP = IAMP.Where(p => p.PA.ToUpper().Contains(searchString.ToUpper()));
}
Controller
public class PaController : Controller
{
PaEntities db = new PaEntities();
// Index Method
public ViewResult Index(string CurrentField, string sortField, string sortOrder, string currentFilter, string searchString, int? page, string Versions, string VPS, string Directors, string IAMP_SEARCH)
{
ViewBag.CurrentOrder = sortOrder = String.IsNullOrEmpty(sortOrder) ? "asc" : sortOrder; // Provides the order to sort
ViewBag.CurrentField = sortField = String.IsNullOrEmpty(sortField) ? "IAMP_PK" : sortField; // Provides the field to sort
var IAMP = from p in db.iamp_mapping select p;
var ISER = from s in db.iamp_search_string select s;
if (Versions != null && Versions != String.Empty) // Version Dropdown Sort Function
{
IAMP = IAMP.Where(v => v.VERSION.Contains(Versions));
}
var VIAMP = from x in db.version_number select x;
var VPIAMP = from v in db.vp_table select v;
var DIAMP = from d in db.director_table select d;
ViewData["SelectedVersion"] = Versions;
ViewData["Versions"] = new SelectList(VIAMP.Select(x => x.VERSION));
ViewData["VPS"] = new SelectList(VPIAMP.Select(x => x.VP));
ViewData["Directors"] = new SelectList(DIAMP.Select(x => x.DIRECTOR));
ViewData["currentFilter"] = currentFilter;
ViewData["IAMP_SEARCH"] = IAMP_SEARCH;
ViewData["IAMP_SEARCH"] = new SelectList(ISER.Select(x => x.IAMP_SEARCH));
if (Request.HttpMethod == "GET") {
searchString = currentFilter; //sets the currentFilter equal to Searchstring
IAMP_SEARCH = sortField;
}
else {
page = 1; // defaults to page 1
}
ViewBag.CurrentFilter = searchString; // Provides the view with the current filter string
if (!String.IsNullOrEmpty(searchString))
{
IAMP = IAMP.Where(p => p.PA.ToUpper().Contains(searchString.ToUpper()));
}
switch (sortField) {
default: IAMP = sortOrder == "asc" ? IAMP.OrderBy(p => p.PA) : IAMP.OrderByDescending(p => p.PA); break;
case "VERSION": IAMP = sortOrder == "asc"? IAMP.OrderBy(p => p.VERSION) : IAMP.OrderByDescending(p => p.VERSION); break;
case "MAJOR_PROGRAM": IAMP = sortOrder == "asc" ? IAMP.OrderBy(p => p.MAJOR_PROGRAM) : IAMP.OrderByDescending(p => p.MAJOR_PROGRAM); break;
case "INVESTMENT_AREA": IAMP = sortOrder == "asc" ? IAMP.OrderBy(p => p.INVESTMENT_AREA) : IAMP.OrderByDescending(p => p.INVESTMENT_AREA); break;
case "VP": IAMP = sortOrder == "asc" ? IAMP.OrderBy(p => p.VP) : IAMP.OrderByDescending(p => p.VP); break;
case "DIRECTOR": IAMP = sortOrder == "asc" ? IAMP.OrderBy(p => p.DIRECTOR) : IAMP.OrderByDescending(p => p.DIRECTOR); break;
}
int pageSize = 15; // number of records shown on page view
int pageNumber = (page ?? 1); // start page number
return View(IAMP.ToPagedList(pageNumber, pageSize)); // uses pagedList method to return correct page values
}
VIEW
#model PagedList.IPagedList<DBFirstMVC.Models.iamp_mapping>
#{
ViewBag.Title = "Index";
}
#using PagedList;
<h2 class="corporate sifr">PA Mapping</h2>
#using (Html.BeginForm())
{
<p>
Show Version: #Html.DropDownList("Versions","All")
<input type = "submit" value = "Go" />
</p>
}
#using (Html.BeginForm())
{
<div class="lefty">
Find by #Html.DropDownList("IAMP_SEARCH","All"): #Html.TextBox("SearchString")
<input type = "submit" value = "Search" />
</div>
}
<div class="righty">
#Html.ActionLink("Add a new PA to the database", "Create")
</div>
<br /><br />
<div>
<div class="lefty">
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
of #Model.PageCount
#if (Model.HasPreviousPage)
{
#Html.ActionLink("<<", "", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
#Html.Raw(" ");
#Html.ActionLink("< Prev", "", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
}
else
{
#:<<
#Html.Raw(" ");
#:< Prev
}
#if (Model.HasNextPage)
{
#Html.ActionLink("Next >", "", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
#Html.Raw(" ");
#Html.ActionLink(">>", "", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
}
else
{
#:Next >
#Html.Raw(" ")
#:>>
}
</div>
<div class="righty">
Showing Records #Model.FirstItemOnPage to #Model.LastItemOnPage from #Model.TotalItemCount
</div>
</div>
<table>
<tr>
<th>
#Html.ActionLink("PA", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "PA", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "PA"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc"/>}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "PA"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc"/>}
</th>
<th>
#Html.ActionLink("MAJOR PROGRAM", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "MAJOR_PROGRAM", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "MAJOR_PROGRAM"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc" />}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "MAJOR_PROGRAM"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc"/>}
</th>
<th>
#Html.ActionLink("INVESTMENT AREA", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "INVESTMENT_AREA", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "INVESTMENT_AREA"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc"/>}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "INVESTMENT_AREA"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc" />}
</th>
<th>
#Html.ActionLink("Version", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "VERSION", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "VERSION"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc" />}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "VERSION"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc"/>}
</th>
<th>
#Html.ActionLink("VP", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "VP", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "VP"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc"/>}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "VP"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc"/>}
</th>
<th>
#Html.ActionLink("DIRECTOR", "", new { sortOrder = ViewBag.currentOrder == "asc" ? "desc" : "asc", sortField = "DIRECTOR", Versions = ViewBag.SelectedVersion })
#if (ViewBag.currentOrder == "asc" && ViewBag.CurrentField == "DIRECTOR"){ <img src="../../Images/ico_tablesortoffset_asc.gif" alt = "table sort arrow asc"/>}
#if (ViewBag.currentOrder == "desc" && ViewBag.CurrentField == "DIRECTOR"){ <img src="../../Images/ico_tablesortoffset_desc.gif" alt = "table sort arrow desc"/>}
</th>
<th></th>
</tr>
#{
var row_class = "odd";
}
#foreach (var item in Model) {
row_class = row_class == "odd"? "even" : "odd";
<tr class="#row_class">
<td>
#Html.DisplayFor(modelItem => item.PA)
</td>
<td>
#Html.DisplayFor(modelItem => item.MAJOR_PROGRAM)
</td>
<td>
#Html.DisplayFor(modelItem => item.INVESTMENT_AREA)
</td>
<td>
#Html.DisplayFor(modelItem => item.VERSION)
</td>
<td>
#Html.DisplayFor(modelItem => item.VP)
</td>
<td>
#Html.DisplayFor(modelItem => item.DIRECTOR)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.PA}) |
#Html.ActionLink("Delete", "Delete", new { id = item.PA})
</td>
</tr>
}
</table>
<div>
<div class="lefty">
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
of #Model.PageCount
#if (Model.HasPreviousPage)
{
#Html.ActionLink("<<", "", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
#Html.Raw(" ");
#Html.ActionLink("< Prev", "", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
}
else
{
#:<<
#Html.Raw(" ");
#:< Prev
}
#if (Model.HasNextPage)
{
#Html.ActionLink("Next >", "", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
#Html.Raw(" ");
#Html.ActionLink(">>", "", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter, Versions = ViewBag.SelectedVersion })
}
else
{
#:Next >
#Html.Raw(" ")
#:>>
}
</div>
<div class="righty">
Showing Records #Model.FirstItemOnPage to #Model.LastItemOnPage from #Model.TotalItemCount
</div>
</div>
<br /><br />
<div>
#Html.ActionLink("Download in Excel Format", "PaExcelOutput", new {Versions = ViewBag.SelectedVersion, currentFilter = ViewBag.currentFilter})
</div>
You could use Dynamic Query which allows you to use dynamic column names.

Adding an ASP.NET MVC grid to application using PagedList

I am trying to add a grid to a table of data in my MVC application but keep getting the following error message:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[AssociateTracker.Models.Associate]', but this dictionary requires a model item of type 'PagedList.IPagedList`1[AssociateTracker.Models.Associate]'.
View:
#model PagedList.IPagedList<AssociateTracker.Models.Associate>
#{
ViewBag.Title = "ViewAll";
}
<h2>View all</h2>
<table>
<tr>
<th>First name</th>
<th>#Html.ActionLink("Last Name", "ViewAll", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })</th>
<th>Email address</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.FirstName</td>
<td>#item.LastName</td>
<td>#item.Email</td>
<td>
#Html.ActionLink("Details", "Details", new { id = item.AssociateId }) |
#Html.ActionLink("Edit", "Edit", new { id = item.AssociateId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.AssociateId })
</td>
</tr>
}
</table>
<div>
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
of #Model.PageCount
#if (Model.HasPreviousPage)
{
#Html.ActionLink("<<", "ViewAll", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter })
#Html.Raw(" ");
#Html.ActionLink("< Prev", "ViewAll", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter })
}
else
{
#:<<
#Html.Raw(" ");
#:< Prev
}
#if (Model.HasNextPage)
{
#Html.ActionLink("Next >", "ViewAll", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter })
#Html.Raw(" ");
#Html.ActionLink(">>", "ViewAll", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter })
}
else
{
#:Next >
#Html.Raw(" ")
#:>>
}
</div>
I have checked against Microsoft's Contosos University example but can't spot any differences. Can anyone else see what the issue might be?
The error message seems pretty self explanatory. Your view expects an IPagedList<Associate> instance but you are passing a List<Associate> from your controller action.
So inside your controller action you need to provide the proper model to the view:
public ActionResult Index(int? page)
{
List<Associate> associates = GetAssociates();
IPagedList<Associate> model = associates.ToPagedList(page ?? 1, 10);
return View(model);
}
I used the extension method from here. The IPagedList<T> is not a standard type built in ASP.NET MVC so you will have to reference the proper assemblies.

Asp.Net MVC 3 (Razor, Json, Ajax) Master Detail - detail save failing

I am new to MVC3 and trying to build a simple Invoicing app. The problem with my code is that the Ajax Post is failing and I cant find out why. Stepped through the JQuery code and it seems fine but by the time the POST hits the controller, the Model.IsValid is false. The problem seems to be with the child records. The invoice Master record is being saved to the DB but the InvoiceRow isnt. The problem lies in the SaveInvoice() function.
public class Invoice
{
[Key]
public int InvoiceID { get; set; }
public int ContractID { get; set; }
[Required]
[Display(Name = "Invoice Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd MMM yyyy}")]
public DateTime InvoiceDate { get; set; }
[Required]
[Display(Name = "Invoice No")]
public int InvoiceNumber { get; set; }
[Required(AllowEmptyStrings = true)]
[Display(Name = "Payment Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd MMM yyyy}")]
public DateTime PaymentDate { get; set; }
public virtual Contract Contract { get; set; }
public virtual ICollection<InvoiceRow> InvoiceRows { get; set; }
}
public class InvoiceRow
{
[Key]
public int Id { get; set; }
public int InvoiceID { get; set; }
public string RowDetail { get; set; }
public int RowQty { get; set; }
public decimal ItemPrice { get; set; }
public decimal RowTotal { get; set; }
public virtual Invoice Invoice { get; set; }
}
public class InvoiceController : Controller
{
private CyberneticsContext db = new CyberneticsContext();
//
// GET: /Invoice/
public ViewResult Index()
{
var invoices = db.Invoices.Include(i => i.Contract);
return View(invoices.ToList());
}
//
// GET: /Invoice/Details/5
public ViewResult Details(int id)
{
Invoice invoice = db.Invoices.Find(id);
return View(invoice);
}
//
// GET: /Invoice/Create
public ActionResult Create()
{
ViewBag.Title = "Create";
ViewBag.ContractID = new SelectList(db.Contracts, "Id", "ContractName");
return View();
}
//
// POST: /Invoice/Create
[HttpPost]
public JsonResult Create(Invoice invoice)
{
try
{
if (ModelState.IsValid)
{
if (invoice.InvoiceID > 0)
{
var invoiceRows = db.InvoiceRows.Where(ir => ir.InvoiceID == invoice.InvoiceID);
foreach (InvoiceRow row in invoiceRows)
{
db.InvoiceRows.Remove(row);
}
foreach (InvoiceRow row in invoice.InvoiceRows)
{
db.InvoiceRows.Add(row);
}
db.Entry(invoice).State = EntityState.Modified;
}
else
{
db.Invoices.Add(invoice);
}
db.SaveChanges();
return Json(new { Success = 1, InvoiceID = invoice.InvoiceID, ex = "" });
}
}
catch (Exception ex)
{
return Json(new { Success = 0, ex = ex.Message.ToString() });
}
return Json(new { Success = 0, ex = new Exception("Unable to Save Invoice").Message.ToString() });
}
//
// GET: /Invoice/Edit/5
public ActionResult Edit(int id)
{
ViewBag.Title = "Edit";
Invoice invoice = db.Invoices.Find(id);
ViewBag.ContractID = new SelectList(db.Contracts, "Id", "ContractName", invoice.ContractID);
return View("Create", invoice);
}
//
// POST: /Invoice/Edit/5
[HttpPost]
public ActionResult Edit(Invoice invoice)
{
if (ModelState.IsValid)
{
db.Entry(invoice).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ContractID = new SelectList(db.Contracts, "Id", "ContractName", invoice.ContractID);
return View(invoice);
}
//
// GET: /Invoice/Delete/5
public ActionResult Delete(int id)
{
Invoice invoice = db.Invoices.Find(id);
return View(invoice);
}
//
// POST: /Invoice/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Invoice invoice = db.Invoices.Find(id);
db.Invoices.Remove(invoice);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
#model Cybernetics2012.Models.Invoice
... script tags excluded for brevity
<h2 class="h2">#ViewBag.Title</h2>
<script type="text/javascript">
$( document ).ready( function ()
{
// here i have used datatables.js (jQuery Data Table)
$( '.tableItems' ).dataTable
(
{
"sDom": 'T<"clear">lfrtip',
"oTableTools": { "aButtons": [], "sRowSelect": "single" },
"bLengthChange": false,
"bFilter": false,
"bSort": true,
"bInfo": false
}
);
// Add DatePicker widget to InvoiceDate textbox
$( '#InvoiceDate' ).datepicker();
// Add DatePicker widget to PaymentDate textbox
$( '#PaymentDate' ).datepicker();
// Get the tableItems table
var oTable = $( '.tableItems' ).dataTable();
} );
// this function is used to add item to table
function AddInvoiceItem()
{
// Adding item to table
$( '.tableItems' ).dataTable().fnAddData( [$( '#RowDetail' ).val(), $( '#RowQty' ).val(), $( '#ItemPrice' ).val(), $( '#RowQty' ).val() * $( '#ItemPrice' ).val()] );
// clear text boes after adding data to table..
$( '#RowDetail' ).val( "" )
$( '#RowQty' ).val( "" )
$( '#ItemPrice' ).val( "" )
}
// This function is used to delete selected row from Invoice Rows Table and then set deleted item to Edit text Boxes
function DeleteRow()
{
// DataTables.TableTools plugin for getting selected row items
var oTT = TableTools.fnGetInstance( 'tableItems' ); // Get Table instance
var sRow = oTT.fnGetSelected(); // Get Selected Item From Table
// Set deleted row item to editable text boxes
$( '#RowDetail' ).val( $.trim( sRow[0].cells[0].innerHTML.toString() ) );
$( '#RowQty' ).val( jQuery.trim( sRow[0].cells[1].innerHTML.toString() ) );
$( '#ItemPrice' ).val( $.trim( sRow[0].cells[2].innerHTML.toString() ) );
$( '.tableItems' ).dataTable().fnDeleteRow( sRow[0] );
}
//This function is used for sending data(JSON Data) to the Invoice Controller
function SaveInvoice()
{
// Step 1: Read View Data and Create JSON Object
// Creating invoicRow Json Object
var invoiceRow = { "InvoiceID": "", "RowDetail": "", "RowQty": "", "ItemPrice": "", "RowTotal": "" };
// Creating invoice Json Object
var invoice = { "InvoiceID": "", "ContractID": "", "InvoiceDate": "", "InvoiceNumber": "", "PaymentDate": "", "InvoiceRows":[] };
// Set Invoice Value
invoice.InvoiceID = $( "#InvoiceID" ).val();
invoice.ContractID = $( "#ContractID" ).val();
invoice.InvoiceDate = $( "#InvoiceDate" ).val();
invoice.InvoiceNumber = $( "#InvoiceNumber" ).val();
invoice.PaymentDate = $( "#PaymentDate" ).val();
// Getting Table Data from where we will fetch Invoice Rows Record
var oTable = $( '.tableItems' ).dataTable().fnGetData();
for ( var i = 0; i < oTable.length; i++ )
{
// IF This view is for edit then it will read InvoiceId from Hidden field
if ( $( 'h2' ).text() == "Edit" )
{
invoiceRow.InvoiceID = $( '#InvoiceID' ).val();
}
else
{
invoiceRow.InvoiceID = 0;
}
// Set InvoiceRow individual Value
invoiceRow.RowDetail = oTable[i][0];
invoiceRow.RowQty = oTable[i][1];
invoiceRow.ItemPrice = oTable[i][2];
invoiceRow.RowTotal = oTable[i][3];
// adding to Invoice.InvoiceRow List Item
invoice.InvoiceRows.push( invoiceRow );
invoiceRow = { "RowDetail": "", "RowQty": "", "ItemPrice": "", "RowTotal": "" };
}
// Step 1: Ends Here
// Set 2: Ajax Post
// Here i have used ajax post for saving/updating information
$.ajax( {
url: '/Invoice/Create',
data: JSON.stringify( invoice ),
type: 'POST',
contentType: 'application/json;',
dataType: 'json',
success: function ( result )
{
if ( result.Success == "1" )
{
window.location.href = "/Invoice/Index";
}
else
{
alert( result.ex );
}
}
} );
}
</script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Invoice</legend>
#if (Model != null)
{
<input type="hidden" id="InvoiceID" name="InvoiceID" value="#Model.InvoiceID" />
}
<div class="editor-label">
#Html.LabelFor(model => model.ContractID, "Contract")
</div>
<div class="editor-field">
#Html.DropDownList("ContractID", String.Empty)
#Html.ValidationMessageFor(model => model.ContractID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceDate)
#Html.ValidationMessageFor(model => model.InvoiceDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceNumber)
#Html.ValidationMessageFor(model => model.InvoiceNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PaymentDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PaymentDate)
#Html.ValidationMessageFor(model => model.PaymentDate)
</div>
</fieldset>
<br />
<fieldset>
<legend>Add Invoice Row</legend>
<br />
<label>
Row Detail :</label>
#Html.TextBox("RowDetail")
<label>
Row Qty :</label>
#Html.TextBox("RowQty", null, new { style = "width:20px;text-align:center" })
<label>
Item Price :</label>
#Html.TextBox("ItemPrice", null, new { style = "width:70px" })
<input onclick="AddInvoiceItem()" type="button" value="Add Invoice Item" />
<table id="tableItems" class="tableItems" width="400px">
<thead>
<tr>
<th>
Detail
</th>
<th>
Qty
</th>
<th>
Price
</th>
<th>
Row Total
</th>
</tr>
</thead>
<tbody>
#if (Model != null)
{
foreach (var item in Model.InvoiceRows)
{
<tr>
<td>
#Html.DisplayFor(i => item.RowDetail)
</td>
<td>
#Html.DisplayFor(i => item.RowQty)
</td>
<td>
#Html.DisplayFor(i => item.ItemPrice)
</td>
<td>
#Html.DisplayFor(i => item.RowTotal)
</td>
</tr>
}
}
</tbody>
</table>
<br />
<input onclick="DeleteRow()" type="button" value="Delete Selected Row" />
</fieldset>
<p>
<input onclick="SaveInvoice()" type="submit" value="Save Invoice" />
</p>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
You need to examine the Model and extract the errors. With those in hand you can begin fixing the problems that are causing the model binder to fail.
Here's an extension method I use to dump invalid model errors to the output console
http://pastebin.com/S0gM3vqg

MVC3 WebGrid toggling sort direction

With an ASP.NET MVC3 WebGrid how can I setup the column sorting such that clicking on the column heading toggles between ascending sort and then descending sort?
Thanks!
Rob
The underlying problem turned out to be a malformed column. The columnName did not in fact match the actual columnName (it was set to be the header text). This problem was hidden because the format specified a Html.ActionLink.
Changing the column definition such that the columnName was correct (and the header set to the actual header value) corrected the problem.
Note 1: Using Razor Syntax for Markup and C# as ServerSide Code.
Note 2: The basic setup knowledge for Sorting came from Sorting, Filtering, and Paging with the Entity Framework Tutorial but I needed more control and leverage than what they supplied for their simple example.
So...
Here is the End-Result-View View for the Grid
Default View http://cognitivegenerationenterprises.net/cgeCorp/Images/Toggling-MVC-Grid/defaultGrid3.png
Here is my ServerSide code on the Index Action (so when my page loads) (again, see link above for more background information)
public ViewResult Index(string sortOrder)
{
// Set the defaults
ViewBag.FirstNameSortParam = "First Name desc";
ViewBag.LastNameSortParam = "Last Name desc";
ViewBag.CitySortParam = "City desc";
ViewBag.StateSortParam = "State desc";
ViewBag.DOBSortParam = "DOB desc";
var students = from s in db.Students select s;
switch (sortOrder)
{
case "First Name desc":
students = students.OrderByDescending(s => s.FirstName);
ViewBag.FirstNameSortParam = sortOrder;
break;
case "First Name asc":
students = students.OrderBy(s => s.FirstName);
ViewBag.FirstNameSortParam = sortOrder;
break;
case "Last Name desc":
students = students.OrderByDescending(s => s.LastName);
ViewBag.LastNameSortParam = sortOrder;
break;
case "Last Name asc":
students = students.OrderBy(s => s.LastName);
ViewBag.LastNameSortParam = sortOrder;
break;
case "City desc":
students = students.OrderByDescending(s => s.StudentAddress.City);
ViewBag.CitySortParam = sortOrder;
break;
case "City asc":
students = students.OrderBy(s => s.StudentAddress.City);
ViewBag.CitySortParam = sortOrder;
break;
case "State desc":
students = students.OrderByDescending(s => s.StudentAddress.State);
ViewBag.StateSortParam = sortOrder;
break;
case "State asc":
students = students.OrderBy(s => s.StudentAddress.State);
ViewBag.StateSortParam = sortOrder;
break;
case "DOB desc":
students = students.OrderByDescending(s => s.DOB);
ViewBag.DOBSortParam = sortOrder;
break;
case "DOB asc":
students = students.OrderBy(s => s.DOB);
ViewBag.DOBSortParam = sortOrder;
break;
default:
students = students.OrderByDescending(s => s.StudentID);
break;
}
return View(students.ToList());
}
The first time this runs, of course "sortOrder" will be null. However the pattern below demonstrates how I accomplished toggling of the "Last Name" column. Also, please note that I set my default 1st state to be Descending. Alternatively you could flip-it-around if you want Ascending as the 1st state registered when a user clicks on the link. For my purposes "Descending" works best.
#{
string fnSort = ViewBag.FirstNameSortParam;
string lnSort = ViewBag.LastNameSortParam;
string citySort = ViewBag.CitySortParam;
string stateSort = ViewBag.StateSortParam;
string dobSort = ViewBag.DOBSortParam;
}
<table>
<tr>
<th>
#{
if (fnSort != "First Name desc")
{
fnSort = "First Name desc";
}else{
fnSort = "First Name asc";
}
}
#Html.ActionLink("First Name", "Index", new { sortOrder = fnSort })
</th>
<th>
#{
if (lnSort != "Last Name desc")
{
lnSort = "Last Name desc";
}else{
lnSort = "Last Name asc";
}
}
#Html.ActionLink("Last Name", "Index", new { sortOrder = lnSort })
</th>
<th>
Email
</th>
<th>
Phone Number
</th>
<th>
#{
if (citySort != "City desc")
{
citySort = "City desc";
}else{
citySort = "City asc";
}
}
#Html.ActionLink("City", "Index", new { sortOrder = citySort })
</th>
<th>
#{
if (stateSort != "State desc")
{
stateSort = "State desc";
}else{
stateSort = "State asc";
}
}
#Html.ActionLink("State", "Index", new { sortOrder = stateSort })
</th>
<th>
#{
if (dobSort != "DOB desc")
{
dobSort = "DOB desc";
}else{
dobSort = "DOB asc";
}
}
#Html.ActionLink("DOB", "Index", new { sortOrder = dobSort })
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.PhoneNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.StudentAddress.City)
</td>
<td>
#Html.DisplayFor(modelItem => item.StudentAddress.State)
</td>
<td>
#Html.DisplayFor(modelItem => item.DOB)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) |
#Html.ActionLink("Details", "Details", new { id=item.StudentID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.StudentID })
</td>
</tr>
}
</table>
That's it... Toggling works now for "Last Name"... now I will implement sort toggling for the other columns as well
Hope this helps. Let me know if you need more assistance! I'm proud to help (if I can)
Cheers!

MVC3 Binding to collection data model

My question isn't so much about displaying the data its about collecting changes to the data.
My specific scenario is the need to allow users to delete multiple items from a list. I don't know if i'm even approaching this in a locgical way.
The List is a collection of Private Messages. My view model has strings for To, From, Subject, and a bool for "Delete".
public class PrivateMessagesModel {
public PrivateMessagesModel()
{
PrivateMessages = new List<PrivateMessageReceivedModel>();
}
public List<PrivateMessageReceivedModel> PrivateMessages;
}
public class PrivateMessageReceivedModel
{
[DataType(DataType.Text)]
[Display(Name = "From")]
public string From { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Subject")]
public string Subject { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Message")]
public string Message { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Date")]
public DateTime DateTimeSent { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Delete")]
public bool Delete { get; set; }
}
The code to display looks like this. And works ok.
#
model ScaleRailsOnline.Models.PrivateMessagesModel
#{
ViewBag.Title = "Private Messages";
}
<div id="content">
<div class="content">
<h2>
Private Messages</h2>
#using (Html.BeginForm())
{ <table>
#for (int i = 0; i < Model.PrivateMessages.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => m.PrivateMessages[i].Delete)
</td>
<td>
#Html.DisplayTextFor(m => m.PrivateMessages[i].From)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Delete" />
</p>
}
</div>
</div>
The problem is when I check a couple of the check boxes and hit the delete button, i get nothing back in the model.
Again, im sure i'm not approaching this in the right way. Any help would be appreciated.
You need to have a controller associated to the Html.BeginForm where the form will be posted.
eg
<div id="formid">
#using (Html.BeginForm("Index1", "Home", new AjaxOptions { UpdateTargetId = "formid",OnSuccess ="OnSuccess" }, new { id = "TheForm" }))
{
#for (int i = 0; i < Model.PrivateMessages.Count; i++)
{
#Html.CheckBoxFor(m => m.PrivateMessages[i].Delete)
}
<input type="submit" name="name" value="Submit" />
}
</div>
On Controller
public ActionResult Index1(List<PrivateMessageReceivedModel> mod)
{
//Now in the mod you will get the value of the delete checkbox.
}
You have to create a Controller class for your model. Then create method that return ActionResult type. and then create View of the newly created controller method.
Here is a sample code.
Model Class
namespace Mvc3Application1.Models
{
public class PrivateMessage
{
public string From { get; set; }
public string Subject { get; set; }
public string Message { get; set; }
public DateTime DateTimeSent { get; set; }
public bool Delete { get; set; }
}
public class PrivateMessageRepository
{
public List<PrivateMessage> GetPrivateMessages()
{
List<PrivateMessage> myPrivateMessages=new List<PrivateMessage>();
//add list of messages in the object myPrivateMessages
myPrivateMessages.Add(new PrivateMessage { From = "abc#abc.com", Subject = "Subject 1", Message = "Message 1", DateTimeSent = DateTime.Now, Delete = false });
myPrivateMessages.Add(new PrivateMessage { From = "abc1#abc.com", Subject = "Subject 2", Message = "Message 2", DateTimeSent = DateTime.Now, Delete = false });
myPrivateMessages.Add(new PrivateMessage { From = "abc2#abc.com", Subject = "Subject 3", Message = "Message 3", DateTimeSent = DateTime.Now, Delete = false });
myPrivateMessages.Add(new PrivateMessage { From = "abc3#abc.com", Subject = "Subject 4", Message = "Message 4", DateTimeSent = DateTime.Now, Delete = false });
myPrivateMessages.Add(new PrivateMessage { From = "abc4#abc.com", Subject = "Subject 5", Message = "Message 5", DateTimeSent = DateTime.Now, Delete = false });
return myPrivateMessages;
}
}
}
Controller class
namespace Mvc3Application1.Controllers {
public class PrivateMessageController : Controller
{
//
// GET: /PrivateMessage/
// Show all the private messages
public ActionResult ListMessages()
{
return View(new Models.PrivateMessageRepository().GetPrivateMessages());
}
//delete the selected messages and return the collection
[HttpPost]
public ActionResult ListMessages(FormCollection collection)
{
List<Models.PrivateMessage> messages = new Models.PrivateMessageRepository().GetPrivateMessages();//all messages
List<Models.PrivateMessage> cloneMessages = new List<Models.PrivateMessage>();//messages left for deletion
int noOfItems = 0;//no of items deleted
int currentItem = 0;//current item in collection 'messages'
string[] deletedItems = collection[0].Split(',');//return from HTML control collection
for (int i = 0; i < deletedItems.Length; i++)
{
if (bool.Parse(deletedItems[i]) == false)//if checkbox is unchecked
{
cloneMessages.Add(messages[currentItem]);//copy original message in cloneMessages collection
}
else //if checkbox is checked
{
noOfItems++;
i++;
}
currentItem++;
}
ViewBag.Items = noOfItems + " message(s) deleted.";
return View(cloneMessages);
}
} }
View
#model IEnumerable<Mvc3Application1.Models.PrivateMessage>
#{
ViewBag.Title = "Private Messages";
}
<h2>Private Messages</h2>
<div style="color:green;font-weight:bold;">#ViewBag.Items</div>
<br />
#using (Html.BeginForm())
{
<table>
<tr>
<th></th>
<th>
From
</th>
<th>
Subject
</th>
<th>
Message
</th>
<th>
DateTimeSent
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.Delete)
</td>
<td>
#Html.DisplayTextFor(modelItem => item.From)
</td>
<td>
#Html.DisplayTextFor(modelItem => item.Subject)
</td>
<td>
#Html.DisplayTextFor(modelItem => item.Message)
</td>
<td>
#Html.DisplayTextFor(modelItem => item.DateTimeSent)
</td>
</tr>
}
</table>
<input type="submit" value="Delete"/>
}

Resources