目录
介绍
如第1部分所述,Index.cshtml视图在单个页面上显示数据库中的书籍。在本文中,我将添加对分页的支持,以便视图在一个页面上显示较少数量的书籍,并且用户可以从一个页面移动到另一个页面以查看整个目录。我还将使用Bootstrap设置内容样式。
背景
在本文中,我们将熟悉一些用于构建BooksStore应用程序核心基础架构的技术,例如如何添加分页、如何使用标签助手、什么是局部视图以及如何使用它、如何创建HTML属性名格式与C#属性名格式之间的映射,如何用Bootstrap设置内容的样式等等。
使用代码
添加分页
我们将使用以下代码向Home控制器中的Index方法添加一个参数:
public class HomeController : Controller
{
private IBooksStoreRepository repository;
public int PageSize = 3;
public HomeController(IBooksStoreRepository repo)
{
repository = repo;
}
public IActionResult Index(int bookPage = 1)
=> View(repository.Books
.OrderBy(b => b.BookID)
.Skip((bookPage - 1) * PageSize)
.Take(PageSize));
}
前面的代码:
- 该PageSize字段指定我们想要每页三本书。
- 我们获取Book对象,按主键(BookID)对它们进行排序,跳过当前页面开始之前出现的书籍,并获取PageSize字段指定的书籍数量。
我们可以使用查询字符串浏览图书目录。运行应用程序,我们将看到页面上现在显示了三本书:
如果我们想查看另一个页面,我们可以将查询字符串参数附加到URL的末尾,如下所示:
http://localhost:44333/?bookPage=2
我们需要在每个图书列表的底部渲染一些页面链接,以便客户可以在页面之间导航。为此,我们将创建一个标签助手,它为我们需要的链接生成HTML标记。
为了支持标签助手,我们将创建一个视图模型类,它专门用于在控制器和视图之间传递数据,方法是在BooksStore项目中创建一个Models/ViewModels文件夹,向其中添加一个名为PagingInfo.cs的类文件,并使用以下代码定义类:
using System;
namespace BooksStore.Models.ViewModels
{
public class PagingInfo
{
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages =>
(int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
}
}
在前面的代码中,我们希望将有关可用页数、当前页和存储库中图书总数的信息传递给视图。
现在我们将在BooksStore项目中创建一个名为MyTagHelper的文件夹,并向其中添加一个名为MyPageLink.cs的类文件,其中包含以下代码:
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 BooksStore.Models.ViewModels;
namespace BooksStore.MyTagHelper
{
[HtmlTargetElement("div", Attributes = "page-model")]
public class MyPageLink : TagHelper
{
private IUrlHelperFactory urlHelperFactory;
public MyPageLink(IUrlHelperFactory helperFactory)
{
urlHelperFactory = helperFactory;
}
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
public PagingInfo PageModel { get; set; }
public string PageAction { get; set; }
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
TagBuilder result = new TagBuilder("div");
for (int i = 1; i <= PageModel.TotalPages; i++)
{
TagBuilder tag = new TagBuilder("a");
tag.Attributes["href"] = urlHelper.Action(PageAction,
new { bookPage = i });
tag.InnerHtml.Append(i.ToString());
result.InnerHtml.AppendHtml(tag);
}
output.Content.AppendHtml(result.InnerHtml);
}
}
}
此标签助手使用与书籍页面对应的a元素填充div元素。大多数ASP.NET Core组件,例如控制器和视图,都是自动发现的,但必须注册标签助手。我们将向Views文件夹中的_ViewImports.cshtml文件添加一条语句,告诉ASP.NET Core在BooksStore项目中查找标记帮助程序类。
@using BooksStore
@using BooksStore.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BooksStore
我们还添加了一个@using表达式,以便我们可以在视图中引用视图模型类,而无需使用命名空间限定它们的名称。
我们必须为视图提供PagingInfo视图模型类的实例。为此,我们将使用以下代码将名为BooksListViewModel.cs的类文件添加到BooksStore项目的Models/ViewModels文件夹中:
using System.Collections.Generic;
namespace BooksStore.Models.ViewModels
{
public class BooksListViewModel
{
public IEnumerable<Book> Books { get; set; }
public PagingInfo PagingInfo { get; set; }
}
}
我们可以在HomeController类中更新Index操作方法,使用BooksListViewModel类向视图提供要在页面上显示的图书的详细信息,并使用以下代码提供分页的详细信息:
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using BooksStore.Models;
using BooksStore.Models.ViewModels;
...
public IActionResult Index(int bookPage = 1)
=> View(new BooksListViewModel
{
Books = repository.Books
.OrderBy(p => p.BookID)
.Skip((bookPage - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = bookPage,
ItemsPerPage = PageSize,
TotalItems = repository.Books.Count()
}
});
我们创建了包含分页信息的视图模型,更新了控制器以便将这些信息传递给视图。现在我们将更改@model指令以匹配新的模型视图类型并添加标签助手将处理以创建页面链接的HTML元素,如以下标记:
@model BooksStore.Models.ViewModels.BooksListViewModel
@foreach (var p in Model.Books)
{
<div>
<h3>@p.Title</h3>
@p.Description
@p.Genre
<h4>@p.Price.ToString("c")</h4>
</div>
}
<div page-model="@Model.PagingInfo" page-action="Index"></div>
因为页面链接仍然使用查询字符串将页面信息传递给服务器,例如http://localhost/?bookPage=2,我们可以在Startup类中添加一个新路由来改进URL,使用以下代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
// for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
// old URL: http://localhost:44333/?bookPage=2
// new URL: https://localhost:44333/Books/2
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("pagination",
"Books/{bookPage}",
new { Controller = "Home", action = "Index" });
endpoints.MapDefaultControllerRoute();
});
SeedData.EnsurePopulated(app);
}
}
这是更改书籍分页的 URL 方案所需的唯一更改。运行应用程序并单击分页链接之一:
样式化内容
我们将使用Bootstrap来提供我们将应用于应用程序的CSS样式。在Visual Studio 2019中,我们可以在wwwroot/lib文件夹中找到Bootstrap:
Razor布局提供通用内容,因此不必在多个视图中重复。我们更改Views/Shared文件夹中的_Layout.cshtml文件,以在发送到浏览器的内容中包含Bootstrap CSS样式表,并定义一个通用标题,该标题将在整个BooksStore应用程序中使用,并带有以下标记:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>BooksStore</title>
<link href="~/lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div>
<div class="bg-dark text-white p-2">
<span class="navbar-brand ml-2">BOOKS STORE</span>
</div>
<div class="row m-1 p-1">
<div id="categories" class="col-3">
The BooksStore homepage helps you explore Earth's Biggest Bookstore
without ever leaving the comfort of your couch.
</div>
<div class="col-9">
@RenderBody()
</div>
</div>
</div>
</body>
</html>
接下来,我们将在Views/Shared文件夹中添加一个名为BookTemplate.cshtml的Razor视图,并添加如下所示的标记:
@model Book
<div class="card" style="width: 100%;">
<div class="card-body">
<h5 class="card-title">
@Model.Title
<span class="badge badge-pill badge-primary">
<small>@Model.Price.ToString("c")</small>
</span>
</h5>
<h6 class="card-subtitle mb-2 text-muted">@Model.Genre</h6>
<p class="card-text">@Model.Description</p>
</div>
</div>
我们创建了一个局部视图,它是一个可以嵌入到另一个视图中的内容片段,就像一个模板。现在我们需要更新Views/Home文件夹中的Index.cshtml文件,以便它使用局部视图:
@model BooksStore.Models.ViewModels.BooksListViewModel
@foreach (var p in Model.Books)
{
<partial name="BookTemplate" model="p" />
}
<div page-model="@Model.PagingInfo" page-action="Index" page-classes-enabled="true"
page-class="btn" page-class-normal="btn-outline-dark"
page-class-selected="btn-primary" class="btn-group pull-right m-1">
</div>
我们需要在div元素上定义自定义属性来指定我们需要的类,这些属性对应于我们添加到标签助手类的属性,然后用于设置生成的a元素的样式。为此,我们将使用以下代码对MyPageLink类进行一些更改:
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 BooksStore.Models.ViewModels;
namespace BooksStore.MyTagHelper
{
[HtmlTargetElement("div", Attributes = "page-model")]
public class MyPageLink : TagHelper
{
private IUrlHelperFactory urlHelperFactory;
public MyPageLink(IUrlHelperFactory helperFactory)
{
urlHelperFactory = helperFactory;
}
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
public PagingInfo PageModel { get; set; }
public string PageAction { get; set; }
public bool PageClassesEnabled { get; set; } = false;
public string PageClass { get; set; }
public string PageClassNormal { get; set; }
public string PageClassSelected { get; set; }
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
TagBuilder result = new TagBuilder("div");
for (int i = 1; i <= PageModel.TotalPages; i++)
{
TagBuilder tag = new TagBuilder("a");
tag.Attributes["href"] = urlHelper.Action(PageAction,new { bookPage = i });
if (PageClassesEnabled)
{
tag.AddCssClass(PageClass);
tag.AddCssClass(i == PageModel.CurrentPage
? PageClassSelected : PageClassNormal);
}
tag.InnerHtml.Append(i.ToString());
result.InnerHtml.AppendHtml(tag);
}
output.Content.AppendHtml(result.InnerHtml);
}
}
}
属性的值会自动用于设置标记助手属性值,其中考虑了HTML属性名称格式(page-class-normal)和C#属性名称格式(PageClassNormal)之间的映射。这允许标签助手根据HTML元素的属性做出不同的响应,从而创建了一种更灵活的方式来在ASP.NET Core应用程序中生成内容。
运行应用程序:
兴趣点
我们添加了对分页的支持,以便视图在一个页面上显示较少数量的书籍,并且用户可以从一个页面移动到另一个页面以查看整个目录。我们还使用Bootstrap来设计应用程序的外观。在下一篇文章中,我们将为应用程序提供按类型导航功能。
https://www.codeproject.com/Articles/5327314/An-Introduction-to-ASP-NET-Core-MVC-through-an-E-2