ASP.NET MVC Webgrid Efficient Paging - asp.net-mvc

I have a ASP.NET MVC 4 project and a SQL View (vvItem).
ItemController
MVCAppEntities db = new MVCAppEntities();
public ActionResult Index()
{
var itemqry = db.vvItem.OrderBy(s => s.name);
//var pageditems = itemqry.Skip(10).Take(20); // 25 seconds
return View(itemqry.ToList()); // 88 seconds
}
Index.cshtml View
#model IEnumerable<MVCApplication1.Models.vvItem>
#{
var norows = 20;
var grid = new WebGrid(Model, canPage: true, rowsPerPage: norows);
grid.Pager(WebGridPagerModes.NextPrevious);
#grid.GetHtml(tableStyle: "table", headerStyle: "header", columns: grid.Columns(
grid.Column(columnName: "name", header: "Name", canSort: true),
grid.Column(columnName: "quantity", header: "Quantity", canSort: true),
grid.Column(columnName: "code", header: "Code", canSort: true),
grid.Column(columnName: "Price", header: "Price", canSort: true),
))}
In vvItem I have almost 400000 records. I thought that the webgrid Pager would load (Take()) only the displayed records and it would know to Skip() the first records if I would go to the next pages.
Q : How can I efficiently make a view to load only the displayed records ?
I found 2 solutions : JSON version and NerdDinner
I'm not so good at JSON so I tried the NerdDinner solution. And as in my commented line
//var pageditems = itemqry.Skip(10).Take(20); itemqry is already loaded with all the records and it took a lot of time to load.
Q2 : How can I do paging now ? I need to modify the page no. from the Index method.
public ActionResult Index(int? page, string filter1 = " ", string filter2 = " ")

I made a SQL Stored Procedure
CREATE PROCEDURE SkipTake
#pagNo int,
#pageSize int
AS
select *
from (select *, row_number() over (order by COD) as rn
from vvSTOC
) as T
where T.rn between (#pagNo - 1) * #pageSize + 1 and #pagNo * #pageSize
I've added this sp in my EF model at Function Import so that it returns an entity (vvSTOC)
public ActionResult Index(int? page)
{
const int pageSize = 20;
return View(db.spSkipTake(page, pageSize).ToList());
}

It shouldn't execute the query right away as long as itemqry is an IEnumerable or IQueryable type. Can you cast it as an IQueryable as follows?
public ActionResult Index()
{
IQueryable<vvItem> itemQry = db.vvItem;
return View(itemQry.OrderBy(s => s.name).Skip(10).Take(20).ToList());
}
If the itemqry is the correct type it won't get executed until the .ToList() is called which converts it to an IList type. Check the SQL that is being produced to be sure.

public ActionResult Index(int? pageIndex)
{
int pageSize = 20;
var itemIndex = ((pageIndex??1) -1) * pageSize;
return View(db.vvItem.OrderBy(s => s.name).Skip(itemIndex).Take(pageSize).ToList());
}

Thanks for this.
I tweaked this to work with some input parameters in my SP to control the PageSize, PageNumber, SortIndex and SortOrder:
declare #sql nvarchar(1000) = '
select * from
(
select *, row_number() over(order by '+#SortIndex+' '+#SortOrder+') as RowNumber from #Results
) as T
where T.RowNumber between ('+#PageNumber+' - 1) * '+#PageSize+' + 1 and '+#PageNumber+' * '+#PageSize
exec sp_executesql #sql

Related

pagination in ASP.NET MVC without using any external nuget package

Could you please let me know is there any way to do the pagination in ASP.NET MVC without using any external nuget package(Eg : PageList.mvc). Actually I'm looking for a server side solution to do the pagination in MVC itself.
Please help me if some one know the answer.
Thanks
Nishad
You can use WebGrid. It's not a NuGet package, it's part of the System.Web.Helpers namespace and provides pagination functionality out of the box.
1.Model:
public class Product
{
public int ID { get; set; }
public string Description { get; set; }
}
2.View:
#model IEnumerable<MVCTutorial.Models.Product>
#{
Layout = null;
WebGrid grid = new WebGrid(Model, canPage: true, canSort: false, rowsPerPage: 2);
}
#grid.GetHtml(
tableStyle: "table",
columns: grid.Columns(
grid.Column("ID", "ID", format: #<text> #item.ID
</text>, style: "p13"),
grid.Column("Description","Description", format: #<text> #item.Description</text>)))
3.Controller:
public class HomeController : Controller
{
public ActionResult GetProducts()
{
var p1 = new Product { ID = 1, Description = "Product 1" };
var p2 = new Product { ID = 2, Description = "Product 2" };
var p3 = new Product { ID = 3, Description = "Product 3" };
var p4 = new Product { ID = 4, Description = "Product 4" };
var products = new List<Product> { p1, p2, p3, p4 };
return View(products);
}
}
You can use LINQ and pass the pageNumber and resultsPerPage to a controller.
var entries = _dbContext.YourTable.OrderBy(e => e.Date).Skip(pageNumber - 1).Take(resultsPerPage);
Your logic should also calculate how many pages there will be by getting the count of records for your particular query and doing some basic math.
Here's a good example: http://jasonwatmore.com/post/2015/10/30/ASPNET-MVC-Pagination-Example-with-Logic-like-Google.aspx

Webgrid not refreshing after delete MVC

I am deleting a row in my webgrid , and redirecting to my GET index as normal after delete.
However as expected my view is showing the old deleted row still in the grid. I know this is by design and i should be able to set the Modelstate to clear , or remove the item individually to achieve this :
ModelState.Remove("id");
Or
foreach (var key in ModelState.Keys.Where(m => m.StartsWith("id")).ToList())
ModelState.Remove(key);
Or
ModelState.Remove("Applicant.ApplicantId");
Or even :
ModelState.Clear()
However none have working for me.
Here is my delete method (simplified -all error handling and non necessary code removed )
public ActionResult DeleteApplicant(int id)
{
var q =
(from a in context.Applicants
where a.ApplicantId == id
select a).Single<Applicant>();
q.ApplicantDocuments.ToList().ForEach(r => context.ApplicantDocuments.Remove(r));
context.Applicants.Remove(q);
context.SaveChanges();
foreach (var key in ModelState.Keys.Where(m => m.StartsWith("id")).ToList())
ModelState.Remove(key);
TempData["SuccessMessage"] = "Successfully deleted row.";
return RedirectToAction("Index");
}
And here is my call to my delete method from the view :
, grid.Column(format: (item) => #Html.ActionLink("Delete", "DeleteApplicant",
new { id = item.ApplicantId }, new { #class = "delete-link" }), style: "DeleteButton")
I have looked at various posts on stackoverflow etc but none seem to solve my issue : MVC 3 The View is not being refreshed after model submit
I have tried also tried re-directing to the Index action via jquery and calling the delete controller action with ajaxOptions , but this hasn't worked either.
, grid.Column(format: (item) => #Ajax.ActionLink("Delete", "DeleteApplicant",
new { id = item.ApplicantId },
new AjaxOptions { HttpMethod = "GET" , OnSuccess= "reloadGrid" }))
And adding a little script to call the home index:
function reloadGrid() {
var pathArray = window.location.pathname.split('/');
var segment_1 = pathArray[1];
var newURL = window.location.protocol + "//" + window.location.host + "/" + segment_1 + "/Home/Index";
$.ajax(
{
type: "GET",
url: newURL,
data: "{}",
cache: false,
dataType: "html",
success: function (data)
{ $().html(data); }
})
}
I am obviously doing something wrong. Is there any other way i can force a refresh of the page in a different way or can anyone spot anything obviously wrong in my current approach. Thanks in advance.
Note: I do have my webgrid markup on my Index view and not in a separate partial viewbut i don't think this should be causing this?
Update :
Here is up GET method as requested: (for context, the filters object is used when search button is clicked and page is posted back ,no filters applied in initial page load)
public ActionResult Index(string page)
{
/////Peform paging initiation
int pageIndex = 0;
if (!string.IsNullOrEmpty(page))
{
pageIndex = int.Parse(page) - 1;
}
////initialize total record count and filter results object
int totalRecordCount = 0;
var filters = new ApplicantSearchFilter();
////Perform search with paging
var applicants = this.GetApplicantsPaging(filters ,pageIndex, out totalRecordCount);
////Return view type
var data = new ApplicantViewModel()
{
Filters =filters ,
TotalRecordCount = totalRecordCount,
ApplicantReportLists = applicants,
PageIndex = pageIndex,
};
////return Index view passing data result to build it
return this.View("Index", data);
}
}
I have finally managed to get this working.
If it helps anyone else here is what i did in the end. I used a solution posted by Dror here : How to achieve edit and delete on Webgrid of MVC3 Razor? . Where he turned a GET delete call from the webgrid into a post. Not exactly what i was looking for but it works.
Does anyone have a better , alternative solution ?
View Code :
added Function :
#functions{
string Delete(dynamic p)
{
string actionController = Url.Action("Delete", "Admin", new {id=p.AccountId});
return "<form style='display:inline;' method='post' action='" + actionController + "'><input type='submit' value='Delete' onclick=\"return confirm('Are you sure?')\"/></form>";
}
}
and changed my delete call in my webgrid to :
grid.Column(header: "", format: p => Html.Raw(Delete(p)))
In the Controller:
[HttpPost]
public ActionResult Delete(int id)
{
PerformDelete(id);
return RedirectToAction("Index");
}

ASP.NET MVC read controller other entity

I have 2 entities Orders and Items with OrdersController and ItemsController.
In my Orders Index View I have a webgrid
grid.Column(columnName: "OrderNo", header: "Order No.", canSort: true),
grid.Column(columnName: "OrderDate", header: "Order Date", canSort: true),
grid.Column(" ", " ", format: #Items)
In ItemsController I've added to Index a parameter :
public ActionResult Index(string id = "0")
{
var orderq = from a in db.items
where a.OrderNo== id
select a;
return View(orderq.ToList()); // ex : orderq.Count = 2
}...
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
} // Object reference not set to an instance of an object
But I get Object reference not set to an instance of an object.
Q : What do I have to to open the items view from my orders ?
Do I have to clear my context and declare it again ?
Order.cshtml
grid.Column(" ", " ", format: #Items)));
ItemsController.cs
public ActionResult Index(string id = "0")
{
var itemsqry = db.items.Where(a=>a.OrderNo == id);
return View(itemsqry .ToList());
}

Error When Submitting Asp.Net MVC Form

ISSUE
I am trying to submit an asp.net mvc form and I get the following error. When the page initially loads it hits the GridData method successfully. If I click the Submit button in the view below, I get the following error.
ERROR
The parameters dictionary contains a null entry for parameter 'page' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.JsonResult GridData(System.String, System.String, Int32, Int32)' in 'HunterEdge.Web.Controllers.DataController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
PARTIAL VIEW: This is the view data I'm trying to submit
#model HunterEdge.Web.Models.HarvestDataFilter
#using (Html.BeginForm("GridData", "Data"))
{
<div style=" width:300px; height:550px; float:left">
html removed for brevity
<input type="submit" value="Search" style=" margin-left:110px" />
</div>
}
CONTROLER ACTION METHOD I'M TRYING TO GET FORM DATA TO
public JsonResult GridData(string sidx, string sord, int page, int rows, HarvestDataFilter filter)
{
var results = (from a in db.t_harvest_statistics_elk
where a.year == "2008" && a.unit_number == 1
orderby a.id
select new { a.id, a.year, a.unit_number, a.total_hunters, a.bulls, a.cows }).ToList();
int pageIndex = Convert.ToInt32(page) - 1;
int pageSize = rows;
int totalRecords = results.Count();
int totalPages = (int)Math.Ceiling((float)totalRecords / (float)pageSize);
var pageResults = results.Skip(pageIndex * pageSize).Take(pageSize);
var jsonData = new
{
total = totalPages,
page,
records = totalRecords,
rows = (
from pageResult in pageResults
select new
{
id = pageResult.id,
cell = new[] {
pageResult.year.ToString(),
"Add",
pageResult.unit_number.ToString(),
pageResult.total_hunters.ToString(),
pageResult.bulls.ToString(),
"add",
pageResult.cows.ToString(),
"add",
"add",
"add"
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
You should filter the grid data in another way. The usage of Html.BeginForm is the wrong way. Look at the answer and use either the filter toolbar or advanced searching. If you do decide to use you custom <input> field you should use postData with the property defined as function (see here) and use .trigger("reloadGrid", [{page: 1}]) to send the searching request to the controller. In the case you should add new parameter which corresponds the property name in the postData to the list of parameters of GridData.

Razor Nested WebGrid

How do I have nested WebGrid with lot of formatting for each column. I can do a nested for-loop, but I need it basically for paging. Or is there any other better option?
Excuse the verbose data setup but this works...
#{
var data = Enumerable.Range(0, 10).Select(i => new { Index = i, SubItems = new object[] { new { A = "A" + i, B = "B" + (i * i) } } }).ToArray();
WebGrid topGrid = new WebGrid(data);
}
#topGrid.GetHtml(columns:
topGrid.Columns(
topGrid.Column("Index"),
topGrid.Column("SubItems", format: (item) =>
{
WebGrid subGrid = subGrid = new WebGrid(item.SubItems);
return subGrid.GetHtml(
columns: subGrid.Columns(
subGrid.Column("A"),
subGrid.Column("B")
)
);
})
)
)
Renders:
Of course you'll have to make sure in the GetHtml() method calls you give each grid (both top and sub) unique parameter names for paging/sorting or you'll end up with conflicts.

Resources