通过示例介绍 ASP.NET Core MVC(第 2 部分)





5.00/5 (2投票s)
ASP.NET Core MVC 入门
引言
正如 第 1 部分中所述,Index.cshtml 视图在一个页面上显示数据库中的图书。在本文中,我将添加对分页的支持,以便视图在页面上显示较少数量的图书,并且用户可以从一页翻到另一页以查看整个目录。我还会使用 Bootstrap 来设置内容样式。
背景
在本文中,我们将熟悉用于构建 BooksStore
应用程序核心基础结构的一些技术,例如如何添加分页,如何使用 标签助手,什么是 部分视图及其用法,如何创建 HTML 属性名格式与 C# 属性名格式之间的映射,如何使用 Bootstrap 设置内容样式等等。
Using the Code
添加分页
我们将向 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 末尾添加查询字符串参数,如下所示:
我们需要在每组图书列表的底部渲染一些页面链接,以便客户可以跨页面导航。为此,我们将创建一个 标签助手,它会生成我们所需的链接的 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
元素,这些 a
元素对应于图书的页面。大多数 ASP.NET Core 组件(如控制器和视图)会自动发现,但标签助手必须注册。我们将向 Views 文件夹中的 _ViewImports.cshtml 文件添加一个语句,告知 ASP.NET Core 在 BooksStore
项目中查找标签助手类。
@using BooksStore
@using BooksStore.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BooksStore
我们还添加了一个 @using
表达式,以便我们可以在视图中引用视图模型类,而无需使用命名空间限定其名称。
我们必须为视图提供 PagingInfo
视图模型类的一个实例。为此,我们将向 BooksStore
项目的 Models/ViewModels 文件夹添加一个名为 BooksListViewModel.cs 的类文件,其中包含以下代码:
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>
由于页面链接仍然使用查询字符串将页面信息传递给服务器,如下所示:https:///?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: https://:44333/?bookPage=2
// new URL: https://: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 View,并添加如下所示的标记:
@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 来设置应用程序的外观样式。在下一篇文章中,我们将为应用程序提供按流派导航的功能。
历史
- 2022 年 3 月 12 日:初始版本