C# cơ bản .NET Core

Sử dụng code-behind trang Razor Page

Kỹ thuật code-behind áp dụng cho ứng dụng web kiểu Razor Page đó là sự phân tách giữa mã HTML viết trong Razor Page và mã nguồn C#. Để thực hành tìm hiểu, hãy nhanh chóng tạo ra một dự án ASP.NET Core Razor Page như sau:

Tạo thư mục dự án razor04.codebehide, vào thư mục này và thực hiện lệnh

dotnet new webapp

Do chưa đến phần áp dụng cơ sở dữ liệu vào trang, nên tạo ra một mô hình dữ liệu đơn giản như sau. Tạo một lớp biểu diễn sản phẩm và một danh sách sản phẩm

using System;
using System.Collections.Generic;
using System.Linq;

namespace razor04.codebehide.Models {

  // Lớp Product
  public class Product {
    public int ID {set; get;}
    public String Name {set; get;}
    public String Desciption {set; get;}
    public Decimal Price {set; get;} = 0;
  }

  // Lớp tĩnh giả định DbContext
  public static class ProductContext {
    public static List<Product> products;
    static ProductContext() {
      // Khởi tạo một danh sách các sản phẩm mẫu
      products = new List<Product>() {
        new Product {
          ID=1,
          Name = "Iphone",
          Price = 900,
          Desciption = "Điện thoại Iphone abc, xyz ..."
        },
        new Product {
          ID = 2,
          Name = "Samsung",
          Price = 800,
          Desciption = "Điện thoại Samsung, samsung điện thoại ..."
        },
        new Product {
          ID = 3,
          Name = "Nokia",
          Price = 700,
          Desciption = "Điện thoại Nokia, điện thoại Android"
        }
      };
    }

    // Tìm sản phẩm theo ID
    public static Product FindProductByID(int ID) {
      var p = from product in products
              where product.ID == ID
              select product;
      return p.FirstOrDefault();
    }

  }

}
// Lấy danh sách sản phẩm: ProductContext.products
// Tìm một sản phẩm theo ID: ProductContext.FindProductByID(id)

Giờ ta sẽ tạo một Razor Page có có tên Pages/ViewProduct.cshtml để hiện thị về một sản phẩm theo ID, sử dụng mô hình dữ liệu trên, trường hợp đầu tiên ta sẽ viết code sử dụng dữ liệu ngay trong .cshtml sau đó ở trường hợp sau sẽ áp dụng mô hình code-behind

Trường hợp viết code ngay trong Razor Page

Pages/ViewProduct.cshtml

@page "/sanpham/{id:int?}"
    
@functions {
  Product product;
  void OnGet() {
    int? ID = null;
    if (Request.RouteValues["id"] != null) {
      ID = Int32.Parse(Request.RouteValues["id"].ToString());
      product = ProductContext.FindProductByID(ID.Value);
    }
  }
}
@{
  this.OnGet();
}

@if (product != null) {
    <h2>@product.Name</h2>
    <p>Mô tả: @product.Desciption</p>
    <p>Giá: @product.Price</p>
    <p>
      <a asp-page="ViewProduct" asp-route-id="">Sản phẩm khác</a>
    </p>
}
else {
  <h2>Các sản phẩm</h2>
  <ul>
    @foreach (var p in ProductContext.products)
    {
        <li>
          <a asp-page="ViewProduct" asp-route-id="@p.ID">@p.Name</a>
        </li>
    }
  </ul>
}

Một số diễn giải cho đoạn code trên:

@page "/sanpham/{id:int?}"

Để định nghĩa route truy nhập tới Razor Page này, các URL phù hợp đó là /sanpham/, /sanpham/1, /sanpham/2 ...

Nếu truy cập /sanpham/1 thì id của route = 1, sẽ hiện thị thông tin sản phẩm có ID = 1, id này đọc được ở đoạn code:

Request.RouteValues["id"]

Khối chỉ thị @functions để khai báo phương thức, thuộc tính cho thêm vào lớp sinh ra bởi Razor. Ở đây thêm thuộc tính product và phương thức OnGet(có chức năng lấy sản phẩm theo id route gửi đến). Sau khi khai báo, phương thức này thi hành ở đoạn code

@{
    this.OnGet();
}

Phần sau là mã HTML hiện thị thông tin sản phẩm, hoặc danh sách sản phẩm (khi truy cập /sanpham/)

Kết quả chạy:

Trường hợp áp dụng code-behide Razor Page

Đầu tiên tạo ra một lớp kế thừa từ PageModel, lớp này khai báo trong thư mục cùng file Razor Page và đặt tên giống Razor Page có thêm phần .cs, ví dụ trên bạn sẽ tạo ra file ViewProduct.cshtml.cs

ViewProduct.cshtml.cs chứa Model cho trang ViewProduct.cshtml

Nội dung trong ViewProduct.cshtml.cs khai báo như sau:

using System;
using Microsoft.AspNetCore.Mvc.RazorPages;
using razor04.codebehide.Models;
using static System.Console;

namespace razor04.codebehide.Pages {

  // Lớp là Model của Razor, nên phải kế thừa PageModel
  public class ViewProductModel : PageModel {

    // Khai báo thuộc tính
    public Product product;


    // Handler chạy khi truy cập trang bằng phương thức get
    public void OnGet () {
      int? ID = null;
      if (Request.RouteValues["id"] != null) {
        ID = Int32.Parse (Request.RouteValues["id"].ToString ());
        product = ProductContext.FindProductByID (ID.Value);
      }
    }

  }
}

Để ứng dụng tự động tạo ra đối tượng lớp này khi truy cập trang Razor, thì trong Razor thêm vào dòng chỉ thị thiết lập Model của trang có kiểu ViewProductModel:

@model ViewProductModel

Lúc này truy cập trang một đối tượng lớp ViewProductModel tạo ra và truy cập được ở thuộc tính Model của Razor. Nội dụng của Razor Page (ViewProduct.cshtml) giờ có thể sửa là:

@page "/sanpham/{id:int?}"
@using razor04.codebehide.Models;
@model ViewProductModel


@if (Model.product != null) {
    <h2>@Model.product.Name</h2>
    <p>Mô tả: @Model.product.Desciption</p>
    <p>Giá: @Model.product.Price</p>
    <p>
      <a asp-page="ViewProduct" asp-route-id="">Sản phẩm khác</a>
    </p>
}
else {
  <h2>Các sản phẩm</h2>
  <ul>
    @foreach (var p in ProductContext.products)
    {
        <li>
          <a asp-page="ViewProduct" asp-route-id="@p.ID">@p.Name</a>
        </li>
    }
  </ul>
}

Kết quả chạy vẫn như trên. Tuy nhiên bạn đã phân tách các phần code chính xử lý ở một file Model và trang Razor chủ yếu là hiện thị thông tin của Model. Qua ví dụ này lưu ý một số vấn để về sử dụng PageModel như sau:

Xử lý yêu cầu gửi đến

Khi thiết lập một lớp (kế thừa từ PageModel) là Model của Razor Page thì khi truy vấn đến Page, Model này tự động tạo ra và Inject vào Razor. Nếu lớp Model đó có tham số khởi tạo là đối tượng dịch vụ, thì dịch vụ này phải đăng ký trong ServiceCollection của hệ thống.

Trong lớp Model có các phương thức có tiền tố là On như OnGet, OnPost, OnGetAsync ... thì chúng gọi là các handler. Phương thức này tự động chạy theo phương thức http truy cập (get, post, put ...).

Các thuộc tính trong PageModel đều có hiệu lực trong Razor, truy cập qua thuộc tính Model.

Các phương thức Handler trong PageModel

Như trên, các handler có tiền tố On mặc định nó sử lý các yêu cầu theo các phương thức của http như OnGet, OnPut ... nếu bất đồng bộ thì OnGetAsync, OnPostAsync ...

Các phương thức này trả về kiểu bất kỳ nhưng thường là void, Task hoặc ActionResult

Các phương thức này cũng có thể có các tham số, tham số này tự động truyền vào bởi giá trị query trong URL hay giá trị trường tương ứng của Route hoặc do phần từ cung trên của Form Post tới. Như ví dụ trên, nếu khai báo

void OnGet(int id)
{
    /...
}

Thì khi truy cập /sanpham/3 thì id sẽ bằng 3.

Các phương thức Named Handler trong PageModel

Bạn có thể khai báo nhiều Handler cùng phục vụ truy vấn đến theo một phương thức, ví dụ post. Tuy nhiên Handler cụ thể sẽ thi hành theo ham số handler gửi đến trong URL. Ví dụ, khai báo ba phương thức sau đều nhận yêu cầu post.

public String Thongbao;

// Chạy truy cập post tới, url = /sanpham/2?handler=xoa
public void OnPostXoa(int i)
{
    Thongbao = "Gọi OnPostXoa";
}
// Chạy truy cập post tới, url = /sanpham/2?handler=soanthao
public void OnPostSoanthao(int id)
{
    Thongbao = "Gọi OnPostSoanthao";
}
// Chạy truy cập post tới, url = /sanpham/2?handler=xemchitiet
public void OnPostXemchitiet(int id)
{
    Thongbao = "Gọi OnPostXemchitiet";
}

Thêm mã vào Razor Page kiểm tra xem

<div class="d-flex">
        <form method="post" asp-page-handler="xoa">
            <button class="btn btn-danger m-1">Xóa</button>
        </form>
        <form method="post" asp-page-handler="soanthao">
            <button class="btn btn-danger m-1">Soạn thảo</button>
        </form>
        <form method="post" asp-page-handler="xemchitiet">
            <button class="btn btn-danger m-1">Xem chi tiết</button>
        </form>
        
</div>
<p class="alert alert-danger">@Model.Thongbao</p>

Hoặc

 <form method="post" class="d-flex">
    <button class="btn btn-danger m-1" asp-page-handler="xoa">Xóa</button>
    <button class="btn btn-danger m-1" asp-page-handler="soanthao">Soạn thảo</button>
    <button class="btn btn-danger m-1" asp-page-handler="xemchitiet">Xem chi tiết</button>
</form>

Truyền dữ liệu từ PageModel đến Razor bằng ViewData

Trong PageModel và Razor cùng có thuộc tính ViewData có kiểu ViewDataDictionary, nó dùng để chuyển dữ liệu từ PageModel sang Razor, ví dụ nếu trong PageModel có gán:

public void OnGet (int id) {
      ViewData["mydata"] = "My Data";
     //...
}

Thì trong Razor đọc được giá trị này bằng @ViewData["mydata"]

Hander trả về IActionResult

Khi khai handler trả về kiểu IActionnResult thì có nhiều phương thức giúp tạo đối tượng trả về, tùy ngữ cảnh mà sử dụng:

Ví dụ, truy cập thì chuyển hướng sang trang khác:

public IActionResult OnGet (int id) {
  int? ID = null;
  if (Request.RouteValues["id"] != null) {
    ID = Int32.Parse (Request.RouteValues["id"].ToString ());
    product = ProductContext.FindProductByID (ID.Value);
    if (product == null) {
      // Khônng thấy chuyển hướng về trang /product/
      return RedirectToPage("ViewProduct", new { id = ""});
    }

  }
  // Trả về kết quả trang Razor
  return Page();
}

Ngoài ra còn một số phương thực tạo IActionResult như:

Method Mã HTTP Mô tả
Challenge 401 Trả về khi xác thực không thành công
Content 200 Thiết lập nội dung trả về trực tiếp
File 200 Trả về nội dung file.
Forbid 403 Cấm truy cập.
LocalRedirect
LocalRedirectPermanent
LocalRedirectPreserveMethod
LocalRedirectPreserveMethodPermanent
302
301
307
308
Chuyển hướng
NotFound 404 Không thấy.
Page 200 Trả về nội dung trang Razor.
Partial 200 Trả về nội dung từ một Partial Page
RedirectToPagePermanent
RedirectToPage
RedirectToPagePreserveMethod
RedirectToPagePreserveMethodPermanent
301
302
307
308
Chuyển hướng

Mã nguồn tham khảo: ASP_NET_CORE/razor04.codebehide


Đăng ký nhận bài viết mới