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

+ + + + + + + + @foreach (var item in Model.Students) + { + + + + + } +
+ Enrollment Date + + 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

+ +
+
+

+ Find by name: + + | + Back to full List +

+
+
+ -@foreach (var item in Model.Student) { - - - - - - -} + @foreach (var item in Model.Students) + { + + + + + + + }
- @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) +
- @Html.DisplayFor(modelItem => item.LastName) - - @Html.DisplayFor(modelItem => item.FirstMidName) - - @Html.DisplayFor(modelItem => item.EnrollmentDate) - - Edit | - Details | - Delete -
+ @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)]