四、商品目录,购物车和结帐

本章将介绍电子商务应用的主要部分及其相关 API 端点的编码。

我们已经在前一章中讨论了用户注册和身份验证,我们将继承这些知识来帮助我们在本章中构建的不同控制器中实现安全性。

为了有效的展示产品和搜索产品,我们还将设计ProductsController

之后,我们还将研究如何将产品放入购物车,讨论如何添加、更新和删除购物车中的商品。

最后,但并非最不重要的是,我们还将查看订单管理和处理。

在本章中,我们将涵盖以下主题:

  • 实现不同的控制器
  • 产品列表和产品搜索
  • 添加、更新和删除购物车项目
  • 对控制器施加安全性
  • 订单处理和运输信息

实现控制器

由于我们将了解应用的核心功能,我们需要设计它的控制器,以便我们有 REST 端点来执行来自客户机的任务。 诸如产品清单,产品搜索加入购物车,订单,【显示】处理出货可以用一个专用的控制器为每个函数。 这些控制器将负责在数据库上执行操作,因此我们需要为相关表建模类。 我们开始工作吧!**

**# 生成模型

下面这行代码可以在 Package Manager Console 中执行,为数据库中的所有表生成模型类:

Scaffold-DbContext "Server=.;Database=FlixOneStore;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Force

前面的命令将填充Models文件夹中每个表的类文件,如下面的截图所示:

If you have not already done so, please refer to the database script in https://github.com/PacktPublishing/Building-RESTful-Web-services-with-DOTNET-Core to generate the database table for your application.

生成控制器

要为模型生成控制器,右键单击Controllers文件夹|添加|控制器| API 控制器与动作,使用实体框架。

首先,让我们从ProductsdetailsController开始,因为我们想首先向客户展示产品列表。

通过 scaffolding 生成的Productsdetail.cs模型类应该如下所示:

public partial class Productsdetail
{
  public Guid Id { get; set; }
  public Guid? Productid { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
  public string Url { get; set; }
  public int Views { get; set; }
  public Products Product { get; set; }
} 

前面的代码还可以用于生成带有GETPOSTPUTDELETE动作方法的控制器:(我们现在关注的是GetProductsdetail方法)。

// GET: api/Productsdetails
[HttpGet]
public IEnumerable<Productsdetail> GetProductsdetail()
{
  return _context.Productsdetail;
}

您可以使用 Postman 快速测试您的控制器是否正在工作,如下图所示:

这里,URL 为http://localhost:57571/api/Productsdetails,类型为GET。 我们可以在结果框中看到结果,它以 JSON 格式向我们显示了产品细节的数组。 注意,我们通过在请求的 header 选项卡中使用application/json值设置contentType报头来发送此请求。

产品清单

现在让我们设计使用该端点所需的 jQuery 代码,以便在 web 页面上显示这些记录并列出可购买的产品。 应该如下所示:

function LoadProducts() 
{
  // Load products' details.
  $.ajax({
    url: 'http://localhost:57571/api/Productsdetails',
    type: "GET",
    contentType: "application/json",
    dataType: "json",
    success: function (result) {
      $.each(result, function (index, value) {
        $('#tblProducts')
        .append('<tr><td>' +
        '<h3>' + value.name + '</h3>' +
        '<p>' + value.description + '</p>' +
        '<a target="_blank" href=' + value.url + '>Amazon Link</a>' +
        '<input type="button" style="float:right;" 
        class="btn btn-success" value="Add To Cart" />' +
        '</td></tr>');
      });
    }
  });
}

To get the code to call the API in different languages, you can click on the code link inside Postman and then select the desired language. We have already discussed this in previous chapters.

前面的方法调用端点http://localhost:57571/api/Productsdetails,并在用success方法接收记录后循环遍历记录。 在循环时,它构建一个 HTML 表行,将其附加到页面上已存在的表。

下面的截图是 jQuery 代码的反映,它显示了所有产品的细节:

注意,我们在顶部有一个搜索框,以及每个产品的“添加到购物车”按钮。 我们将在稍后讨论这些功能。

你注意到产品的主要参数,价格,没有显示吗? 这是因为价格没有出现在Productdetail表中。 那么,现在让我们来看看Product.cs模型类,如下所示:

public partial class Products
{
  public Products()
  {
    Cart = new HashSet<Cart>();
    CartAttributes = new HashSet<CartAttributes>();
    OrdersProducts = new HashSet<OrdersProducts>();
    ProductsAttributes = new HashSet<ProductsAttributes>();
    Productsdetail = new HashSet<Productsdetail>();
    Reviews = new HashSet<Reviews>();
  }
  public Guid Id { get; set; }
  public int Qty { get; set; }
  public string Model { get; set; }
  public string Image { get; set; }
  public decimal Price { get; set; }
  public DateTime Addedon { get; set; }
  public DateTime Modifiedon { get; set; }
  public decimal Weight { get; set; }
  public byte Status { get; set; }
  public Guid? ManufactureId { get; set; }
  public Guid? Taxclassid { get; set; }
  public ICollection<Cart> Cart { get; set; }
  public ICollection<CartAttributes> CartAttributes { get; set; }
  public ICollection<OrdersProducts> OrdersProducts { get; set; }
  public ICollection<ProductsAttributes> ProductsAttributes 
  { get; set; }
  public ICollection<Productsdetail> Productsdetail { get; set; }
  public ICollection<Reviews> Reviews { get; set; }
}

显然,Product类包含了我们需要的所有内容,包括NameDescriptionUrlViews等等,以Productdetail为参考点。 我们已经消费了ProductdetailsControllerGET动作来展示我们的产品,现在是时候使用ProductsController阅读我们所有的产品了。

ProductsControllerGET操作将返回Productdetail中所有的产品记录,如下:

// GET: api/Products
[HttpGet]
public IEnumerable<Products> GetProducts()
{
  return _context.Products.Include(x => x.Productsdetail).ToList();
}

前面代码中的粗体代码片段是Include子句,它用于包含Productdetail的结果。 现在,我们不再调用/api/Productsdetails,而是调用/api/Products

Calling this endpoint won't actually work, this is because of circular reference. If you observe both the Products and Productdetail models closely, you should see that both contain references to each other. This creates a problem when parsing to JSON. To avoid this, we need to write the following code inside Startup: services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });

现在让我们看看在调用该端点时收到的产品响应,如下面的代码片段所示。 注意,实际上你会得到一个数组,但为了简洁起见,我们只向你显示一条记录:

{
  "id": "98a95bb6-c573-450d-a470-0a637e126dd7",
  "qty": 30,
  "model": "A",
  "image": "NA",
  "price": 49.99,
  "addedon": "2018-05-13T12:09:39.873",
  "modifiedon": "2018-05-13T12:09:39.873",
  "weight": 0.9,
  "status": 1,
  "manufactureId": null,
  "taxclassid": null,
  "cart": [],
  "cartAttributes": [],
  "ordersProducts": [],
  "productsAttributes": [],
  "productsdetail": [
  {
    "id": "c96ac991-6581-4675-b00c-439df3961f03",
    "productid": "98a95bb6-c573-450d-a470-0a637e126dd7",
    "name": "Dependency Injection in .NET Core 2.0",
    "description": "Make use of constructors, parameters, 
    setters, and interface injection to write reusable and 
    loosely-coupled code",
    "url": "https://www.amazon.com/Dependency-Injection-NET-Core-
    loosely-coupled/dp/1787121305/ref=tmm_pap_swatch_0? 
    _encoding=UTF8&qid=1510939068&sr=8-3",
    "views": 5000
  }],
  "reviews": []
}

现在,我们需要修改客户端代码,以反映Productdetail现在在Product对象内的事实,如下所示:

function LoadProducts() 
{
  // Load products' details.
  $.ajax({
    url: 'http://localhost:57571/api/Products',
    type: "GET",
    contentType: "application/json",
    dataType: "json",
    success: function (result) {
      console.log(result);
      $.each(result, function (index, value) {
        $('#tblProducts')
        .append('<tr><td>' +
        '<h3>' + value.productsdetail[0].name + '</h3>' +
        '<span class="spanPrice">Price: $' + value.price + 
        '</span>' +
        '<p>' + value.productsdetail[0].description + '</p>' +
        '<a target="_blank" href=' + value.productsdetail[0].url +
        '>Amazon Link</a>' +
        '<input type="button" style="float:right;" class="btn btn-
        success" value="Add To Cart" />' +
        '</td></tr>');
      });
    }
  });
}

这很容易理解,不是吗? 在这里,您应该注意到我们对 URL 所做的更改,以及我们如何读取产品详细信息。 Productsdetail作为数组在Product对象中,因此它被写成value.productsdetail[0],其中value是产品对象。 我们还介绍了value.price

你现在应该看到下面的截图,它已经更新了:

产品搜索

现在是时候实现搜索功能了,允许客户在搜索框中放入任何字符串来查找产品。 我们需要向 UI 添加一个搜索按钮,单击该按钮将接收输入的字符串并相应地获取记录。

首先,action方法需要接受客户输入的搜索文本作为参数; 目前GetProducts()不接受任何参数。

更新后的GetProducts()应该如下所示:

// GET: api/Products
[HttpGet]
public IEnumerable<Products> GetProducts(string searchText)
{
  var products = _context.Products.Include(x => 
  x.Productsdetail).ToList();
  if (!string.IsNullOrEmpty(searchText))
  products = products.Where(p => p.Productsdetail
 .Any(pd => pd.Name.ToLower().Contains(searchText.ToLower())))
  .ToList();
  return products;
}

将考虑searchText参数,并通过位于Product对象内Productsdetail集合的Name字段中的图书标题过滤结果。 因此,Any用于检查searchText是否存在于Productsdetail对象中。

现在 API 已经准备好搜索了,让我们更新客户端代码,以发送如下参数:

function LoadProducts(searchText) 
{
  if (!searchText)
  searchText = "";
  // Load products' details.
  $.ajax({
    url: 'http://localhost:57571/api/Products?searchText=' + 
    searchText,
    type: "GET",
    // Other codes removed for brevity.

正如您在前面的代码片段中看到的,LoadProducts现在接受作为 URL 参数传递给 API 的searchText参数。 现在只需在调用时将参数值发送给该方法。

下面的代码演示了搜索函数,它获取文本并使用输入的值执行LoadProducts:

$('#btnSearch').click(function () 
{
  var searchText = $('#txtSearch').val().trim();
  if (searchText) 
  {
    $('#tblProducts').empty();
    LoadProducts(searchText);
  }
});

下面的屏幕截图显示了这个功能的作用:

添加到购物车

我们现在准备好下一个重要的主题:所有关于添加到购物车! 然而,在实现这个函数之前,有一些事情值得注意。 在我们的应用中,我们不允许未知的用户添加到购物车,因为我们将在数据库中存储与购物车相关的任何信息。

实现安全

这就是安全性的用武之地,即身份验证。 如第 3 章用户注册与管理中所述,基本身份验证可以在处理程序的帮助下应用,也可以在令牌的帮助下应用持有者身份验证。

首先,让我们使用与前面相同的步骤生成CartsController。 现在我们需要将[Authorize]属性直接应用到控制器,这样购物车中的所有操作都可以被验证。 我们的应用已经设置为处理无记名身份验证。

以下是CartsController的代码快照:

[Produces("application/json")]
[Route("api/Carts")]
[Authorize]
public class CartsController : Controller

由于[Authorize]属性,如果不提供访问令牌,该控制器将不允许您访问GETPOSTPUTDELETE操作方法。

让我们开始在客户端设计一些与购物车相关的函数,并尝试调用该控制器中的动作方法,如下图所示:

客户端 AddToCart 函数

当客户点击 Add To Cart 时,信息被添加到另一个名为 My Cart 的 HTML 表中。 如果你为一个产品点击两次“添加到购物车”,它的数量将更新为 2,并相应地计算价格。 每次点击都假设一个特定产品的一个单元。

现在让我们深入研究代码。 下面的代码片段向我们展示了 JavaScript 的AddToCart函数:

function AddToCart(productId, productName, qty, price) 
{
  $('#tblCart tbody')
  .append($('<tr>')
    .attr('data-product-id', productId)
    .append($('<td>').html(productName))
    .append($('<td class="qty">').html(qty))
    .append($('<td class="price">').html('$' + qty * price))
    .append($('<td>')
      .append($('<a>')
        .attr('href', '#')
        .append($('<span>').addClass('glyphicon glyphicon-trash')) 
 // For Delete Icon.
        .click(function () 
        {
          // Delete Cart from Database.
        })
      )
    )
  );
  // Add one Cart record in Database.
}

该函数接受参数productIdproductNameqtyprice,这些信息显示在 cart HTML 表中。

注意,在上面的图像中,每一行都有一个删除图标。 这是通过在包含锚的 span 中添加glyphicon而产生的,锚的click事件也已定义。 我们将在本章后面讨论删除功能。

另外,请注意已添加到该行的data-product-id属性。 这帮助我们唯一地识别一个购物车行,稍后您将看到这是如何帮助的。

现在我们已经准备好调用CartsController将购物车详细信息插入到数据库中。 然而,在这个方法中我们还需要一个东西。 毕竟,如果产品意外地被添加到购物车中会发生什么呢?

在这里,我们需要更新购物车,而不是仅仅向它添加产品,如下面的代码片段所示:

function AddToCart(productId, productName, qty, price) 
{
  // Check if item already present. If yes, increase the qty 
  and calculate price.
  var cartItem = $('#tblCart').find('tr[data-product-id=' + 
  productId + ']');
  if (cartItem.length > 0) 
  {
    var qtyTd = cartItem.find('td.qty');
    var newQty = parseInt(qtyTd.html()) + qty;
    qtyTd.html(newQty);
    cartItem.find('td.price').html('$' + (newQty * price).toFixed(2));
 // Update Cart in Database: PUT /api/Carts/{id}
return;
  }
  $('#tblCart tbody')
  .append($('<tr>')
    .attr('data-product-id', productId)
    .append($('<td>').html(productName))
    .append($('<td class="qty">').html(qty))
    .append($('<td class="price">').html('$' + qty * price))
    .append($('<td>')
      .append($('<a>')
        .attr('href', '#')
        .append($('<span>').addClass('glyphicon glyphicon-trash'))
        .click(function () {
          // Delete Cart from Database: DELETE /api/Carts/{id}
        })
      )
    )
  );
  // Add one Cart record in Database: POST /api/Carts
}

很简单,不是吗? 首先,在产品 ID 的帮助下从购物车表中获取记录,然后相应地更新其数量和价格。 从该块开始,一个return语句确保该记录不再添加到表中。

现在,一切都应该在客户端工作。 现在,我们只需要调用 api 来涉及数据库操作,以便插入、更新和删除的任何行也会在服务器端更新。

API 调用 AddToCart

在本节中,我们将查看客户机进行的实际 API 调用。

POST – api/Carts

将数据插入购物车表可以通过调用POST操作来完成,该操作应该类似于CartsController中的代码块:

// POST: api/Carts
[HttpPost]
public async Task<IActionResult> PostCart([FromBody] Cart cart)
{
  if (!ModelState.IsValid)
  {
    return BadRequest(ModelState);
  }
  _context.Cart.Add(cart);
  try
  {
    await _context.SaveChangesAsync();
  }
  catch (DbUpdateException)
  {
    if (CartExists(cart.Id))
    {
      return new StatusCodeResult(StatusCodes.Status409Conflict);
    }
    else
    {
      throw;
    }
  }
  return CreatedAtAction("GetCart", new { id = cart.Id }, cart);
}

调用此操作的客户端函数可以设计如下:

function PostCart(customerId, productId, qty, finalPrice) 
{
  var cart = 
  {
    Customerid: customerId,
    Productid: productId,
    Qty: qty,
    Finalprice: finalPrice
  };
  $.ajax({
    url: 'http://localhost:57571/api/Carts',
    type: "POST",
    contentType: "application/json",
    dataType: "json",
    data: JSON.stringify(cart),
    success: function (result) {
      console.log(result);
    },
    error: function (message) {
      console.log(message.statusText);
    }
  });
}

简单,不是吗? 现在我们可以构建一个 cart 对象并向它发送一个POST操作。 你可以通过在我们的AddToCart()函数中调用下面的方法来尝试:

// Add one Cart record in Database: POST /api/Carts
PostCart('910D4C2F-B394-4578-8D9C-7CA3FD3266E2',
  productId,
  cartItem.find('td.qty').html(),
  cartItem.find('td.price').html().replace('$', ''))

这里,第一个参数是Customerid,我们对其进行了硬编码。 Customerid可以为任何请求保存在会话存储中——尽管这被认为是一种有风险的做法。

Instead of sending the Customerid, you can send the email id to the POST action. Then using email id, you can get the Customerid, which can be used to insert a Cart record.

现在让我们运行我们的应用,并为特定的产品点击添加到购物车。 哦! 在开发工具中出现以下错误:

为什么会这样?

这个错误的原因其实很明显。 由于已将[Authorize]属性应用于控制器,现在对CartsController的每个调用都期望通过请求Email IdPassword生成 OAuth2.0 Authorize 服务器的令牌。

We already explored OAuth2.0 Authentication in detail.

为了继续我们的实现,我们将从 Postman 调用令牌服务器,并在我们的应用中使用它。理想情况下,当你收到一个未授权的错误时,你应该打开登录屏幕,以便用户可以登录。 如果验证了Email IdPassword,将返回一个令牌。 此令牌可用于进一步的请求,如 Add To Cart。

为了节省时间和空间,我们将直接使用 Postman 生成一个令牌,使用taditdash@gmail.com作为我们的电子邮件 ID,12345作为我们的密码。 使用令牌的后续 Ajax 调用应该如下所示:

$.ajax({
  url: 'http://localhost:57571/api/Carts',
  type: "POST",
  contentType: "application/json",
  dataType: "json",
  data: JSON.stringify(cart),
  headers: { "Authorization": "Bearer eyJhbGciOiJSUzI1NiIs...
  [Long String Removed]" },
  success: function (result) 
  {
    var cartItem = $('#tblCart').find('tr[data-product-id=' + 
    productId + ']');
    cartItem.attr('data-cart-id', result.id);
  },
});

注意,为了简洁起见,我们在前面的代码片段中删除了标记字符串。 使用前面的代码,将创建一个 cart 记录,并且从 API 返回的数据将为我们提供关于该记录的所有细节。 您可以将购物车的Id存储在 HTML 行中(如前面代码块中所示),以便在更新或删除购物车记录时进行进一步处理。

下面的截图的元素标签在 Chrome 开发工具中说明了购物车记录的 ID 存储为一个data-cart-id属性:

PUT – api/Carts/{id}

现在我们已经添加了一条购物车记录,接下来让我们在客户重复点击 Add to cart 按钮时更新该记录。 我们已经有了更新客户端表上的数量和价格的代码,所以我们只需要编写调用PUT端点来更新记录的代码,如下:

function PutCart(cartItem) 
{
  var cart = 
  {
    Id: cartItem.attr('data-cart-id'),
    Customerid: '910D4C2F-B394-4578-8D9C-7CA3FD3266E2',
    Productid: cartItem.attr('data-product-id'),
    Qty: cartItem.find('td.qty').html(),
    Finalprice: cartItem.find('td.price').html().replace('$', '')
  };
  $.ajax({
    url: 'http://localhost:57571/api/Carts/' + cart.Id,
    type: "PUT",
    contentType: "application/json",
    dataType: "json",
    data: JSON.stringify(cart),
    headers: { "Authorization": "Bearer eyJhbGciOiJSUzI1NiIs..." }
  });
}

前面代码的重要部分是 URL,它还包含购物车Id,因为该路径实际上是api/Carts/{id}。 数据进入体内。

参数cartItem是可以从AddToCart函数传递的行,如下所示:

// Update Cart in Database: PUT /api/Carts/{id}
PutCart($('#tblCart').find('tr[data-product-id=' + productId + ']'));

API 操作应该如下所示:

// PUT: api/Carts/5
[HttpPut("{id}")]
public async Task<IActionResult> PutCart([FromRoute] Guid id, [FromBody] Cart cart)
{
  if (!ModelState.IsValid)
  {
    return BadRequest(ModelState);
  }
  if (id != cart.Id)
  {
    return BadRequest();
  }
  _context.Entry(cart).State = EntityState.Modified;
  try
  {
    await _context.SaveChangesAsync();
  }
  catch (DbUpdateConcurrencyException)
  {
    if (!CartExists(id))
    {
      return NotFound();
    }
    else
    {
      throw;
    }
  }
  return NoContent();
}

注意,id是从路由中读取的,因为它被属性[FromRoute]标记,而 cart 对象是从请求体中读取的,因为它被标记为[FromBody]。 如果一个 ID 没有与路由一起发送,客户端将收到一个 400 BadRequest 错误。

API 动作现在已经更新了记录的必要细节,如下图所示:

如你所见,我们已经点击了四次添加到购物车。 finalPrice49.99 * 4计算。

DELETE – api/Carts/{id}

Route/api/Carts/{id}告诉我们只需要将 cartId发送到 API; 其他一切都将由 API 处理,以便从数据库中删除该记录。

删除记录的操作方法如下:

// DELETE: api/Carts/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCart([FromRoute] Guid id)
{
  if (!ModelState.IsValid)
  {
    return BadRequest(ModelState);
  }
  var cart = await _context.Cart.SingleOrDefaultAsync(m => m.Id == id);
  if (cart == null)
  {
    return NotFound();
  }
  _context.Cart.Remove(cart);
  await _context.SaveChangesAsync();
  return Ok(cart);
}

客户端应用必须更新以允许此特性。 由于删除图标已经显示在 HTML 表的每个购物车行上,我们只需要在用户单击时将购物车的 ID 发送给 API。

下面的 JavaScript 函数可以用来删除购物车记录:

function DeleteCart(cartId) 
{
  $.ajax({
    url: 'http://localhost:57571/api/Carts/' + cartId,
    type: "DELETE",
    contentType: "application/json",
    headers: { "Authorization": "Bearer " + accessToken },
    success: function (result) {
      if (result.id) {
 // Deleting the row from the html table.
        var cartItem = $('#tblCart').find('tr[data-cart-id=' + 
        cartId + ']');
        cartItem.remove();
      }
    }
  });
}

如您所见,DeleteCart函数需要一个参数cartId,该参数将在单击删除图标时提供。 该函数调用类型为DELETEId和 URL 的 API。 成功删除后,购物车行将从 HTML 表中删除。

DeleteCart调用的代码块位于AddToCart内部,如下代码片段所示:

cartItem = $('#tblCart tbody')
.append($('<tr>')
  .attr('data-product-id', productId)
  .append($('<td>').html(productName))
  .append($('<td class="qty">').html(qty))
  .append($('<td class="price">').html('$' + qty * price))
  .append($('<td>')
    .append($('<a>')
      .attr('href', '#')
      .append($('<span>').addClass('glyphicon glyphicon-trash'))
      .click(function () {
        // Delete Cart from Database: DELETE /api/Carts/{id}
        DeleteCart($(this).parents('tr').attr('data-cart-id'));
      })
    )
  )
);

DeleteCart在显示删除图标的锚的单击事件中调用。 在事件内部,我们通过提取data-cart-id属性的值从行本身获得购物车Id

订单

我们的购物车现在装满了所需产品的正确数量,所以现在是时候下订单了。 为此,我们需要调用另一个控制器——OrdersController

以下两个表负责订购过程:

  • Orders:存储发货地址详细信息、客户详细信息、订单状态等
  • OrdersProducts:存储添加到购物车的产品、它们的价格和数量

Orders类是由我们最初所做的 scaffolding 生成的,它包含所有必要的信息。 让我们用这个类来生成控制器。 遵循与生成ProductsControllerProductsdetailsControllerCartsController相同的过程来生成控制器。

The model and controller class can be found in the GitHub repository.

现在可以调用OrdersControllerPOST操作在客户端保存订单。 下面的代码是实现该功能的函数框架:

function PostOrders()
{
  // 1\. Build order object to match the model class Orders.cs.
  // 2\. Push cart items into order object as an array.
  // 3\. Call POST /api/Orders.
}

现在让我们一步一步地解释这个问题。

UI 设计下一个订单

在继续进行下一步之前,我们需要向用户显示一个模态,用户可以在其中输入送货地址。 一旦单击 Place Order 按钮,模式将被打开,如下面的截图所示。

下面的代码片段演示了“Place Order”的点击事件(如果购物车项目出现,模式将被打开):

$('#btnPlaceOrder').click(function () 
{
  var cartItems = $('#tblCart tbody tr');
  // If Cart items present, then show modal to enter Shipping Address.
  if (cartItems.length > 0) {
    $('#Order').modal('show');
    return;
  }
  alert("Please add items into the cart.");
});

Ajax 调用通过使用POST单击 Submit 将订单记录插入到数据库中。 下面的代码片段是 Submit 的点击事件:

$('#btnConfirmOrder').click(function () {
  PostOrders();
});

客户端 PostOrder 函数

现在让我们来看看PostOrders所需的步骤。

构建 order 对象来匹配模型类 Orders.cs

在这里,我们必须从与运输信息相关的文本框中读取值,并将它们与Orders.cs的字段进行匹配,以构建一个对象。 OrdersProducts是表示模型类OrdersProducts.cs的数组。 每个订单都可以有多个与之相关的产品。

下面的代码实现了 order 对象:

// 1\. Build order object to match the model class Orders.cs.
var order = {
  Customerid: customerId,
  CustomerStreetaddress: $('#txtStreetAdd').val(),
  Customercity: $('#txtCity').val(),
  Customerstate: $('#txtState').val(),
  Customerpostalcode: $('#txtPostalCode').val(),
  Customercountry: $('#txtCountry').val(),
  OrdersProducts: new Array()
};

将购物车项作为数组推入 order 对象

填充OrdersProducts数组是下一步,这可以通过循环遍历购物车表的行并将每个购物车行的详细信息推入数组来完成。 在循环中,从该行的属性或td中读取所有必要的值。 记住要形成一个对象,并将值赋给匹配模型类的字段名,如下所示:

// 2\. Push cart items into order object as an array.
$('#tblCart tbody tr').each(function () 
{
  order.OrdersProducts.push(
  {
    Productid: $(this).attr('data-product-id'),
    Productname: $(this).find('td.name').html(),
    Productprice: $(this).attr('data-price'),
    Finalprice: $(this).find('td.price').html().replace('$', ''),
    Productqty: $(this).find('td.qty').html()
  });
});

调用 POST / api /订单

太好了,我们找到目标了! 现在是时候调用带有POST请求的 API/api/Orders,以便我们的订单进入数据库,如下所示:

// 3\. Call POST /api/Orders.
$.ajax({
  url: 'http://localhost:57571/api/Orders',
  type: "POST",
  contentType: "application/json",
  dataType: "json",
  data: JSON.stringify(order),
  headers: { "Authorization": "Bearer " + accessToken },
  success: function (result) {
    alert("Order Placed Successfully.")
  }
});

如果一切正常,你应该会看到如下截图:

但是我们忘记了一些东西; 虽然我们已经成功下了订单,但是我们需要清空购物车。 这可以通过调用PostOrderssuccess函数中的每个购物车项目DELETE /api/Carts来实现,如下所示:

success: function (result) 
{
  // Empty Cart.
 $('#tblCart tbody tr').each(function () {
 DeleteCart($(this).attr('data-cart-id'));
 });
  alert("Order Placed Successfully.");
},

我们已经从客户端探索了所有内容,所以现在是时候检查 API 了。

PostOrders API POST 方法

Orders 表看起来与我们发送给客户端的表有点不同。 在下面的屏幕截图中,注意框中标记的字段。 这些是我们不发送的字段,而是将在 action 方法中操作:

可以从 Customers 表中获取姓名、电子邮件和电话号码等字段。 Customerid是从客户端发送的,我们将使用它来获取这些细节,如下所示:

// POST: api/Orders
[HttpPost]
public async Task<IActionResult> PostOrders([FromBody] Orders orders)
{
  if (!ModelState.IsValid)
  {
    return BadRequest(ModelState);
  }
  // Retrieve customer details and add to order.
  if (orders.Customerid != null)
  {
    var customer = _context.Customers.SingleOrDefault
    (x => x.Id == orders.Customerid);
    if (customer != null)
    {
      orders.Deliveryname = orders.Customername = customer.Firstname;
 orders.Customeremail = customer.Email;
 orders.Customertelephone = customer.Telephone;
    }
  }
  ...

下面的代码片段演示了用户如何复制他们的账单地址,以便它也是他们的送货地址:

// Copy customer address to delivery address.
orders.Deliverycity = orders.Customercity;
orders.Deliverycountry = orders.Customercountry;
orders.Deliverystreetaddress = orders.CustomerStreetaddress;
orders.Deliverypostalcode = orders.Customerpostalcode;
orders.Deliverystate = orders.Customerstate;

其他字段datapurchasedlastmodifiedorderdatefinished设置为DateTime.Now。 细节如currencycurrency_value将被设置为美元($)和零(0),我们还将Guid.NewGuid设置为shipingmethodidpaymentmethodid

这些可以在 Orders 构造器中创建,如下所示:

public Orders()
{
  Id = Guid.NewGuid();
  OrderProductAttributes = new HashSet<OrderProductAttributes>();
  OrdersProducts = new HashSet<OrdersProducts>();
  Datepurchased = DateTime.Now;
  Lastmodified = DateTime.Now;
  Shipingmethodid = Guid.NewGuid();
  Paymentmethodid = Guid.NewGuid();
  Shippingcost = 0;
  Orderdatefinished = DateTime.Now;
  Currency = "$";
  CurrencyValue = 0;
  Orderstatus = "Placed";
}

注意,放置了Orderstatus。 这是在订单准备交付时由站点更新的内容。 随后的状态可能包括已批准、就绪、已装运、已交付等等。 如果您设计了管理屏幕,请确保将此字段更新与latsmodifiedorderdatefinished一起处理。

The application demonstrated in this book is not production-ready. Generally, there should be a login page that works with OAuth2.0 Authentication. Basic validations on the API side, as well as the client side, also need to be handled. In this book, our application is built to showcase the concepts we are exploring, but you can definitely optimize our example and even build on top of it.

暴露航运细节

GET请求可以在OrdersController上使用一个订单 ID,以便第三方站点可以使用订单详细信息进行显示。 例如,许多快递公司公开了它们的 api,其他站点使用这些 api 来显示订单、发货和跟踪信息。

例如,让我们看看我们的GET方法OrdersController,它将 ID 作为参数:

// GET: api/Orders/5
[HttpGet("{id}")]
public async Task<IActionResult> GetOrders([FromRoute] Guid id)
{
  if (!ModelState.IsValid)
  {
    return BadRequest(ModelState);
  }
  var orders = await _context.Orders.Include
  (o => o.OrdersProducts).SingleOrDefaultAsync(m => m.Id == id);
  if (orders == null)
  {
    return NotFound();
  }
  return Ok(orders);
}

注意,Include子句用于包含OrdersProducts表的结果。 现在让我们对这个端点执行一个快速的 Postman 调用,以查看前面的订单的结果,如下面的截图所示:

在这里,您可以看到从 API 返回的与订单相关的每个细节及其产品。

总结

如果您已经读到了这本书的这一点,那么您应该已经使用 api 设计了一些很酷的东西。 做得好!

在本章中,我们使用ProductsController在客户端应用中显示产品列表。产品属性及其定价细节通过使用 Bootstrap、jQuery 和 HTML 设计的简单 UI 显示。

ProductsController中使用searchString参数稍微修改了GET请求,帮助我们从 API 检索搜索结果。 客户端可以通过使用文本端点轻松实现搜索功能。

然后我们看了看购物车。 我们探索了如何使用CartsController操作来添加、更新和删除购物车条目,同时更新 UI。 在这个过程中,我们使用身份验证来实现控制器的安全性。

最后,我们将购物车中的物品转换为可视化的订单。 这是使用OrdersController完成的,它还可以用于向客户提供运输和跟踪信息。

在下一章中,我们将看看测试。net Core 中设计的 RESTful Web API 的不同技术。**