目录
介绍
使用代码
配置购物车页面
创建购物车页面
创建添加到购物车按钮
启用会话
使用购物车功能
兴趣点
介绍在本文中,我们将创建任何曾经在线购物的人都会熟悉的购物车体验。购物车体验可能如下所示:
- 添加到购物车按钮将显示在目录中的每本书旁边。单击此按钮将显示客户迄今为止选择的书籍的摘要,包括总成本。
- 此时,用户可以单击Continue Shopping 按钮返回图书目录或单击Checkout Now按钮完成订单并完成购物会话。
Checkout Now按钮和其他一些高级体验将在下一篇文章中介绍。顺便说一句,到目前为止,我们已经完成了构建BooksStore应用程序的3个部分:
- 第1部分
- 第2部分
- 第3部分
在本节中,我们将使用Razor Pages来实现购物车。为此,第一步,我们将配置Startup类以在BooksStore 应用程序中启用Razor Pages。
using System;
using BooksStore.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BooksStore
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext(opts => {
opts.UseSqlServer(
Configuration["ConnectionStrings:SportsStoreConnection"]);
});
services.AddScoped();
services.AddRazorPages();
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
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();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("genpage",
"{genre}/{bookPage:int}",
new { Controller = "Home", action = "Index" });
endpoints.MapControllerRoute("page", "{bookPage:int}",
new { Controller = "Home", action = "Index", bookPage = 1 });
endpoints.MapControllerRoute("genre", "{genre}",
new { Controller = "Home", action = "Index", bookPage = 1 });
endpoints.MapControllerRoute("pagination", "Books/Page{bookPage}",
new { Controller = "Home", action = "Index", bookPage = 1 });
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
SeedData.EnsurePopulated(app);
}
}
}
该AddRazorPages方法设置Razor Pages使用的服务,并且该MapRazorPages方法注册Razor Pages为URL路由系统可以用来处理请求的端点。
下一步,我们将一个名为Pages的文件夹添加到BooksStore 项目中,这是Razor Pages的常规位置。我们将使用以下代码将名为_ViewImports.cshtml的文件添加到Pages文件夹:
@namespace BooksStore.Pages
@using Microsoft.AspNetCore.Mvc.RazorPages
@using BooksStore.Models
@using BooksStore.MyTagHelper
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
这些表达式设置了Razor Pages将属于的命名空间,并允许在Razor Pages不需要指定它们的命名空间的情况下使用这些BooksStore类。
接下来,我们将在Pages文件夹中添加一个名为_ViewStart.cshtml的文件,代码如下:
@{
Layout = "_MyCartLayout";
}
Razor Pages有自己的配置文件,这个指定Razor Pages在BooksStore 项目中默认会使用一个名为_MyCartLayout的布局文件。
最后,为了提供Razor Pages将使用的布局,将名为_MyCartLayout.cshtml的文件添加到Pages文件夹中,并带有以下标记:
Books Store
BOOKS STORE
@RenderBody()
在Visual Studio中,我们将添加Razor Page模板项并将项名称设置为MyCart.cshtml 到Pages文件夹。这将创建一个MyCart.cshtml文件和一个MyCart.cshtml.cs类文件。将MyCart.cshtml文件的内容替换为以下标记:
@page
Hello, I am the Cart Page
使用url https://localhost:yourport/mycart运行应用程序(我的url: https://localhost:44333/mycart):
在实现购物车功能之前,我们需要创建将书籍添加到购物车的按钮。为此,我们将在MyTagHelper文件夹中添加一个名为UrlExtensions.cs的类文件,并使用以下代码定义扩展方法:
using Microsoft.AspNetCore.Http;
namespace BooksStore.MyTagHelper
{
public static class UrlExtensions
{
public static string PathAndQuery(this HttpRequest request) =>
request.QueryString.HasValue
? $"{request.Path}{request.QueryString}"
: request.Path.ToString();
}
}
PathAndQuery扩展方法对ASP.NET Core用来描述HTTP请求的HttpRequest类进行操作。扩展方法生成一个URL,在购物车更新后浏览器将返回到该URL,考虑到查询字符串(如果有)。
接下来,我们还将包含扩展方法的命名空间添加到视图导入文件中,以便我们可以通过将BooksStore.MyTagHelper命名空间添加到BooksStore/Views文件夹中的_ViewImports.cshtml文件中来在局部视图中使用它:
@using BooksStore
@using BooksStore.Models
@using BooksStore.MyTagHelper
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BooksStore
最后,我们将更新描述每本书的局部视图,使其包含添加到购物车按钮,方法是将按钮添加到BooksStore/Views/Shared文件夹中的BookTemplate.cshtml文件视图,并使用以下标记:
@model Book
@Model.Title
@Model.Price.ToString("c")
@Model.Genre
@Model.Description
Add To Cart
在前面的标记中:
- 我们添加了一个包含隐藏input元素的form元素,该元素指定BookID视图模型中的值以及在购物车更新后浏览器应返回的URL。
- form元素和其中一个input元素是使用内置标签助手配置的,这是生成包含模型值并以控制器或Razor Pages为目标的表单的有用方法
- 另一个input元素使用我们创建的扩展方法来设置返回URL。我们还添加了一个将表单提交到应用程序的button元素。
我们将使用会话状态存储用户购物车的详细信息,会话状态是与用户提出的一系列请求相关联的数据。有不同的方法来存储会话状态,但在这个项目中,我们将把它存储在内存中。这样做的好处是简单,但是这意味着当应用程序停止或重新启动时会话数据会丢失。在BooksStore项目的Startup.cs文件中 ,我们将添加服务和中间件以启用会话:
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddRazorPages();
services.AddDistributedMemoryCache();
services.AddSession();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseStaticFiles();
app.UseSession();
app.UseRouting();
...
}
AddDistributedMemoryCache方法调用设置内存数据存储。该AddSession方法注册用于访问会话数据的服务,并且该UseSession方法允许会话系统在请求从客户端到达时自动将请求与会话相关联。
使用购物车功能现在要使用购物车功能,我们将在BooksStore项目的Models文件夹中添加一个名为MyCart.cs的类文件,并使用它来定义具有以下代码的类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BooksStore.Models
{
public class MyCart
{
public List Lines { get; set; } = new List();
public void AddItem(Book book, int quantity)
{
CartLine line = Lines
.Where(b => b.Book.BookID == book.BookID)
.FirstOrDefault();
if (line == null)
{
Lines.Add(new CartLine
{
Book = book,
Quantity = quantity
});
}
else
{
line.Quantity += quantity;
}
}
public void RemoveLine(Book book) =>
Lines.RemoveAll(l => l.Book.BookID == book.BookID);
public decimal ComputeTotalValue() =>
Lines.Sum(e => e.Book.Price * e.Quantity);
public void Clear() => Lines.Clear();
}
public class CartLine
{
public int CartLineID { get; set; }
public Book Book { get; set; }
public int Quantity { get; set; }
}
}
该类MyCart使用在同一文件中定义的CartLine类来表示客户选择的书籍和用户想要购买的数量。我们定义了将商品添加到购物车、从购物车中移除之前添加的商品、计算购物车中商品的总成本以及通过移除所有商品来重置购物车的方法。
ASP.NET Core中的会话状态功能仅存储int、string和byte[]值。由于我们要存储一个MyCart对象,我们需要为ISession接口定义扩展方法,它提供对会话状态数据的访问,以将MyCart对象序列化JSON并转换回来。我们将在MyTagHelper文件夹中添加一个名为SessionExtensions.cs的类文件,并使用以下代码定义扩展方法:
using Microsoft.AspNetCore.Http;
using System.Text.Json;
namespace BooksStore.MyTagHelper
{
public static class SessionExtensions
{
public static void SetJson(this ISession session, string key, object value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T GetJson(this ISession session, string key)
{
var sessionData = session.GetString(key);
return sessionData == null
? default(T) : JsonSerializer.Deserialize(sessionData);
}
}
}
这些方法将对象序列化为JavaScript Object Notation格式,使存储和检索MyCart对象变得容易。
在由Visual Studio中的Razor Page模板项创建的Pages文件夹中名为MyCart.cshtml.cs的类文件中,使用以下代码定义该类:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using BooksStore.MyTagHelper;
using BooksStore.Models;
using System.Linq;
namespace BooksStore.Pages
{
public class MyCartModel : PageModel
{
private IBooksStoreRepository repository;
public MyCartModel(IBooksStoreRepository repo)
{
repository = repo;
}
public MyCart myCart { get; set; }
public string ReturnUrl { get; set; }
public void OnGet(string returnUrl)
{
ReturnUrl = returnUrl ?? "/";
myCart = HttpContext.Session.GetJson("mycart") ?? new MyCart();
}
public IActionResult OnPost(long bookId, string returnUrl)
{
Book book = repository.Books
.FirstOrDefault(b => b.BookID == bookId);
myCart = HttpContext.Session.GetJson("mycart") ?? new MyCart();
myCart.AddItem(book, 1);
HttpContext.Session.SetJson("mycart", myCart);
return RedirectToPage(new { returnUrl = returnUrl });
}
}
}
在前面的代码中:
- 名为MyCartModel的页面模型类定义了一个OnPost处理程序方法,调用该方法来处理HTTP POST请求。它通过从数据库中检索Book、从会话数据中检索用户的购物车并使用Book更新其内容。
- 该GET请求由OnGet处理程序方法处理,该方法设置ReturnUrl和myCart属性的值,然后呈现页面的Razor内容部分。
- 处理程序方法使用与BookTemplate.cshtml视图生成的HTML表单中的输入元素匹配的参数名称。
当用户单击“添加到购物车”按钮时, MyCart Razor页面将接收浏览器发送的HTTP POST请求。它将使用请求表单数据从数据库中获取Book对象并使用它来更新用户的购物车,该购物车将存储为会话数据以供将来的请求使用。使用以下标记更新BooksStore/Pages文件夹中MyCart.cshtml文件的内容:
@page
@model MyCartModel
Your cart
Quantity
Item
Price
Subtotal
@foreach (var line in Model.myCart.Lines)
{
@line.Quantity
@line.Book.Title
@line.Book.Price.ToString("c")
@((line.Quantity * line.Book.Price).ToString("c"))
}
Total:
@Model.myCart.ComputeTotalValue().ToString("c")
Continue shopping
运行应用程序:
单击添加到购物车按钮:
单击继续购物按钮将用户返回到他们来自的页面。
兴趣点在本文中,我们将使用“添加到购物车”和“继续购物”按钮创建购物车体验。Checkout Now按钮和其他一些高级体验将在下一篇文章中介绍。
https://www.codeproject.com/Articles/5329085/An-Introduction-to-ASP-NET-Core-MVC-through-an-E-4