[HttpGet("{pageNumber}{pageSize?}{filter?}{sortOrder?}", Name = "GetEntriesPaged")]
public ActionResult<List<Entry>> GetEntriesPaged(
int pageNumber, int pageSize = 10, string filter = "", string sortOrder = "desc") {
Run-time exception:
System.ArgumentException: An optional parameter must be at the end of
the segment. In the segment
'{pageNumber}{pageSize?}{filter?}{sortOrder?}', optional parameter
'pageSize' is followed by 'filter'. Parameter name: routeTemplate
What is the point? I have an optional parameter at the end of the segment, as asked....
PS. The more complete code:
[HttpGet]
public ActionResult<List<Entry>> GetAll() {
var result = _db.Entries.OrderByDescending(x => x.Date).ToList();
return result;
}
[HttpGet("{pageNumber}{pageSize?}{filter?}{sortOrder?}", Name = "GetEntriesPaged")]
public ActionResult<List<Entry>> GetEntriesPaged(int pageNumber = 1, int pageSize = 10, string filter = "", string sortOrder = "desc") {
int take = pageSize;
int skip = ((pageNumber - 1) * pageSize);
IQueryable<Entry> result;
if (sortOrder == "asc") {
result = _db.Entries.OrderBy(x => x.Date);
}
else {
result = _db.Entries.OrderByDescending(x => x.Date);
}
return result.Skip(skip).Take(take).Where(x => x.Origin.Contains(filter)).ToList();
}
[HttpGet("{id}", Name = "GetEntry")]
public ActionResult<Entry> GetById(long id) {
var item = _db.Entries.Find(id);
if (item == null) {
return NotFound();
}
return item;
}
I need for /entries the GetAll() method to be used, but with /esntries?pageNumber=3 the GetEntriesPaged(...) one
No you don't. sortOrder is at the end, but pageSize and filter are not. In short, you cannot have multiple optional parameters like this. It creates too many combinations of routes, which would render it impossible to determine how to route the request or what particular route params to fill. For example, what if you fill in pageSize and sortOrder but not filter? How is ASP.NET Core supposed to know that what you supplied for sortOrder is not actually meant for filter?
FWIW, you also need slashes between these route params. Otherwise, there's no way to know where one ends and the next begins. For example, is a route like /111 saying page one with a size of 11, page 11 with a size of 1, or page 111, and no size set? That still doesn't help you with having them all be optional though.
If you need multiple optional things in the URL, it's best to just use the query string to supply them.
UPDATE
You don't need and really shouldn't have multiple actions for paged or not. There's too much common functionality and the difference is too slight. The typical formula is:
public async Task<IActionResult> GetAll(int? page = null, int pageSize = 10)
{
var query = _context.Foos;
if (page.HasValue)
{
query = query.Skip((page.Value - 1) * pageSize).Take(pageSize);
}
return Ok(await query.ToListAsync());
}
Related
In my website with paging and search options i have to redirect the user and pass the search options. With this action I don't want to pass the search model with the URL (ex. http://localhost:1234/?Project.Models.TestModel=....).
My actionlink right now is as follows:
#Html.ActionLink(i.ToString(), "Index", new { idtrips = Model.idTrips, inituser = Model.initUser, date = Model.date.ToString("ddMMyyyy"), page = i})
When clicked this results in a following header:
http://localhost:58763/?idtrips=0&inituser=0&date=05042016&page=5
My question is:
Can you somehow add if clauses to the Html.ActionLink to only give values if they are needed. For example I only have a date set, then I want the Url to be: http://localhost:58763/?date=05042016&page=5. The other values have a default value in my controller so this url will work but i'm not able to generate it.
My controller:
public ActionResult Index(long idtrips = 0, long inituser = 0, string date = "", int page = 1)
{ ... }
What i'm looking for is something like this,:
#Html.ActionLink(i.ToString(), "Index", new { if(Model.idTrips > 0) { idtrips = Model.idTrips,} page = i})
You could probably accomplish this with a ternary statement (e.g. an inline-if statement) :
new { idtrips = Model.idTrips > 0 ? Model.IdTrips : 0, page = i}
Since you have a default parameter value for idtrips, this should only set it to something else if it is greater than 0.
If you don't want the value to appear at all, you could consider simply making it a nullable long long? parameter (and ensure this matches on your Model as well):
public ActionResult Index(long? idtrips = 0, ... ) { ... }
Using this approach, you could simply set it as expected :
new { idtrips = Model.idTrips, page = i}
And if Model.idTrips was null (e.g. the default value), then it would point to :
Controller/Index?page=1
However, if it was any non-null value, it would render the querystring parameter as expected :
Controller/Index?idtrips=42&page=1
I am trying to use PagedList.MVC I went to GitHub (https://github.com/troygoode/PagedList) and looked at the examples. I am Referring to "Example 2: Manual Paging". When I use this code I get this error:
Argument "1: cannot convert from 'System.Web.Security.MembershipUserCollection' to 'System.Collections.Generic.IEnumerable'"
Looking at StaticPagedList documentation, it does look to me like the first parameter is System.Collections.Generic.IEnumerable<T> and The sample code (below) for var users is of type System.Web.Security.MembershipUserCollection I need to find a way to get the first parameter to work. I am not sure how to solve this problem. (I have a "New Driver" bumper sticker.)
public class UserController : Controller
{
public object Index(int? page)
{
var pageIndex = (page ?? 1) - 1; //MembershipProvider expects a 0 for the first page
var pageSize = 10;
int totalUserCount; // will be set by call to GetAllUsers due to _out_ paramter :-|
var users = Membership.GetAllUsers(pageIndex, pageSize, out totalUserCount);
var usersAsIPagedList = new StaticPagedList<MembershipUser>(users, pageIndex + 1, pageSize, totalUserCount);
ViewBag.OnePageOfUsers = usersAsIPagedList;
return View();
}
}
I am using a third party library for a Grid which uses fixed querystring parameters as shown below.
/Home/GetData/?$skip=0&$top=10
These parameters have a $ in their key and I wanted to know if there is a way to still have the MVC model binding work for these parameters.
i.e.
applying them to this action (which won't compile because of the $ in the parameter names.
public ActionResult GetData(int $skip, int $top)
{
...
return View();
}
Thanks to Andrei for pointing me in the right direction.
The below solutions both do the trick.
Via prefix alias model binding
public ActionResult GetData([Bind(Prefix = "$top")]int top = 0, [Bind(Prefix = "$skip")]int skip = 0)
{
return View();
}
By Request object to get the Querystring values
public ActionResult GetData()
{
var topParam = Request.QueryString["$top"];
var skipParam = Request.QueryString["$skip"];
var top = 0;
int.TryParse(topParam, out top);
var skip = 0;
int.TryParse(skipParam, out skip);
return View();
}
I am new to MVC3 and cannot move with this problem.
I am making a simple blog with posts, that are divided into categories and each post may have some tags.
If I show posts to user, I have paging there, where url is like localhost/Posts/1, where "1" is number of the page.
But how can I do this if I want to show posts only from some category or with some tag?
It is in format localhost/Posts/Categories/1, where "1" is id of the category or localhost/Posts/Tags/tag1 where "tag1" is particular tag
I would like to change it all to be in format localhost/Posts/Page/1 or localhost/Posts/Categories/1/Page/1 or localhost/Posts/Tags/tag1/Page/1, but I really cannot find out how to achieve this in controller.
So my question is: How to make methods in controller to accept these complex urls?
I guess it has something to do with routing, but couldn't find any explanation of my problem.
Thanks a lot for any help.
My code:
public ActionResult Tags(string id)
{
Tag tag = GetTag(id);
ViewBag.IdUser = IDUser;
if (IDUser != -1)
{
ViewBag.IsAdmin = IsAdmin;
ViewBag.UserName = model.Users.Where(x => x.IDUser == IDUser).First().Name;
}
return View("Index", tag.Posts.OrderByDescending(x => x.DateTime));
}
public ActionResult Index(int? id)
{
int pageNumber = id ?? 0;
IEnumerable<Post> posts =
(from post in model.Posts
where post.DateTime < DateTime.Now
orderby post.DateTime descending
select post).Skip(pageNumber * PostsPerPage).Take(PostsPerPage + 1);
ViewBag.IsPreviousLinkVisible = pageNumber > 0;
ViewBag.IsNextLinkVisible = posts.Count() > PostsPerPage;
ViewBag.PageNumber = pageNumber;
ViewBag.IdUser = IDUser;
if (IDUser != -1)
{
ViewBag.IsAdmin = IsAdmin;
ViewBag.UserName = model.Users.Where(x => x.IDUser == IDUser).First().Name;
}
return View(posts.Take(PostsPerPage));
}
Create new routes to direct those URL patterns to your controller (or another contoller, as appropriate)
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/asp-net-mvc-routing-overview-cs
For example, this route definition
routes.MapRoute(
"CategoryPage", // Route name
"Posts/Categories/{CategoryID}/Page/{PageID}", // URL with parameters
new { controller = "Home", action = "ViewPage", CategoryID = "", PageID="" } // Parameter defaults
);
Would be picked up by this action in the HomeController:
public ActionResult ViewPage(int CategoryID, int PageID)
I've created a SelectList from a enum. The enum has a description, and the int value is being set to the value I want to store in the database.
The problem is, the default (BLANK) I set on construction isn't being used.
This is my enum:
public enum Stage
{
[Description("")]
BLANK = -99,
[Description("No Data")]
NoData = 9999,
[Description("Nil")]
Nil = 0,
[Description("Action")]
SAction = 1,
[Description("Action Plus")]
SActionPlus = 2,
[Description("Full")]
Full = 3
}
I create it in my controller:
private static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
return new SelectList(values, "Id", "Name", (int)Stage.BLANK);
}
Where I thought the final parameter set the selected item.
I assign it as ViewData, and access it like:
<%= Html.DropDownList("Stage", (IEnumerable<SelectListItem>)ViewData["StageList"])%>
However, Nil is always the selected value.
What am I missing here??
Thanks!
The last parameter does determine the selected value. However, you are passing a Stage enumerated value as the last parameter, while the actual elements of your list are made up of an ID value and a Stage value. To make this work, you have to pass it the actual object from values with a Stage value of BLANK.
Iainie,
Using your code, i managed to get this working first time. here's my amended code (using the accountcontroller for testing) [using .net 3.5]:
// from account controller - put the enum, etc in there for brevity
public enum Stage
{
[Description("")]
BLANK = -99,
[Description("No Data")]
NoData = 9999,
[Description("Nil")]
Nil = 0,
[Description("Action")]
SAction = 1,
[Description("Action Plus")]
SActionPlus = 2,
[Description("Full")]
Full = 3
}
public static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
var sellist= new SelectList(values, "Id", "Name", (int)Stage.BLANK);
return sellist;
}
public virtual ActionResult LogOn()
{
var res = GenerateSenStageList();
ViewData["StageList"] = res;
return View();
}
// the ToDescription() extension method
public static class Extn
{
public static string ToDescription(this Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
var attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
}
// then in the LogOn view:
<%= Html.DropDownList("Stage", (IEnumerable<SelectListItem>)ViewData["StageList"])%>
this all works exactly as you'd hoped for, so I'm wondering if your invocation from the view is somehow getting a bit fuddled. try my example above and see if there are any subtle differences in the selectlist generated code etc.
Just a guess, but:
You cast the ViewData["StageList"] to IEnumerable<SelectListItem>. That enumerable may be the SelectList that you created in the Controller, but it does not have the SelectedValue property.
Maybe it works if you cast ViewData["StageList"] to SelectList instead?
<%= Html.DropDownList("Stage", (SelectList)ViewData["StageList"])%>
In this case using the interface may be the wrong thing, because you actually need the information provided by the SelectList object.
#Ken Wayne VanderLinde is right. If you are using C# 4.0, then you can do this to populate the selected value:
private static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
return new SelectList(values, "Id", "Name", values.Cast<dynamic>().Where(x => (x.ID == (int)Stage.BLANK)));
}