diff --git a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Students/Index.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Students/Index.cshtml.cs
index 982be9802642..f7ac236f6718 100644
--- a/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Students/Index.cshtml.cs
+++ b/aspnetcore/data/ef-rp/intro/samples/cu30/Pages/Students/Index.cshtml.cs
@@ -1,5 +1,4 @@
-#region snippet_All
-using ContosoUniversity.Data;
+using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
@@ -10,6 +9,7 @@
namespace ContosoUniversity.Pages.Students
{
+ #region snippet_All
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
@@ -71,5 +71,5 @@ public async Task OnGetAsync(string sortOrder,
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
+ #endregion
}
-#endregion
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs
index 6f12b9ef287a..cdb2f027a750 100644
--- a/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs
+++ b/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs
@@ -1,4 +1,3 @@
-#region snippet_All
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
@@ -10,10 +9,10 @@
namespace ContosoUniversity.Pages.Students
{
+ #region snippet_All
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
-
public IndexModel(SchoolContext context)
{
_context = context;
@@ -28,6 +27,7 @@ public IndexModel(SchoolContext context)
public async Task OnGetAsync(string sortOrder)
{
+ // using System;
#region snippet_Ternary
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
@@ -59,5 +59,5 @@ public async Task OnGetAsync(string sortOrder)
#endregion
}
}
+ #endregion
}
-#endregion
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs
index 0ee8d449334c..38e918485e7a 100644
--- a/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs
+++ b/aspnetcore/data/ef-rp/intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs
@@ -1,5 +1,4 @@
-#region snippet_All
-using ContosoUniversity.Data;
+using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
@@ -10,6 +9,7 @@
namespace ContosoUniversity.Pages.Students
{
+ #region snippet_All
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
@@ -60,5 +60,5 @@ public async Task OnGetAsync(string sortOrder, string searchString)
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
+ #endregion
}
-#endregion
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Models/SchoolViewModels/EnrollmentDateGroup.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/Models/SchoolViewModels/EnrollmentDateGroup.cs
new file mode 100644
index 000000000000..829bee1480c6
--- /dev/null
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Models/SchoolViewModels/EnrollmentDateGroup.cs
@@ -0,0 +1,13 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace ContosoUniversity.Models.SchoolViewModels
+{
+ public class EnrollmentDateGroup
+ {
+ [DataType(DataType.Date)]
+ public DateTime? EnrollmentDate { get; set; }
+
+ public int StudentCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml
new file mode 100644
index 000000000000..86467fc986b8
--- /dev/null
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml
@@ -0,0 +1,31 @@
+@page
+@model ContosoUniversity.Pages.AboutModel
+
+@{
+ ViewData["Title"] = "Student Body Statistics";
+}
+
+
Student Body Statistics
+
+
+
+
+ Enrollment Date
+ |
+
+ Students
+ |
+
+
+ @foreach (var item in Model.Students)
+ {
+
+
+ @Html.DisplayFor(modelItem => item.EnrollmentDate)
+ |
+
+ @item.StudentCount
+ |
+
+ }
+
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml.cs
new file mode 100644
index 000000000000..b5cb69078383
--- /dev/null
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/About.cshtml.cs
@@ -0,0 +1,37 @@
+using ContosoUniversity.Models.SchoolViewModels;
+using ContosoUniversity.Data;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using ContosoUniversity.Models;
+
+namespace ContosoUniversity.Pages
+{
+ public class AboutModel : PageModel
+ {
+ private readonly SchoolContext _context;
+
+ public AboutModel(SchoolContext context)
+ {
+ _context = context;
+ }
+
+ public IList Students { get; set; }
+
+ public async Task OnGetAsync()
+ {
+ IQueryable data =
+ from student in _context.Students
+ group student by student.EnrollmentDate into dateGroup
+ select new EnrollmentDateGroup()
+ {
+ EnrollmentDate = dateGroup.Key,
+ StudentCount = dateGroup.Count()
+ };
+
+ Students = await data.AsNoTracking().ToListAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml
index c4ac0a015677..29476aea8e9f 100644
--- a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml
@@ -2,47 +2,86 @@
@model ContosoUniversity.Pages.Students.IndexModel
@{
- ViewData["Title"] = "Index";
+ ViewData["Title"] = "Students";
}
-Index
+Students
Create New
+
+
+
- @Html.DisplayNameFor(model => model.Student[0].LastName)
+
+ @Html.DisplayNameFor(model => model.Students[0].LastName)
+
|
- @Html.DisplayNameFor(model => model.Student[0].FirstMidName)
+ @Html.DisplayNameFor(model => model.Students[0].FirstMidName)
|
- @Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
+
+ @Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
+
|
|
-@foreach (var item in Model.Student) {
-
-
- @Html.DisplayFor(modelItem => item.LastName)
- |
-
- @Html.DisplayFor(modelItem => item.FirstMidName)
- |
-
- @Html.DisplayFor(modelItem => item.EnrollmentDate)
- |
-
- Edit |
- Details |
- Delete
- |
-
-}
+ @foreach (var item in Model.Students)
+ {
+
+
+ @Html.DisplayFor(modelItem => item.LastName)
+ |
+
+ @Html.DisplayFor(modelItem => item.FirstMidName)
+ |
+
+ @Html.DisplayFor(modelItem => item.EnrollmentDate)
+ |
+
+ Edit |
+ Details |
+ Delete
+ |
+
+ }
+
+@{
+ var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
+ var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
+}
+
+
+ Previous
+
+
+ Next
+
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml.cs
index c65d64f35c4c..1a87ff1a8de5 100644
--- a/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml.cs
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/Pages/Students/Index.cshtml.cs
@@ -1,34 +1,73 @@
using ContosoUniversity.Data;
using ContosoUniversity.Models;
-using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Options;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
- #region snippet
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
- private readonly MvcOptions _mvcOptions;
- public IndexModel(SchoolContext context, IOptions mvcOptions)
+ public IndexModel(SchoolContext context)
{
_context = context;
- _mvcOptions = mvcOptions.Value;
}
- public IList Student { get;set; }
+ public string NameSort { get; set; }
+ public string DateSort { get; set; }
+ public string CurrentFilter { get; set; }
+ public string CurrentSort { get; set; }
- public async Task OnGetAsync()
+ public PaginatedList Students { get; set; }
+
+ public async Task OnGetAsync(string sortOrder,
+ string currentFilter, string searchString, int? pageIndex)
{
- Student = await _context.Students.Take(
- _mvcOptions.MaxModelBindingCollectionSize).ToListAsync();
+ CurrentSort = sortOrder;
+ NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
+ DateSort = sortOrder == "Date" ? "date_desc" : "Date";
+ if (searchString != null)
+ {
+ pageIndex = 1;
+ }
+ else
+ {
+ searchString = currentFilter;
+ }
+
+ CurrentFilter = searchString;
+
+ IQueryable studentsIQ = from s in _context.Students
+ select s;
+ if (!String.IsNullOrEmpty(searchString))
+ {
+ studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
+ || s.FirstMidName.Contains(searchString));
+ }
+ switch (sortOrder)
+ {
+ case "name_desc":
+ studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
+ break;
+ case "Date":
+ studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
+ break;
+ case "date_desc":
+ studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
+ break;
+ default:
+ studentsIQ = studentsIQ.OrderBy(s => s.LastName);
+ break;
+ }
+
+ int pageSize = 3;
+ Students = await PaginatedList.CreateAsync(
+ studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
- #endregion
}
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/intro/samples/cu50/PaginatedList.cs b/aspnetcore/data/ef-rp/intro/samples/cu50/PaginatedList.cs
new file mode 100644
index 000000000000..6693a30048d3
--- /dev/null
+++ b/aspnetcore/data/ef-rp/intro/samples/cu50/PaginatedList.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+
+namespace ContosoUniversity
+{
+ public class PaginatedList : List
+ {
+ public int PageIndex { get; private set; }
+ public int TotalPages { get; private set; }
+
+ public PaginatedList(List items, int count, int pageIndex, int pageSize)
+ {
+ PageIndex = pageIndex;
+ TotalPages = (int)Math.Ceiling(count / (double)pageSize);
+
+ this.AddRange(items);
+ }
+
+ public bool HasPreviousPage
+ {
+ get
+ {
+ return (PageIndex > 1);
+ }
+ }
+
+ public bool HasNextPage
+ {
+ get
+ {
+ return (PageIndex < TotalPages);
+ }
+ }
+
+ public static async Task> CreateAsync(
+ IQueryable source, int pageIndex, int pageSize)
+ {
+ var count = await source.CountAsync();
+ var items = await source.Skip(
+ (pageIndex - 1) * pageSize)
+ .Take(pageSize).ToListAsync();
+ return new PaginatedList(items, count, pageIndex, pageSize);
+ }
+ }
+}
\ No newline at end of file
diff --git a/aspnetcore/data/ef-rp/sort-filter-page.md b/aspnetcore/data/ef-rp/sort-filter-page.md
index 35dba93dab0a..7a0c75bed858 100644
--- a/aspnetcore/data/ef-rp/sort-filter-page.md
+++ b/aspnetcore/data/ef-rp/sort-filter-page.md
@@ -27,25 +27,26 @@ The following illustration shows a completed page. The column headings are click
Replace the code in *Pages/Students/Index.cshtml.cs* with the following code to add sorting.
-[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs?name=snippet_All&highlight=21-24,26,28-52)]
+[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs?name=snippet_All)]
The preceding code:
+* Requires adding `using System;`.
* Adds properties to contain the sorting parameters.
* Changes the name of the `Student` property to `Students`.
* Replaces the code in the `OnGetAsync` method.
-The `OnGetAsync` method receives a `sortOrder` parameter from the query string in the URL. The URL (including the query string) is generated by the [Anchor Tag Helper](xref:mvc/views/tag-helpers/builtin-th/anchor-tag-helper).
+The `OnGetAsync` method receives a `sortOrder` parameter from the query string in the URL. The URL and query string is generated by the [Anchor Tag Helper](xref:mvc/views/tag-helpers/builtin-th/anchor-tag-helper).
-The `sortOrder` parameter is either "Name" or "Date." The `sortOrder` parameter is optionally followed by "_desc" to specify descending order. The default sort order is ascending.
+The `sortOrder` parameter is either `Name` or `Date`. The `sortOrder` parameter is optionally followed by `_desc` to specify descending order. The default sort order is ascending.
-When the Index page is requested from the **Students** link, there's no query string. The students are displayed in ascending order by last name. Ascending order by last name is the default (fall-through case) in the `switch` statement. When the user clicks a column heading link, the appropriate `sortOrder` value is provided in the query string value.
+When the Index page is requested from the **Students** link, there's no query string. The students are displayed in ascending order by last name. Ascending order by last name is the `default` in the `switch` statement. When the user clicks a column heading link, the appropriate `sortOrder` value is provided in the query string value.
`NameSort` and `DateSort` are used by the Razor Page to configure the column heading hyperlinks with the appropriate query string values:
[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs?name=snippet_Ternary)]
-The code uses the C# conditional operator [?:](/dotnet/csharp/language-reference/operators/conditional-operator). The `?:` operator is a ternary operator (it takes three operands). The first line specifies that when `sortOrder` is null or empty, `NameSort` is set to "name_desc." If `sortOrder` is **not** null or empty, `NameSort` is set to an empty string.
+The code uses the C# [conditional operator ?:](/dotnet/csharp/language-reference/operators/conditional-operator). The `?:` operator is a ternary operator, it takes three operands. The first line specifies that when `sortOrder` is null or empty, `NameSort` is set to `name_desc`. If `sortOrder` is ***not*** null or empty, `NameSort` is set to an empty string.
These two statements enable the page to set the column heading hyperlinks as follows:
@@ -60,7 +61,7 @@ The method uses LINQ to Entities to specify the column to sort by. The code init
[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs?name=snippet_IQueryable)]
-When an`IQueryable` is created or modified, no query is sent to the database. The query isn't executed until the `IQueryable` object is converted into a collection. `IQueryable` are converted to a collection by calling a method such as `ToListAsync`. Therefore, the `IQueryable` code results in a single query that's not executed until the following statement:
+When an `IQueryable` is created or modified, no query is sent to the database. The query isn't executed until the `IQueryable` object is converted into a collection. `IQueryable` are converted to a collection by calling a method such as `ToListAsync`. Therefore, the `IQueryable` code results in a single query that's not executed until the following statement:
[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index1.cshtml.cs?name=snippet_SortOnlyRtn)]
@@ -95,7 +96,7 @@ To add filtering to the Students Index page:
Replace the code in *Students/Index.cshtml.cs* with the following code to add filtering:
-[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs?name=snippet_All&highlight=28,33,37-41)]
+[!code-csharp[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml.cs?name=snippet_All&highlight=17,22,26-30)]
The preceding code:
@@ -104,7 +105,7 @@ The preceding code:
### IQueryable vs. IEnumerable
-The code calls the `Where` method on an `IQueryable` object, and the filter is processed on the server. In some scenarios, the app might be calling the `Where` method as an extension method on an in-memory collection. For example, suppose `_context.Students` changes from EF Core `DbSet` to a repository method that returns an `IEnumerable` collection. The result would normally be the same but in some cases may be different.
+The code calls the method on an `IQueryable` object, and the filter is processed on the server. In some scenarios, the app might be calling the `Where` method as an extension method on an in-memory collection. For example, suppose `_context.Students` changes from EF Core `DbSet` to a repository method that returns an `IEnumerable` collection. The result would normally be the same but in some cases may be different.
For example, the .NET Framework implementation of `Contains` performs a case-sensitive comparison by default. In SQL Server, `Contains` case-sensitivity is determined by the collation setting of the SQL Server instance. SQL Server defaults to case-insensitive. SQLite defaults to case-sensitive. `ToUpper` could be called to make the test explicitly case-insensitive:
@@ -124,7 +125,7 @@ For more information, see [How to use case-insensitive query with Sqlite provide
### Update the Razor page
-Replace the code in *Pages/Students/Index.cshtml* to create a **Search** button and assorted chrome.
+Replace the code in *Pages/Students/Index.cshtml* to add a **Search** button.
[!code-cshtml[Main](intro/samples/cu30snapshots/3-sorting/Pages/Students/Index2.cshtml?highlight=14-23)]
@@ -138,8 +139,8 @@ Test the app:
Notice that the URL contains the search string. For example:
-```
-https://localhost:/Students?SearchString=an
+```browser-address-bar
+https://localhost:5001/Students?SearchString=an
```
If the page is bookmarked, the bookmark contains the URL to the page and the `SearchString` query string. The `method="get"` in the `form` tag is what caused the query string to be generated.
@@ -166,15 +167,16 @@ The `CreateAsync` method is used to create the `PaginatedList`. A constructor
Replace the code in *Students/Index.cshtml.cs* to add paging.
-[!code-csharp[Main](intro/samples/cu30/Pages/Students/Index.cshtml.cs?name=snippet_All&highlight=26,28-29,31,34-41,68-70)]
+[!code-csharp[Main](intro/samples/cu30/Pages/Students/Index.cshtml.cs?name=snippet_All&highlight=15-20,23-30,57-59)]
The preceding code:
* Changes the type of the `Students` property from `IList` to `PaginatedList`.
* Adds the page index, the current `sortOrder`, and the `currentFilter` to the `OnGetAsync` method signature.
-* Saves the sort order in the CurrentSort property.
+* Saves the sort order in the `CurrentSort` property.
* Resets page index to 1 when there's a new search string.
* Uses the `PaginatedList` class to get Student entities.
+* Sets `pageSize` to 3. A real app would use [Configuration](xref:fundamentals/configuration) to set the page size value.
All the parameters that `OnGetAsync` receives are null when:
@@ -197,7 +199,7 @@ If the search string is changed while paging, the page is reset to 1. The page h
The `PaginatedList.CreateAsync` method converts the student query to a single page of students in a collection type that supports paging. That single page of students is passed to the Razor Page.
- The two question marks after `pageIndex` in the `PaginatedList.CreateAsync` call represent the [null-coalescing operator](/dotnet/csharp/language-reference/operators/null-conditional-operator). The null-coalescing operator defines a default value for a nullable type. The expression `(pageIndex ?? 1)` means return the value of `pageIndex` if it has a value. If `pageIndex` doesn't have a value, return 1.
+ The two question marks after `pageIndex` in the `PaginatedList.CreateAsync` call represent the [null-coalescing operator](/dotnet/csharp/language-reference/operators/null-conditional-operator). The null-coalescing operator defines a default value for a nullable type. The expression `pageIndex ?? 1` returns the value of `pageIndex` if it has a value, otherwise, it returns 1.
### Add paging links to the Razor Page
@@ -243,7 +245,7 @@ Create a *Pages/About.cshtml* file with the following code:
### Create the page model
-Create a *Pages/About.cshtml.cs* file with the following code:
+Update the *Pages/About.cshtml.cs* file with the following code:
[!code-csharp[Main](intro/samples/cu30/Pages/About.cshtml.cs)]