ASP.NET Core Razor Pages 教程五 使用数据

使用数据

第一次使用数据时,你将重点关注于使用 BakeryContext 检索要在主页和订购页上显示的数据, 这些数据还没有添加到应用程序中。 提醒一下,主页应该类似于此处的 ASP.NET Web Pages 版本:
Home

所有产品的描述、图片和价格一起显示,随机选择其中一个产品作为特色产品出现在页面的顶部。

管理数据的显示需要少量的准备工作。首先,将以下代码添加到位于 wwwroot/css 中的现有 site.css 文件中:

body{
  color: #696969;
}
a:link {
  color: #3b3420;
  text-decoration: none;
}

a:visited {
  color: #3b3420;
  text-decoration: none;
}

a:hover {
  color: #a52f09;
  text-decoration: none;
}

a:active {
  color: #a52f09;
}

a.order-button, a.order-button:hover{
  color: #fdfcf7;
}

.productInfo, .action{
  max-width: 200px;
}

p{
  font-size: 0.8rem;
}

#orderProcess {
  list-style: none;
  padding: 0;
  clear: both;
}

#orderProcess li {
  color: #696969;
  display: inline;
  font-size: 1.2em;
  margin-right: 15px;
  padding: 3px 0px 0px 5px;
}

.step-number {
    background-color: #edece8;
    border: 1px solid #e6e4d9;
    font-size: 1.5em;
    margin-right: 5px;
    padding: 3px 10px;
}

.current .step-number {
    background-color: #a52f09;
    border-color: #712107;
    color: #fefefe;
} 

.orderPageList{
    padding-inline-start: 20px;
}

.actions .order-button{
  margin-left:20px;
}

PageModel

现在, 打开 Pages/Index.cshtml.cs 文件, 并替换为以下的内容:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bakery.Data;
using Bakery.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace Bakery.Pages
{
    public class IndexModel : PageModel
    {
        private readonly BakeryContext db;  

        public IndexModel(BakeryContext db) => this.db = db;

        public List<Product> Products { get; set; } = new List<Product>();  
        public Product FeaturedProduct { get; set; }  

        public async Task OnGetAsync()
        {
            Products = await db.Products.ToListAsync();
            FeaturedProduct = Products.ElementAt(new Random().Next(Products.Count));
        }
    }
}

这是 PageModel 文件。 PageModel 充当页面控制器和视图模型的组合。 作为控制器, 它的角色是处理来自请求的信息, 然后为视图准备一个模型(视图模型)。 页面模型(PageModel)和内容页面(视图)存在一对一的映射, 因此页面模型(PageModel)本身就是视图模型(viewmodel)。

来自请求的信息在处理程序方法(handler methods)中处理。这个 PageModel 有一个处理方法 - OnGetAsync,它是由使用 GET 谓词发出的 HTTP 请求按照约定执行的。 该 PageModel 有一个名为 dbBakeryContext 类型私有字段。它还有一个接受 BakeryContext 对象作为参数的构造方法。参数值由依赖注入系统提供。 这种模式称为构造注入。 该参数被分配给构造函数中的私有字段(使用表达式主体)。

该 PageModel 类有两个公共属性 - 一个产品列表和一个表示显示在页面顶部的特色产品的单个产品。 该列表由 OnGetAsync 方法中的以下代码填充:

Products = await db.Products.ToListAsync();

OnGetAsync 方法中的下一行代码将其中一个产品随机分配给 FeaturedProduct 属性:

FeaturedProduct = Products.ElementAt(new Random().Next(Products.Count));

内容页面

现在是生成UI的时候了。 将 Index 内容页面(Pages/Index.cshtml)的内容替换为以下的代码:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<h1>Welcome to Fourth Coffee!</h1>

<div id="featuredProduct" class="row"> 
    <div class="col-sm-8">
        <img alt="Featured Product" src="~/Images/Products/@Model.FeaturedProduct.ImageName" class="img-fluid rounded"/>
    </div>
    <div id="featuredProductInfo" class="col-sm-4">
        <div id="productInfo">
            <h2>@Model.FeaturedProduct.Name</h2>
            <p class="price">$@string.Format("{0:f}", Model.FeaturedProduct.Price)</p>
            <p class="description">@Model.FeaturedProduct.Description</p>
        </div>
        <div id="callToAction">
            <a class="btn btn-danger order-button" asp-page="/order" asp-route-id="@Model.FeaturedProduct.Id" title="Order @Model.FeaturedProduct.Name">Order Now</a>
        </div>
    </div>
</div>

<div id="productsWrapper" class="row">
@foreach (var product in Model.Products)
{
    <div class="col-sm-3">
        <a asp-page="/order" asp-route-id="@product.Id" title="Order @product.Name">
            <div class="productInfo">
                <h3>@product.Name</h3>
                <img class="product-image img-fluid img-thumbnail" src="~/Images/Products/Thumbnails/@product.ImageName" alt="Image of @product.Name" />
                <p class="description">@product.Description</p>
            </div>
        </a>

        <div class="action">
            <p class="price float-left">$@string.Format("{0:f}", product.Price)</p>
            <a class="btn btn-sm btn-danger order-button float-right" asp-page="/order" asp-route-id="@product.Id" title="Order @product.Name">Order Now</a>
        </div>
    </div>
}
</div>

页面顶部的 @model 指令指定页面模型(IndexModel)的类型。 您可以通过内容页面的 Model 属性使用 PageModel

HTML 的顶部显示了特色产品。 底部部分循环遍历所有产品并显示它们的缩略图。 每个产品都包含一个超链接,样式类似于按钮(使用 Bootstrap 样式)。 虽然它还没有实质的链接目标, 但是超链接是由一个锚标记助手(anchor tag helper)生成的, 其中包括一个 asp-route 属性。 此属性用于将数据作为路由值传递到目标页 。添加 Order 页面之后就可以看到它是如何工作的了,当然了,那个是下一步要做。

同时,通过在终端上执行 dotnet run 来测试应用程序,然后浏览到 https://localhost:5001 。主页应该是这样的:
HomePage

移动用户
原始的 Web Pages Bakery 模板使用设备检测或浏览器嗅探来满足移动用户的需求。如果检测到用户使用移动设备(主要是从user-agent报头中找到的值推断出来的),则站点将切换到使用不同的布局文件(sitelayout.mobile.cshtml)。这种方法有两个问题:首先,您需要使您的设备检测库保持最新,否则它的失败可能比成功更多;其次,您需要维护站点布局文件和样式表的多个版本。
如今,处理不同设备分辨率问题的解决方案是使用响应式设计(Responsive Design), 这种技术可以检测可用的屏幕大小,并相应地对内容进行回流。这个功能内置在Bootstrap中,您可以通过调整当前打开的浏览器的大小来了解它是如何工作的。当浏览器窗口宽度降至576px以下时,显示会发生巨大变化:
mobile

添加 Order 页面

在终端中执行以下命令添加一个新页面:

dotnet new page --name Order --namespace Bakery.Pages --output Pages

打开刚刚新创建的 Order.cshtml.cs 文件并替换其内容为以下代码:

using System;
using System.Threading.Tasks;
using Bakery.Data;
using Bakery.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Bakery.Pages
{
    public class OrderModel : PageModel
    {
        private BakeryContext db;

        public OrderModel(BakeryContext db) => this.db = db;

        [BindProperty(SupportsGet =true)]
        public int Id { get; set; }

        public Product Product { get; set;}

        public async Task OnGetAsync() =>  Product = await db.Products.FindAsync(Id);
    }
}

同样,BakeryContext 被注入到 PageModel 构造函数中。 公共属性 Product 在 OnGetAsync 方法中实例化。 FindAsync 方法接受一个值, 该值表示要返回的实体的主键。 在这种情况下, 传递给 FindAsync 方法的参数是另一个公共属性 - Id。 但是它获取的值从哪里来呢?

Id 属性(property,类的属性)使用 BindProperty属性(attribute,注解用的属性)进行修饰。该属性(attribute)确保类的属性(property)包含在模型绑定过程中,这将导致作为HTTP请求的一部分传递的值映射到 PageModel 属性和处理程序方法参数。 默认情况下, 模型绑定只对 POST 请求中传递的值有效。 通过单击主页上的链接可以到达 Order 页面, 这将导致 GET 请求。您必须添加 SupportsGet = true 来选择对 GET 请求进行模型绑定。

如果您还记得的话,链接到 Order 页面的主页上的锚标记助手包括一个 asp-route-id 属性, 它表示一个名为 id 的路由值。 路由值作为URL的一部分传递。 如果接收页面定义了匹配的路由参数, 该值将作为 URL 的一部分传递, 例如 order/3。 否则, 它将作为查询字符串值传递:order?id=3。 无论哪种方式, 传入的值都将绑定到 Id 属性。

接下来,修改 Order.cshtml 的内容如以下所示代码:

@page "{id:int}"
@model Bakery.Pages.OrderModel
@{
    ViewData["Title"] = "Place Your Order";
}
<ol id="orderProcess">
    <li><span class="step-number">1</span>Choose Item</li>
    <li class="current"><span class="step-number">2</span>Details &amp; Submit</li>
    <li><span class="step-number">3</span>Receipt</li>
</ol>
<h1>Place Your Order: @Model.Product.Name</h1>

代码第一行包含 @page 指令, 这是该页面成为 Razor Page 的原因,它还包含以下内容: "{id:int}"。 这是一个路由模板。 这是你在页面定义路由参数的位置。 这个模板定义了一个名为 id 的参数(这将导致主页上的锚标记助手以 id 值作为段生成 url)。 你还添加了一个约束, 在本例中, 你已经指定了 id 的值必须是整数类型(:int)。 基本上,这意味着除非提供 id 路由参数的值,否则无法到达 Order 页面。

现在如果你运行应用程序并点击主页上的的某一个按钮, Order页面将显示所选择产品的名称:
product

摘要

您已成功使用 BakeryContext 连接到数据库并检索已分配 给PageModel 属性的数据。 它们通过内容页面中的 Model 属性公开,您可以在其中循环显示产品集合以显示它们。 您还在本节中看到了如何在URL中传递数据并利用 BindProperty 属性将路由值映射到 PageModel 中的公共属性。 最后,您已经了解了如何使用该值来查询特定项目,以便您可以显示它的详细信息。

在下一部分中,您将允许用户通过表单提供订单的联系方式和发货详情。

posted on 2019-02-23 15:39  路盟  阅读(1316)  评论(0编辑  收藏  举报

导航