65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2022 年 3 月 12 日

CPOL

5分钟阅读

viewsIcon

10313

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 末尾添加查询字符串参数,如下所示:

https://: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 元素,这些 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.cshtmlRazor 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 日:初始版本
© . All rights reserved.