Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Продолжим работу с проектом, который был создан в прошлой теме, где у нас были модели User и Company:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Company { get; set; } public int CompanyId { get; set; } public Company Company { get; set; } } public class Company { public int Id { get; set; } public string Name { get; set; } public List<User> Users { get; set; } public Company() { Users = new List<User>(); } }
Также был класс UsersContext для взаимодействия с бд:
using Microsoft.EntityFrameworkCore; namespace SortApp.Models { public class UsersContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<Company> Companies { get; set; } public UsersContext(DbContextOptions<UsersContext> options) : base(options) { Database.EnsureCreated(); } } }
И было перечисление, которое описывает все критерии сортировки:
public enum SortState { NameAsc, NameDesc, AgeAsc, AgeDesc, CompanyAsc, CompanyDesc }
Теперь добавим специальный tag-хелпер для создания ссылок, по нажатию на которые будет производиться сортировка. Это позволит управлять созданием заголовков, настраивать их. Для этого добавим в проект новую папку TagHelpers. А в эту папку поместим новый класс SortHeaderTagHelper:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using SortApp.Models; namespace SortApp.TagHelpers { public class SortHeaderTagHelper : TagHelper { public SortState Property { get; set; } // значение текущего свойства, для которого создается тег public SortState Current { get; set; } // значение активного свойства, выбранного для сортировки public string Action { get; set; } // действие контроллера, на которое создается ссылка public bool Up { get; set; } // сортировка по возрастанию или убыванию private IUrlHelperFactory urlHelperFactory; public SortHeaderTagHelper(IUrlHelperFactory helperFactory) { urlHelperFactory = helperFactory; } [ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext); output.TagName = "a"; string url = urlHelper.Action(Action, new { sortOrder = Property }); output.Attributes.SetAttribute("href", url); // если текущее свойство имеет значение CurrentSort if (Current == Property) { TagBuilder tag = new TagBuilder("i"); tag.AddCssClass("glyphicon"); if (Up == true) // если сортировка по возрастанию tag.AddCssClass("glyphicon-chevron-up"); else // если сортировка по убыванию tag.AddCssClass("glyphicon-chevron-down"); output.PreContent.AppendHtml(tag); } } } }
Данные в tag-хелпер будут передаваться извне через набор свойств:
public SortState Property { get; set; } // значение текущего свойства, для которого создается тег public SortState Current { get; set; } // значение активного свойства, выбранного для сортировки public string Action { get; set; } // действие контроллера, на которое создается ссылка public bool Up { get; set; } // сортировка по возрастанию или убыванию
В идеале все эти свойства можно выделить в отдельную модель, но я не буду этого делать, чтобы не множить чрезмерно классы.
Для создания адреса ссылки по методу контроллера потребуется объект IUrlHelperFactory. И мы можем получить его в конструкторе, так как он встраивается по умолчанию через встроенный в ASP.NET Core механизм dependency injection.
Через тот же механизм внедрения зависимостей мы можем через атрибут получить контекст представления ViewContext, в котором будет вызываться хелпер:
[ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; }
С помощью этого объекта мы сможем получить объект IUrlHelper, который необходим для создания ссылки.
Далее в методе Process идет создание ссылки. Для ее стилизации используются классы, которые будут определены далее в представлении и которые для визуализации будут использовать шрифты библиотеки font-awesome.
Теперь нам надо передать данные для этого хелпера. Для этого определим в папке Models новый класс SortViewModel:
namespace SortApp.Models { public class SortViewModel { public SortState NameSort { get; set; } // значение для сортировки по имени public SortState AgeSort { get; set; } // значение для сортировки по возрасту public SortState CompanySort { get; set; } // значение для сортировки по компании public SortState Current { get; set; } // значение свойства, выбранного для сортировки public bool Up { get; set; } // Сортировка по возрастанию или убыванию public SortViewModel(SortState sortOrder) { // значения по умолчанию NameSort = SortState.NameAsc; AgeSort = SortState.AgeAsc; CompanySort = SortState.CompanyAsc; Up = true; if (sortOrder == SortState.AgeDesc || sortOrder == SortState.NameDesc || sortOrder == SortState.CompanyDesc) { Up = false; } switch (sortOrder) { case SortState.NameDesc: Current = NameSort = SortState.NameAsc; break; case SortState.AgeAsc: Current = AgeSort = SortState.AgeDesc; break; case SortState.AgeDesc: Current = AgeSort = SortState.AgeAsc; break; case SortState.CompanyAsc: Current = CompanySort = SortState.CompanyDesc; break; case SortState.CompanyDesc: Current = CompanySort = SortState.CompanyAsc; break; default: Current = NameSort = SortState.NameDesc; break; } } } }
Здесь важно понимать смысл свойства Current
. Оно нам нужно лишь для того, чтобы в выше определенном tag-хелпере определить, что данное свойство,
для которого применяется хелпер, используется в текущий момент для сортировки. Поэтому свойство Current указывает на значение текущего выбраного свойства, по которому проводится сортировка. То есть свойство Current будет равно одну из свойств NameSort, AgeSort или CompanySort
И далее добавим в папку Models новый класс IndexViewModel, который будет представлять модель для представления Index.cshtml:
using System.Collections.Generic; namespace SortApp.Models { public class IndexViewModel { public IEnumerable<User> Users { get; set; } public SortViewModel SortViewModel { get; set; } } }
Теперь изменим код контроллера HomeController:
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using SortApp.Models; using System.Linq; namespace SortApp.Controllers { public class HomeController : Controller { UsersContext db; public HomeController(UsersContext context) { this.db = context; // добавим начальные данные для тестирования if(db.Companies.Count() == 0) { Company oracle = new Company { Name = "Oracle" }; Company google = new Company { Name = "Google" }; Company microsoft = new Company { Name = "Microsoft" }; Company apple = new Company { Name = "Apple" }; User user1 = new User { Name = "Олег Васильев", Company = oracle, Age = 26 }; User user2 = new User { Name = "Александр Овсов", Company = oracle, Age = 24 }; User user3 = new User { Name = "Алексей Петров", Company = microsoft, Age = 25 }; User user4 = new User { Name = "Иван Иванов", Company = microsoft, Age = 26 }; User user5 = new User { Name = "Петр Андреев", Company = microsoft, Age = 23 }; User user6 = new User { Name = "Василий Иванов", Company = google, Age = 23 }; User user7 = new User { Name = "Олег Кузнецов", Company = google, Age = 25 }; User user8 = new User { Name = "Андрей Петров", Company = apple, Age = 24 }; db.Companies.AddRange(oracle, microsoft, google, apple); db.Users.AddRange(user1, user2, user3, user4, user5, user6, user7, user8); db.SaveChanges(); } } public async Task<IActionResult> Index(SortState sortOrder = SortState.NameAsc) { IQueryable<User> users = db.Users.Include(x=>x.Company); users = sortOrder switch { SortState.NameDesc => users.OrderByDescending(s => s.Name), SortState.AgeAsc => users.OrderBy(s => s.Age), SortState.AgeDesc => users.OrderByDescending(s => s.Age), SortState.CompanyAsc => users.OrderBy(s => s.Company.Name), SortState.CompanyDesc => users.OrderByDescending(s => s.Company.Name), _ => users.OrderBy(s => s.Name), }; IndexViewModel viewModel = new IndexViewModel { Users = await users.AsNoTracking().ToListAsync(), SortViewModel = new SortViewModel(sortOrder) }; return View(viewModel); } } }
И в конце изменим код представления Index.cshtml:
@using SortApp.Models @model IndexViewModel <!--импортируем tag-хелперы проекта--> @addTagHelper *, SortApp @{ ViewData["Title"] = "Список пользователей"; } <style> @@font-face{font-family:'FontAwesome';src:url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/fonts/fontawesome-webfont.woff2') format('woff2'), url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/fonts/fontawesome-webfont.woff') format('woff'), url('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/fonts/fontawesome-webfont.ttf') format('truetype');font-weight:normal;font-style:normal} .glyphicon { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale } .glyphicon-chevron-down:before { content: "\f078"; } .glyphicon-chevron-up:before { content: "\f077"; } </style> <h1>Список пользователей</h1> <table class="table"> <tr> <th> <sort-header action="Index" up="@Model.SortViewModel.Up" current="@Model.SortViewModel.Current" property="@Model.SortViewModel.NameSort"> Имя </sort-header> </th> <th> <sort-header action="Index" up="@Model.SortViewModel.Up" current="@Model.SortViewModel.Current" property="@Model.SortViewModel.AgeSort"> Возраст </sort-header> </th> <th> <sort-header action="Index" up="@Model.SortViewModel.Up" current="@Model.SortViewModel.Current" property="@Model.SortViewModel.CompanySort"> Компания </sort-header> </th> </tr> @foreach (User u in Model.Users) { <tr><td>@u.Name</td><td>@u.Age</td><td>@u.Company.Name</td></tr> } </table>
Поскольку создаваемый тег-хелпер использует классы glyphicon, glyphicon-chevron-down и glyphicon-chevron-up, которые визуализируются с помощью библиотеки font-awesome. В данном случае подключение необходимых шрифтов font-awesome и определение используемых их классов для краткости производися в представлении, но в реальном приложении, конечно, все это можно вынести в отдельный css-файл.
Поскольку класс tag-хелпера в своем названии имеет несколько слов, которые начинаются с большой буквы - SortHeaderTagHelper,
то в имени соотвествующего тега все части названия будут разделяться дефисом: <sort-header>
(суффикс TagHelper при этом отбрасывается).
Через атрибуты тега sort-header
мы можем передать значения для соотвествующих одноименных свойств класса SortHeaderTagHelper.
В итоге получился следующий проект:
Запустим проект и отсортируем по разным критериям: