asp.net mvc paging error - asp.net-mvc

I have a pagination problem. I have a Product model, it has a string ProductCategory attribute. One page can take 4 products, whenever it exceeds 4, it points out page 2. The problem is that when I click "Car" category and click page 2, it takes every product, rather than taking only "Car" .
I got the book from the book, ASP.NET MVC 4, published by apress.
Here is my ProductListViewModel:
public class ProductsListViewModel
{
public List<Product> Products { get; set; }
public PagingInfo PagingInfo { get; set; }
public string CurrentCategory { get; set; }
}
Here is my ProductController's List Action: When I debug the application, at the click at page 2, category parameter is null.
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.ProductCategory.Equals(category))
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize).Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.ProductCategory == category).Count()
}
};
model.CurrentCategory = category;
return View(model);
}
Here is my List View:
#model SportsStore.WebUI.Models.ProductsListViewModel
#{
ViewBag.Title = "Products";
}
#foreach (var p in Model.Products)
{
<div class="item">
#Html.Partial("ProductSummary", p)
</div>
}
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x, ProductCategory = Model.CurrentCategory }))
ProductSummary is a partial view that views the product. Pagelinks is a extention methods:
public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo,
Func<int, string> pageUrl)
{
StringBuilder result = new StringBuilder();
for (int i = 1; i <= pagingInfo.TotalPages; i++)
{
TagBuilder tag = new TagBuilder("a"); // Construct an <a> tag
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
tag.AddCssClass("selected");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
As pictured above, when I click page 2, it gets every product, rather than car. How can I solve it?
Thanks in advance.
NOTE: Routes has been added below:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null,
"",
new
{
controller = "Product",
action = "List",
category = (string)null,
page = 1
}
);
/*
*http://localhost:56701/?page=2 olmasındansa http://localhost:56701/Page4 olmasını sağlayan kod parçacığı.
*
*/
routes.MapRoute(null,
"Page{page}",
new { controller = "Product", action = "List", category = (string)null },
new { page = #"\d+" }
);
routes.MapRoute(null,
"{category}",
new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}",
new { controller = "Product", action = "List" },
new { page = #"\d+" }
);
routes.MapRoute(null, "{controller}/{action}");
}
}
NOTE 2:
Here is the result when I click page 2 of the Car category:
As you see below, at the page 2, every car items exist. (Blue rectangle). But I do no want to see page 3 and 4 at the bottom of the page (Red rectangle).
Thanks in advance.

The solution has implemented in the book, and it seems it has been escaped from my attention.
Two revisions should be made in the source code, one in List.cshtml :
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x, category = Model.CurrentCategory }))
Another is in the List action of the ProductController:
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel viewModel = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.ProductCategory == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
**TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.ProductCategory == category).Count()**
},
CurrentCategory = category
};
return View(viewModel);
}
TotalItems attribute shall be updated as mentioned above.

Related

ASP.Net MVC: Recursive approach not showing nested child

I am trying to show nested data in ul/li, but nested children are not showing. See my code and please tell me what is wrong there.
Controller:
public ActionResult Index()
{
List<MenuItem> allMenu = new List<MenuItem>
{
new MenuItem {Id=1,Name="Parent 1", ParentId=0},
new MenuItem {Id=2,Name="child 1", ParentId=1},
new MenuItem {Id=3,Name="child 2", ParentId=1},
new MenuItem {Id=4,Name="child 3", ParentId=1},
new MenuItem {Id=5,Name="Parent 2", ParentId=0},
new MenuItem {Id=6,Name="child 4", ParentId=4}
};
List<MenuItem> mi = allMenu
.Where(e => e.ParentId == 0) /* grab only the root parent nodes */
.Select(e => new MenuItem
{
Id = e.Id,
Name = e.Name,
ParentId = e.ParentId,
Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
}).ToList();
ViewBag.menusList = mi;
return View();
}
POCO class:
public class MenuItem
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public virtual List<MenuItem> Children { get; set; }
}
View:
#helper ShowTree(List<Scaffolding.Controllers.MenuItem> menusList)
{
<ul>
#foreach (var item in menusList)
{
<li>
<span>#item.Name</span>
#if (item.Children != null && item.Children.Any())
{
#ShowTree(item.Children)
}
</li>
}
</ul>
}
#{
var menuList = ViewBag.menusList as List<Scaffolding.Controllers.MenuItem>;
#ShowTree(menuList);
}
If you run the code then you will see child 4 is not showing which is a child of child 3. Please advise what I need to change in my code. Thanks
Your query gets the top level elements (ParentId == 0) only and then populate just their direct child elements.
Your query needs to be changed to populate all child elements for all levels. Note that your MeuItem does not need the ParentId property.
// Group the items by parentId and project to MenuItem
var groups = allMenu.ToLookup(x => x.ParentId, x => new MenuItem
{
Id = x.Id,
Name = x.Name,
});
// Assign the child menus to all items
foreach (var item in allMenu)
{
item.children = groups[item.Id].ToList();
}
// Return just the top level items
ViewBag.menusList = groups[0].ToList();
As a side note, do not use ViewBag. Pass the model to the view instead
return View(groups[0].ToList());
and in the view
#model List<MenuItem>
....
#ShowTree(Model);
Now i could fix my problem. the problem was in logic of razor code and also i comment this line //.Where(e => e.ParentId == 0) here i am adding working code.
#helper ShowTree(List<NestedChild.Controllers.MenuItem> menu, int? parentid = 0, int level = 0)
{
var items = menu.Where(m => m.ParentId == parentid);
if (items.Any())
{
if (items.First().ParentId > 0)
{
level++;
}
<ul>
#foreach (var item in items)
{
<li>
#item.Name
</li>
#ShowTree(menu, item.Id, level);
}
</ul>
}
}
#{
var menuList = ViewBag.menusList as List<NestedChild.Controllers.MenuItem>;
#ShowTree(menuList);
}
Action
public ActionResult Index()
{
List<MenuItem> allMenu = new List<MenuItem>
{
new MenuItem {Id=1,Name="Parent 1", ParentId=0},
new MenuItem {Id=2,Name="child 1", ParentId=1},
new MenuItem {Id=3,Name="child 2", ParentId=1},
new MenuItem {Id=4,Name="child 3", ParentId=1},
new MenuItem {Id=5,Name="Parent 2", ParentId=0},
new MenuItem {Id=6,Name="child 4", ParentId=4}
};
List<MenuItem> mi = allMenu
//.Where(e => e.ParentId == 0) /* grab only the root parent nodes */
.Select(e => new MenuItem
{
Id = e.Id,
Name = e.Name,
ParentId = e.ParentId,
Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
}).ToList();
ViewBag.menusList = mi;
return View();
}

MVC checkboxes & FormCollection

On a mass-edit form page I display about 50 objects that have some boolean properties as well. The controller receives a FormCollection with all values from the edit page.
public void _EditAll(FormCollection c)
{
int i = 0;
if (ModelState.IsValid)
{
var arrId = c.GetValues("channel.ID");
var arrName = c.GetValues("channel.displayedName");
var arrCheckbox = c.GetValues("channel.isActive");
for (i = 0; i < arrId.Count(); i++)
{
Channel chan = db.Channels.Find(Convert.ToInt32(arrId[i]));
chan.displayedName = arrName[i];
chan.isActive = Convert.ToBoolean(arrCheckbox[i]);
db.Entry(chan).State = EntityState.Modified;
}
db.SaveChanges();
}
}
Now, for checkboxes, MVC creates hidden inputs on the form (otherwise "false" could not be posted back). In the controller, when receiving the FormCollection, this leads to the case that I receive an array of say
50 IDs,
50 names and ..
71 or so values for the checkboxes,
since the hidden checkbox has the same name as the visible one.
What's a good way to handle that and get the proper value of the checkbox?
Sample for editing array of entities that have boolean field.
Entity:
public class Entity
{
public int Id { get; set; }
public bool State { get; set; }
}
Controller:
public ActionResult Index()
{
Entity[] model = new Entity[]
{
new Entity() {Id = 1, State = true},
new Entity() {Id = 2, State = false},
new Entity() {Id = 3, State = true}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Entity[] entities)
{
// here you can see populated model
throw new NotImplementedException();
}
View:
#model Entity[]
#{
using (Html.BeginForm())
{
for (int i = 0; i < Model.Count(); i++ )
{
#Html.Hidden("entities[" + i + "].Id", Model[i].Id)
#Html.CheckBox("entities[" + i + "].State", Model[i].State)
}
<input type="submit"/>
}
}
The only tricky thing is html elements naming.
More info about binding arrays.
I'm converting all arrays containing checkbox-values:
"false" => "false", if not preceded by "true"

Will a DropDownList html helper work correctly with a IList<SelectListItem>?

I am having problems with getting a DropDownList to correctly select the right value and display it.
I am using the following:
#Html.DropDownListFor(x => Model.AdminSummaries[index].Status, AdminStatusReference.GetAdminStatusOptions(), new { id = string.Format("Status_{0}",index ) })
Is it okay that AdminStatusReference.GetAdminStatusOptions() returns a List or MUST it return an IEnumerable?
Model:
public class MyViewModel
{
public IList<AdminSummary> AdminSummaries { get; set; }
}
public class AdminSummary
{
public string Status { get; set; }
}
public static class AdminStatusReference
{
public static IEnumerable<SelectListItem> GetAdminStatusOptions()
{
return new[]
{
new SelectListItem { Value = "1", Text = "status 1" },
new SelectListItem { Value = "2", Text = "status 2" },
new SelectListItem { Value = "3", Text = "status 3" },
};
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
AdminSummaries = new[]
{
// preselect the first item
new AdminSummary { Status = "1" },
// preselect the second item
new AdminSummary { Status = "2" },
// nothing will be preselected because there is no xxx Value in the list
new AdminSummary { Status = "xxx" },
}.ToList()
};
return View(model);
}
}
View:
#model MyViewModel
#for (int index = 0; index < Model.AdminSummaries.Count; index++)
{
#Html.DropDownListFor(
x => x.AdminSummaries[index].Status,
new SelectList(
AdminStatusReference.GetAdminStatusOptions(),
"Value",
"Text",
Model.AdminSummaries[index].Status
)
)
}
It will be fine.
List<T> implements IEnumerable<T>
for using
#Html.DropDownListFor<>
in my projects I have always used a IEnumerable<SelectListItem> in my View and therefore before hand I have set which item has the propery "Selected" as true. This will then set the default item.
forgot I could edit my original answer :P
here is the html rendered on my project

telerik treeview asp.net mvc - link does not work for non root nodes

I am using this code in my view:
#(Html.Telerik().TreeView()
.Name("AjaxTreeView")
.BindTo(Model, (item, category) =>
{
// bind initial data - can be omitted if there is none
item.Text = category.Name;
item.Action("Details", "Categories", new { Id = category.Id });
item.Value = category.Id.ToString();
item.LoadOnDemand = category.NOChildren > 0;
})
.DataBinding(dataBinding => dataBinding
.Ajax().Select("_TreeViewAjaxLoading", "Categories")
)
)
It works fine (ajaxified expand and collapse). The action links work fine but only for the root nodes. My current controller that spews out JSON for the ajax load:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0
};
return new JsonResult { Data = nodes };
}
does not set the action link. How can I set the action link here? Thanks.
Christian
This seems to do the trick:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0,
Url = u.Action("Details", "Categories", new { Id = item.Id} )
};
return new JsonResult { Data = nodes };
}

How do you link to an action that takes an array as a parameter (RedirectToAction and/or ActionLink)?

I have an action defined like so:
public ActionResult Foo(int[] bar) { ... }
Url's like this will work as expected:
.../Controller/Foo?bar=1&bar=3&bar=5
I have another action that does some work and then redirects to the Foo action above for some computed values of bar.
Is there a simple way of specifying the route values with RedirectToAction or ActionLink so that the url's get generated like the above example?
These don't seem to work:
return RedirectToAction("Foo", new { bar = new[] { 1, 3, 5 } });
return RedirectToAction("Foo", new[] { 1, 3, 5 });
<%= Html.ActionLink("Foo", "Foo", new { bar = new[] { 1, 3, 5 } }) %>
<%= Html.ActionLink("Foo", "Foo", new[] { 1, 3, 5 }) %>
However, for a single item in the array, these do work:
return RedirectToAction("Foo", new { bar = 1 });
<%= Html.ActionLink("Foo", "Foo", new { bar = 1 }) %>
When setting bar to an array, it redirects to the following:
.../Controller/Foo?bar=System.Int32[]
Finally, this is with ASP.NET MVC 2 RC.
Thanks.
There are a few ways to do this. If you want to keep it stateless avoid using
TempData and create a action filter.
Somthing like this:
ActionFilter:
public class BindArrayAttribute:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var keys = filterContext.HttpContext.Request.QueryString.AllKeys.Where(p => p.StartsWith("id"));
var idArray = new int[keys.Count()];
var counter = 0;
foreach (var key in keys)
{
var id = filterContext.HttpContext.Request.QueryString[key];
idArray[counter] = int.Parse(id);
counter++;
}
filterContext.ActionParameters["id"] = idArray;
base.OnActionExecuting(filterContext);
}
}
Controller:
[HttpPost]
public ActionResult Index(ItemModel model)
{
var dic = new RouteValueDictionary();
var counter = 0;
foreach (var id in model.SelectedItemIds)
{
dic.Add("id" + counter, id);
counter++;
}
return RedirectToAction("Display", dic);
}
[HttpGet]
[BindArray]
public ActionResult Display(int[] id = null)
{
return View(id);
}
I'm not sure how to accomplish that using the existing helpers. But you could write your own method to do so.
Here's something I threw together:
public static string EnumerableActionLink(this HtmlHelper htmlHelper, string linkText, string controllerName, string actionName, IEnumerable enumerable, string variableName)
{
var builder = new StringBuilder(string.Format("/{0}/{1}?", controllerName, actionName));
foreach (var item in enumerable)
builder.Append(string.Format("{0}={1}&", variableName, item));
return string.Format("{1}", builder, linkText);
}
Usage example:
<%= Html.EnumerableActionLink("Foo", "Foo", "Foo", new[] { 1, 3, 5 }, "bar")%>
<%= Html.ActionLink("Foo", "Foo", "Foo",
new[] { 1, 3, 5 }.Aggregate(string.Empty, (a, x) => a += "bar=" + x + "&"))
%>

Resources