C# Cơ bản .NET Core §1 Cài đặt, chương trình C# đầu tiên §2 Biến, kiểu dữ liệu và nhập/xuất §3 Toán tử số học và gán §4 So sánh, logic và lệnh if, switch §5 Vòng lặp for, while §6 Phương thức - Method §7 Phương thức - Delegate §8 Lớp - Class §9 Namespace §10 Partial, Nested §11 Kiểu giá trị, tham chiếu §12 Kiểu vô danh và dynamic §13 Biểu thức lambda §14 Event §15 Hàm hủy - Quá tải toán tử - thành viên tĩnh - indexer §16 null và nullable §17 Mảng §18 Chuỗi ký tự §19 Tính kế thừa §20 Phương thức khởi tạo §21 Tính đa hình - abstract - interface §22 Struct và Enum §23 Ngoại lệ Exeption §24 IDisposable - using §25 File cơ bản §26 FileStream §27 Generic §28 Collection - List §29 SortedList §30 Queue / Stack §31 Linkedlist §32 Dictionary - HashSet §33 Phương thức mở rộng §34 ObservableCollection §35 LINQ §36 (Multithreading) async - bất đồng bộ §37 Type §38 Attribute Annotation §39 DI Dependency Injection §40 (Multithreading) Parallel §41 Thư viện lớp §42 (Networking) HttpClient §43 (Networking) HttpMessageHandler §44 (Networking) HttpListener §45 (Networking) Tcp TcpListenerr/TcpClient §46 (ADO.NET) SqlConnection §47 (ADO.NET) SqlCommand §48 (EF Core) Tổng quan §49 (EF Core) Tạo Model §50 (EF Core) Fluent API §51 (EF Core) Query §52 (EF Core) Scaffold §53 (EF Core) Migration §54 (ASP.NET CORE) Hello World! §55 (ASP.NET CORE) Middleware §56 (ASP.NET CORE) Map - Request - Response §57 (ASP.NET CORE) IServiceCollection - MapWhen §58 (ASP.NET CORE) Session - ISession §59 (ASP.NET CORE) Configuration §60 (ASP.NET CORE) Gửi Mail §61 (ASP.NET CORE) SASS/SCSS §62 (ASP.NET CORE) LibMan §63 (ASP.NET RAZOR) Khởi tạo và Route §64 (ASP.NET RAZOR) Cú pháp Razor §65 (ASP.NET RAZOR) Layout trong ASP.NET Core §66 (ASP.NET RAZOR) Partial §67 (ASP.NET RAZOR) ViewComponent §68 (ASP.NET RAZOR) TagHelper §69 (ASP.NET RAZOR) PageModel §70 (ASP.NET RAZOR) Model Binding §71 (ASP.NET RAZOR) HTML Form, Validation §72 (ASP.NET RAZOR) Upload File §73 (ASP.NET RAZOR) HtmlHelper §74 (ASP.NET RAZOR) Entity Framework §75 (ASP.NET RAZOR) Paging §76 (ASP.NET RAZOR) Identity (1) - Register, Login, Logout §77 (ASP.NET RAZOR) Identity (2) Lockout, Reset Password §78 (ASP.NET RAZOR) Identity (3) Google Login §79 (ASP.NET RAZOR) Identity (4) Facebook Login §80 (ASP.NET RAZOR) Identity (5) profile, password, email ... §81 (ASP.NET RAZOR) Identity (6) Role §82 (ASP.NET RAZOR) Identity (7) Role-based Authorization §83 (ASP.NET RAZOR) Identity (8) RoleClaim §84 (ASP.NET RAZOR) Identity (9) Authorization Handler §85 (ASP.NET RAZOR) IAuthorizationService §86 (ASP.NET MVC) Controller - View §87 (ASP.NET MVC) Route §88 (ASP.NET MVC) EF, Identity §89 (ASP.NET MVC) Binding, Validation §90 (ASP.NET MVC) Xây dựng Website(1) §91 (ASP.NET MVC) Xây dựng Website(2) §92 (ASP.NET MVC) Xây dựng Website(3) §93 (ASP.NET MVC) Xây dựng Website(4) §94 (ASP.NET MVC) Giỏ hàng - Cart (5) §95 (ASP.NET MVC) elFinder (5) §96 (ASP.NET MVC) SB Admin (6)

Trong phần này xây dựng chức năng tạo giỏ hàng đơn giản với ASP.NET Core, ta sẽ tạo trang liệt kê các sản phẩm và cho phép chọn sản phảm vào giỏ hàng Card.

Bài thực hành này tiếp tục trên ví dụ cũ mvcblog: Trang hiện thị các bài viết (phần 4), tải mã nguồn về mở ra tiếp tục phát triển ex068-fontendpost

Tạo Model Product

Tạo ra một lớp đơn giản biểu diễn các sản phẩm, đặt tên lớp là Product

[Table("Product")]
public class Product
{
    [Key]
    public int ProductId {set; get;}
    public string Name {set; get;}
    public string Description {set; get;}
    public decimal Price {set; get;}
}

Cập nhật vào AppDbContext

public class AppDbContext : IdentityDbContext<AppUser> {

    / ... (nội dung khác)

    public DbSet<Product> Products {set; get;}

    / ... (nội dung khác)

    protected override void OnModelCreating (ModelBuilder builder) {

        / ... (nội dung khác)

        // SeedData - chèn ngay bốn sản phẩm khi bảng Product được tạo
        builder.Entity<Product>().HasData(
                new Product() {
                    ProductId = 1,
                    Name = "Đá phong thuỷ tự nhiên",
                    Description = "Số 1 cao 40cm rộng 20cm dày 20cm màu xanh lá cây đậm",
                    Price = 1000000
                },
                new Product() {
                    ProductId = 2,
                    Name = "Đèn đá muối hình tròn",
                    Description = "Trang trí trong nhà Chất liệu : • Đá muối",
                    Price = 1500000
                },
                new Product() {
                    ProductId = 3,
                    Name = "Tranh sơn mài",
                    Description = "Tranh sơn mài loại nhỏ 15x 15 giá 50.000",
                    Price = 50000
                } ,
                new Product() {
                    ProductId = 4,
                    Name = "Tranh sơn dầu - Ngựa",
                    Description = "Nguyên liệu thể hiện :    Sơn dầu",
                    Price = 450000
                }
        );
    }

}

Thực hiện tạo Migration và cập nhật vào Database, Migration mới đặt tên là AddProduct

dotnet ef migrations add AddProduct
dotnet ef database update AddProduct

Trong quá trình tạo Db ở trên, có SeedData tạo ra 4 sản phẩm mẫu trong CSDL. Trong trường hợp muốn tạo các chức năng CRUD thì thực hiện tương tự như với Post trong ví dụ trước, tại đây chỉ sử dụng các sản phẩm mẫu chèn vào như trên để cho ngắn gọn.

mvcblog

Controller hiện thị danh sách sản phẩm

Ta xây dựng Controller - ProductController hiện thị danh sách sản phẩm và các chức năng liên quan đến cart như đưa một sản phẩm vào giỏ hàng, xóa sản phẩm khỏi giỏ hàng ... Các chức năng đó thực hiện với các Action tương ứng như sau:

Controllers/ProductController.cs

[Route("/products")]
public class ProductController : Controller
{
    private readonly ILogger<ProductController> _logger;

    private readonly AppDbContext _context;

    public ProductController(ILogger<ProductController> logger, AppDbContext context)
    {
        _logger = logger;
        _context = context;
    }

    // Hiện thị danh sách sản phẩm, có nút chọn đưa vào giỏ hàng
    public IActionResult Index()
    {
        var products = _context.Products.ToList();
        return View(products);
    }

    /// Thêm sản phẩm vào cart
    [Route("addcart/{productid:int}")]
    public IActionResult AddToCart([FromRoute]int productid) {

        var product = _context.Products
                        .Where(p => p.ProductId == productid)
                        .FirstOrDefault();
        if (product == null)
            return NotFound("Không có sản phẩm");

        // Xử lý đưa vào Cart ...


        return RedirectToAction(nameof(Cart));
    }
    /// xóa item trong cart
    [Route("/removecart/{productid:int}", Name = "removecart")]
    public IActionResult RemoveCart([FromRoute]int productid) {

        // Xử lý xóa một mục của Cart ...
        return RedirectToAction(nameof(Cart));
    }

    /// Cập nhật
    [Route ("/updatecart", Name = "updatecart")]
    [HttpPost]
    public IActionResult UpdateCart([FromForm]int productid, [FromForm]int quantity) {
        // Cập nhật Cart thay đổi số lượng quantity ...

        return RedirectToAction(nameof(Cart));
    }


    // Hiện thị giỏ hàng
    [Route("/cart", Name = "cart")]
    public IActionResult Cart()
    {
        return View();
    }

    [Route("/checkout")]
    public IActionResult CheckOut()
    {
        // Xử lý khi đặt hàng
        return View();
    }

}

Trang Index tương ứng với Action Index ở trên, hiện thị danh sách các sản phẩm, tại View Index.cshtml xây dựng như sau:

Views/Product/index.cshtml

@model List<mvcblog.Models.Product>
@{
    ViewData["Title"] = "Các sản phẩm";
}

<div class="card-columns">
    @foreach (var product in Model)
    {
        <div class=card>
            <h3 class="card-header">@product.Name</h3>
            <div class="card-body" style="height: 150px;">
                @product.Description
            </div>
            <div class="card-footer">
                <span class="text-muted">@(product.Price.ToString("n0")) VND</span>    
                <a asp-route="addcart" 
                   asp-route-productid="@product.ProductId"
                   class="btn btn-secondary btn-sm float-right">Đặt hàng</a>
            </div>
        </div>
    }

</div>
mvcblog

Kích hoạt và sử dụng Session

Để xây dựng chức năng giỏ hàng, danh sách các mặt hàng sẽ lưu trong Session của hệ thống. Do vậy, ứng dụng cần đảm bảo kích hoạt Session - làm theo hướng dẫn tại Sử dụng Session trong ASP.NET , đồng thời cũng dùng kỹ thuật JSON để lưu dữ liệu nên cần đảm bảo tích hợp hai gói là:

dotnet add package Newtonsoft.Json
dotnet add package Microsoft.AspNetCore.Session
dotnet add package Microsoft.Extensions.Caching.Memory

Thêm vảo Startup.ConfigureServices

services.AddDistributedMemoryCache();           // Đăng ký dịch vụ lưu cache trong bộ nhớ (Session sẽ sử dụng nó)
services.AddSession(cfg => {                    // Đăng ký dịch vụ Session
    cfg.Cookie.Name = "xuanthulab";             // Đặt tên Session - tên này sử dụng ở Browser (Cookie)
    cfg.IdleTimeout = new TimeSpan(0,30, 0);    // Thời gian tồn tại của Session
});

Trong Startup.Configure cho thêm vào (sau UseStaticFiles()):

app.UseSession();

Giờ trong hệ thống Session đã làm việc, đồng thời cũng có thư viện JSON, một chức năng cần nhớ để áp dụng cho ví dụ này là:

  • Trong Controller, Session được truy cập thông qua giao diện ISession, giao diện này lấy được bằng thuộc tính HttpContext.Session

    var session = HttpContext.Session;
  • Các mục lưu trong session dưới dạng key/value. Bạn có thể đọc và lưu một chuỗi vào session bằng phương thức GetString(key, value)SetString(key, value). Tuy hai phương thức này là mở rộng của ISession nên cần có using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http;
    
    /...
    
        var session = HttpContext.Session;
        string valueString = session.GetString ("key");
    
        session.SetString("yourkey", "yourstring");
    
  • Thư viện Newtonsoft.Json giúp làm việc với JSON, ở đây cần nhớ hai chức năng. Chuyển một đối tượng thành chuỗi json và ngược lại phục hồi đối tượng từ chuỗi json

    Để chuyển một đối tượng (thuộc tính đối tượng) thành chuỗi json dùng SerializeObject

    string jsonstring =  JsonConvert.SerializeObject(ob);

    Để chuyển chuỗi json thành đối tượng dùng DeserializeObject<ObjectClas>

    Type obj = JsonConvert.DeserializeObject<Type>(jsonstring);
    

Xây dựng cấu trúc Cart

Giỏ hàng ta xây dựng nó là một danh sách List, chứa các mục trong giỏ hàng, mỗi mục là một đối tượng lớp CartItem, lớp đó như sau:

public class CartItem
{
    public int quantity {set; get;}
    public Product product {set; get;}
}

Từ đó ta sẽ xây dựng các chức năng của Cart là các phương thức trong controller gồm:

public class ProductController : Controller {

    /...

    // Key lưu chuỗi json của Cart
    public const string CARTKEY = "cart";

    // Lấy cart từ Session (danh sách CartItem)
    List<CartItem> GetCartItems () {

        var session = HttpContext.Session;
        string jsoncart = session.GetString (CARTKEY);
        if (jsoncart != null) {
            return JsonConvert.DeserializeObject<List<CartItem>> (jsoncart);
        }
        return new List<CartItem> ();
    }

    // Xóa cart khỏi session
    void ClearCart () {
        var session = HttpContext.Session;
        session.Remove (CARTKEY);
    }

    // Lưu Cart (Danh sách CartItem) vào session
    void SaveCartSession (List<CartItem> ls) {
        var session = HttpContext.Session;
        string jsoncart = JsonConvert.SerializeObject (ls);
        session.SetString (CARTKEY, jsoncart);
    }
/...

Xây dựng chức năng thêm mặt hàng vào cart

Khi người dùng bấm vào Đặt hàng ở danh sách sản phẩm, thì nó sẽ chuyển đến Action AddToCart, có chức năng đưa sản phẩm đó vào Cart. Xây dựng Action đó như sau:

/// Thêm sản phẩm vào cart
[Route ("addcart/{productid:int}", Name = "addcart")]
public IActionResult AddToCart ([FromRoute] int productid) {

    var product = _context.Products
        .Where (p => p.ProductId == productid)
        .FirstOrDefault ();
    if (product == null)
        return NotFound ("Không có sản phẩm");

    // Xử lý đưa vào Cart ...
    var cart = GetCartItems ();
    var cartitem = cart.Find (p => p.product.ProductId == productid);
    if (cartitem != null) {
        // Đã tồn tại, tăng thêm 1
        cartitem.quantity++;
    } else {
        //  Thêm mới
        cart.Add (new CartItem () { quantity = 1, product = product });
    }

    // Lưu cart vào Session
    SaveCartSession (cart);
    // Chuyển đến trang hiện thị Cart
    return RedirectToAction (nameof (Cart));
}

Trang hiện thị Cart

Tương ứng với action Cart

// Hiện thị giỏ hàng
[Route ("/cart", Name = "cart")]
public IActionResult Cart () {
    return View (GetCartItems());
}

Views/Product/cart.cshtml

@model List<mvcblog.Models.CartItem>

<h2>GIỎ HÀNG</h2>

@if (Model.Count > 0) {
  decimal total = 0;
  int stt = 1;
  
  <table class="table">
    <tr>
      <th>#</th>
      <th>Sản phẩm</th>
      <th>Giá</th>
      <th>Số lượng</th>
      <th>Thành tiền</th>
      <th></th>
    </tr>
    @foreach (var cartitem in Model)
    {
        var thanhtien = cartitem.quantity * cartitem.product.Price;
        total += thanhtien;

        <tr>
           <td>@(stt++)</td>
           <td>@cartitem.product.Name</td>
           <td>@(cartitem.product.Price.ToString("n0"))</td>
           <td><input asp-for="@cartitem.quantity" id="@($"quantity-{cartitem.product.ProductId}")"/></td>
           <td>@(thanhtien.ToString("n0"))</td>
           <td>
              <button class="btn btn-success updatecartitem" 
                    data-productid="@cartitem.product.ProductId">Cập nhật</button>
              <a asp-route="removecart" asp-route-productid="@cartitem.product.ProductId"
                class="btn btn-danger">Xóa</a>
           </td>
        </tr>
    }
      <tr>
          <td colspan="4" class="text-right">Tổng tiền</td>
          <td>@(total.ToString("n0"))</td>
          <td></td>
      </tr>
  </table>

  <a asp-controller="Product" asp-action="Checkout" class="btn btn-success">Gửi đơn hàng</a>

      @section Scripts {
        <script>
          $(document).ready(function () {
              $(".updatecartitem").click(function (event) {
                  event.preventDefault();
                  var productid = $(this).attr("data-productid");
                  var quantity = $("#quantity-" + productid).val();
                  $.ajax({
                      type: "POST",
                      url:"@Url.RouteUrl("updatecart")",
                      data: {
                          productid: productid,
                          quantity:quantity
                      },
                      success: function (result) {
                          window.location.href = "@Url.RouteUrl("cart")";
                      }
                  });
              });
          });
        </script>
      }

}
else {
  <p class="alert alert-danger">Giỏ hàng trống</p>
}
mvcblog

Trong đó đoạn mã tại section Script là JQuery - để cập nhật lại số lượng của một mặt hàng nào đó. Người dùng điền số lượng mới và bấm vào cập nhật. Khi bấm vào cập nhật sẽ sử dụng Ajax gửi (post) dữ liệu là ProductId và số lượng tương ứng đến Action UpdateCart, nội dung Action này như sau

/// Cập nhật
[Route ("/updatecart", Name = "updatecart")]
[HttpPost]
public IActionResult UpdateCart ([FromForm] int productid, [FromForm] int quantity) {
    // Cập nhật Cart thay đổi số lượng quantity ...
    var cart = GetCartItems ();
    var cartitem = cart.Find (p => p.product.ProductId == productid);
    if (cartitem != null) {
        // Đã tồn tại, tăng thêm 1
        cartitem.quantity = quantity;
    }
    SaveCartSession (cart);
    // Trả về mã thành công (không có nội dung gì - chỉ để Ajax gọi)
    return Ok();
}

Tương tự action RemoveCart có chức năng xóa một sản phẩm khỏi Cart, xây dựng nó như sau

/// xóa item trong cart
[Route ("/removecart/{productid:int}", Name = "removecart")]
public IActionResult RemoveCart ([FromRoute] int productid) {
    var cart = GetCartItems ();
    var cartitem = cart.Find (p => p.product.ProductId == productid);
    if (cartitem != null) {
        // Đã tồn tại, tăng thêm 1
        cart.Remove(cartitem);
    }

    SaveCartSession (cart);
    return RedirectToAction (nameof (Cart));
}

Tạo Partial hiện thị giỏ hàng ở menu

Tạo ra partial _CartPartial.cshtml để hiện thị giỏ hàng trên menu như sau:

Views/Shared/_CartPartial.cshtml

@using Microsoft.AspNetCore.Http
@using Newtonsoft.Json
@inject IHttpContextAccessor HttpContextAccessor


@{
    var session = HttpContextAccessor.HttpContext.Session;
    string jsoncart = session.GetString (mvcblog.Controllers.ProductController.CARTKEY);
    if (jsoncart != null) {
        var cart = JsonConvert.DeserializeObject<List<CartItem>> (jsoncart);
        <div class="nav-item"><a asp-route="cart">Giỏ hàng(@cart.Count)</a></div>
         
    }
}

Trong Code trên truy cập Session từ View, do đó phải Inject IHttpContextAccessor.

Mở layout ra, chèn đoạn mã để render partial này tại menu

@await Html.PartialAsync("_CartPartial")
mvcblog

Mã nguồn tham khảo ASP_NET_CORE/mvcblog, hoặc tải về bản bài này ex068-cart

Đăng ký theo dõi ủng hộ kênh