Chuyển đến nội dung chính

Day 2 — Kết nối Next.js 16 với ASP.NET Core Web API: Routing, Parameters và Data Fetching

Ở Day 1, mình đã làm quen với Next.js 16, App Router, cấu trúc thư mục và cách tạo các trang giao diện. Tuy nhiên, một ứng dụng thực tế không thể chỉ hiển thị nội dung viết sẵn trong code. Nó thường cần lấy dữ liệu từ backend, xử lý dữ liệu và hiển thị cho người dùng.

Trong bài này, mình sẽ xây dựng một ví dụ nhỏ gồm hai phần:

  • Frontend: Next.js 16 sử dụng App Router và TypeScript.
  • Backend: ASP.NET Core Web API cung cấp dữ liệu bài viết.

Mục tiêu là hiểu được toàn bộ luồng dữ liệu từ lúc Next.js gửi request, ASP.NET Core xử lý request, trả về JSON cho đến khi dữ liệu xuất hiện trên giao diện.

1. Mục tiêu học tập

Sau khi hoàn thành bài viết, mình muốn có thể:

  • Hiểu request, response, HTTP method và status code.
  • Phân biệt routing của Next.js với routing của ASP.NET Core API.
  • Phân biệt route parameter và query parameter.
  • Tạo API GET để lấy danh sách và chi tiết bài viết.
  • Hiểu cách ASP.NET Core trả về JSON.
  • Sử dụng fetch() và async/await trong Next.js Server Component.
  • Sử dụng TypeScript để mô tả kiểu dữ liệu nhận từ API.
  • Kiểm tra lỗi và debug khi frontend không lấy được dữ liệu.

2. Tổng quan về HTTP và REST API

2.1. Request và Response

Khi trình duyệt hoặc một ứng dụng gửi HTTP request đến server, server sẽ xử lý request rồi trả về HTTP response.

Một request thường có các thành phần:

  • Method: thao tác muốn thực hiện, ví dụ GET hoặc POST.
  • URL: địa chỉ của tài nguyên cần truy cập.
  • Headers: thông tin bổ sung của request.
  • Body: dữ liệu gửi kèm, thường dùng khi tạo hoặc cập nhật dữ liệu.

Một response thường có:

  • Status code: kết quả xử lý request.
  • Headers: metadata của response.
  • Body: dữ liệu trả về, ví dụ JSON.

2.2. Các HTTP method thường gặp

Method Mục đích Ví dụ
GET Lấy dữ liệu GET /api/posts
POST Tạo tài nguyên mới POST /api/posts
PUT Thay thế toàn bộ tài nguyên PUT /api/posts/1
PATCH Cập nhật một phần tài nguyên PATCH /api/posts/1
DELETE Xóa tài nguyên DELETE /api/posts/1

Trong Day 2, mình sẽ tập trung vào GET. POST sẽ được giới thiệu ở phần mở rộng để hiểu cách gửi dữ liệu lên backend.

2.3. Một số status code quan trọng

Status code Ý nghĩa
200 OK Request được xử lý thành công.
201 Created Tài nguyên mới được tạo thành công.
400 Bad Request Request không hợp lệ.
404 Not Found Không tìm thấy tài nguyên.
500 Internal Server Error Server gặp lỗi khi xử lý request.

3. Phân biệt routing của Next.js và ASP.NET Core

Đây là hai khái niệm dễ nhầm lẫn khi bắt đầu làm ứng dụng full-stack. Cả hai đều sử dụng URL, nhưng đảm nhiệm những nhiệm vụ khác nhau.

3.1. Next.js routing

Next.js App Router sử dụng cấu trúc thư mục trong thư mục app để xác định các route của giao diện.

src/
  app/
    page.tsx
    posts/
      page.tsx
      [id]/
        page.tsx

Cấu trúc trên tạo ra các trang:

  • /: trang chủ.
  • /posts: danh sách bài viết.
  • /posts/1: trang chi tiết bài viết có ID bằng 1.

Nếu dự án không sử dụng thư mục src, các thư mục trên sẽ nằm trực tiếp trong app/.

3.2. ASP.NET Core API routing

ASP.NET Core sử dụng attribute routing để xác định endpoint của API. Ví dụ:

[ApiController]
[Route("api/[controller]")]
public class PostsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetPosts()
    {
        return Ok();
    }
}

Với controller tên PostsController, route cơ sở là /api/posts. Attribute [HttpGet] xác định endpoint xử lý HTTP GET.

Vì vậy, /posts và /api/posts không phải cùng một route:

  • /posts là trang giao diện Next.js.
  • /api/posts là endpoint lấy dữ liệu từ ASP.NET Core.

4. Xây dựng ASP.NET Core Web API

Để tập trung vào routing và data fetching, mình sẽ sử dụng dữ liệu giả lập trong bộ nhớ. Chưa cần database hoặc Entity Framework Core.

4.1. Tạo project

Mở terminal và tạo project Web API:

dotnet new webapi -n BlogApi
cd BlogApi
Cài đặt Swagger
dotnet add package Swashbuckle.AspNetCore
Cấu hình Swagger
var builder = WebApplication.CreateBuilder(args);

// OpenAPI
builder.Services.AddOpenApi();

// Register Controller-based APIs
builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();

    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint(
            "/openapi/v1.json",
            "OpenAPI V1");
    });
}

app.UseHttpsRedirection();

// Existing Minimal API
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast(
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();

    return forecast;
})
.WithName("GetWeatherForecast");

// Map Controller routes
app.MapControllers();

app.Run();

record WeatherForecast(
    DateOnly Date,
    int TemperatureC,
    string? Summary)
{
    public int TemperatureF =>
        32 + (int)(TemperatureC / 0.5556);
}

Mở project bằng IDE, tạo thư mục Controllers và thêm file PostsController.cs.

4.2. Tạo model Post

Tạo file Models/Post.cs:

namespace BlogApi.Models;

public record Post(
    int Id,
    string Title,
    string Description
);

Record biểu diễn dữ liệu của một bài viết. Mỗi bài viết có ID, tiêu đề và mô tả.

4.3. Tạo PostsController

Tạo file Controllers/PostsController.cs:

using BlogApi.Models;
using Microsoft.AspNetCore.Mvc;

namespace BlogApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class PostsController : ControllerBase
{
    private static readonly List<Post> Posts =
    [
        new(1, "Learn Next.js", "Introduction to Next.js"),
        new(2, "Learn ASP.NET Core", "Build a REST API"),
        new(3, "Learn Umbraco", "Introduction to Headless CMS")
    ];

    // GET /api/posts
    // GET /api/posts?search=Next
    [HttpGet]
    public IActionResult GetPosts(
        [FromQuery] string? search)
    {
        IEnumerable<Post> result = Posts;

        if (!string.IsNullOrWhiteSpace(search))
        {
            result = result.Where(post =>
                post.Title.Contains(
                    search,
                    StringComparison.OrdinalIgnoreCase));
        }

        return Ok(result);
    }

    // GET /api/posts/1
    [HttpGet("{id:int}")]
    public IActionResult GetPost(int id)
    {
        var post = Posts.FirstOrDefault(
            item => item.Id == id);

        if (post is null)
        {
            return NotFound();
        }

        return Ok(post);
    }
}

Controller này có hai endpoint GET. Endpoint đầu tiên lấy danh sách bài viết và có thể lọc theo tiêu đề. Endpoint thứ hai lấy một bài viết theo ID.

Dữ liệu hiện được lưu trong một danh sách tĩnh để học tập. Đây không phải cách lưu dữ liệu phù hợp cho ứng dụng production: dữ liệu có thể bị mất khi ứng dụng khởi động lại và việc thay đổi dữ liệu đồng thời cần được xử lý cẩn thận.

4.4. Hiểu cách routing hoạt động

Hãy phân tích các attribute trong controller:

  • [ApiController]: cung cấp các hành vi dành cho API controller.
  • [Route("api/[controller]")]: định nghĩa route cơ sở. Token [controller] được thay bằng tên controller sau khi bỏ hậu tố Controller.
  • [HttpGet]: khai báo endpoint xử lý GET tại route cơ sở.
  • [HttpGet("{id:int}")]: khai báo endpoint GET có route parameter tên id và ràng buộc kiểu số nguyên.
  • [FromQuery]: lấy giá trị từ query string.
  • Ok(...): trả về HTTP 200 cùng dữ liệu.
  • NotFound(): trả về HTTP 404.

5. Route parameter và query parameter

5.1. Route parameter

Route parameter là một phần của đường dẫn URL, thường dùng để xác định một tài nguyên cụ thể.

Ví dụ:

GET /api/posts/1

Trong controller, đoạn code sau lấy ID từ URL:

[HttpGet("{id:int}")]
public IActionResult GetPost(int id)
{
    var post = Posts.FirstOrDefault(
        item => item.Id == id);

    if (post is null)
    {
        return NotFound();
    }

    return Ok(post);
}

Khi gọi /api/posts/1, ASP.NET Core bind giá trị 1 vào tham số id.

5.2. Query parameter

Query parameter nằm sau dấu ? trong URL. Nhiều tham số được phân tách bằng dấu &.

GET /api/posts?search=Next

Ở đây, search là query parameter. ASP.NET Core lấy giá trị này thông qua [FromQuery].

[HttpGet]
public IActionResult GetPosts(
    [FromQuery] string? search)
{
    // Filter posts using search.
    // Return the result.
}

Trong ứng dụng thực tế, query parameter thường dùng cho tìm kiếm, lọc, phân trang hoặc sắp xếp dữ liệu.

Ví dụ:

GET /api/posts?page=1&pageSize=10
GET /api/posts?search=Next
GET /api/posts?search=Next&page=1

Lưu ý: ví dụ controller ở trên mới xử lý search. Các tham số page và pageSize chỉ là ví dụ về URL, chưa được triển khai phân trang.

6. Hiểu JSON response

Khi API xử lý thành công và gọi return Ok(result), ASP.NET Core sẽ serialize dữ liệu thành JSON và trả về client.

Ví dụ response của GET /api/posts:

[
  {
    "id": 1,
    "title": "Learn Next.js",
    "description": "Introduction to Next.js"
  },
  {
    "id": 2,
    "title": "Learn ASP.NET Core",
    "description": "Build a REST API"
  },
  {
    "id": 3,
    "title": "Learn Umbraco",
    "description": "Introduction to Headless CMS"
  }
]

Response của GET /api/posts/1 có dạng một object:

{
  "id": 1,
  "title": "Learn Next.js",
  "description": "Introduction to Next.js"
}

Nếu gọi GET /api/posts/999, API trả về HTTP 404 khi không tìm thấy bài viết.

JSON là định dạng dữ liệu, không phải giao diện. Backend trả về dữ liệu; frontend quyết định cách hiển thị dữ liệu đó.

7. Chạy và kiểm tra API

Khởi động project ASP.NET Core:

dotnet run

Hãy xem địa chỉ HTTP và HTTPS mà ứng dụng in ra trong terminal, hoặc xem file Properties/launchSettings.json. Địa chỉ có thể khác nhau giữa các máy.

Nếu project có cấu hình Swagger, hãy mở trang Swagger theo URL được hiển thị hoặc cấu hình trong project. Bạn cũng có thể gọi các endpoint trực tiếp bằng trình duyệt hoặc công cụ gửi HTTP request.

Thử lần lượt:

  • GET /api/posts
  • GET /api/posts?search=Next
  • GET /api/posts/1
  • GET /api/posts/999

Nếu kết quả trả về đúng, backend đã sẵn sàng để Next.js lấy dữ liệu. Hãy ghi lại base URL thực tế của API để sử dụng ở phần tiếp theo.

8. Kết nối Next.js 16 với ASP.NET Core API

8.1. Khai báo kiểu dữ liệu TypeScript

Trong Next.js, tạo hoặc mở file app/posts/page.tsx. Nếu project dùng thư mục src, đường dẫn sẽ là src/app/posts/page.tsx.

Trước tiên, khai báo kiểu dữ liệu mà API trả về:

type Post = {
  id: number;
  title: string;
  description: string;
};

Kiểu Post giúp TypeScript kiểm tra cách sử dụng dữ liệu trong code. Tuy nhiên, khai báo type không tự xác thực JSON ở runtime. Nếu API trả về dữ liệu không đúng cấu trúc, ứng dụng vẫn cần có cơ chế kiểm tra dữ liệu khi yêu cầu đó quan trọng.

8.2. Viết hàm gọi API

async function getPosts(): Promise<Post[]> {
  const response = await fetch(
    "https://localhost:7001/api/posts"
  );

  if (!response.ok) {
    throw new Error("Failed to fetch posts");
  }

  return response.json();
}

Trong ví dụ này, https://localhost:7001 chỉ là địa chỉ minh họa. Hãy thay bằng base URL thực tế của ASP.NET Core API.

Phân tích từng phần:

  • async: cho phép sử dụng await bên trong hàm.
  • fetch(): gửi HTTP request đến API.
  • await: chờ Promise hoàn thành.
  • response.ok: kiểm tra status code có nằm trong nhóm 200–299 hay không.
  • response.json(): đọc response body và parse JSON.
  • Promise<Post[]>: mô tả kiểu dữ liệu mà hàm dự kiến trả về.

Tại sao phải kiểm tra response.ok? Vì fetch không tự throw error chỉ vì server trả về HTTP 404 hoặc 500. Nếu không kiểm tra status, code có thể tiếp tục đọc response như thể request thành công.

8.3. Hiển thị danh sách bài viết

Tiếp theo, sử dụng hàm getPosts trong Server Component:

type Post = {
  id: number;
  title: string;
  description: string;
};

async function getPosts(): Promise<Post[]> {
  const response = await fetch(
    "https://localhost:7001/api/posts"
  );

  if (!response.ok) {
    throw new Error("Failed to fetch posts");
  }

  return response.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <main>
      <h1>Danh sách bài viết</h1>

      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.description}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

Trong đoạn code trên, PostsPage là Server Component. Hàm getPosts chạy ở phía server của Next.js, lấy dữ liệu từ ASP.NET Core và dùng dữ liệu đó để tạo giao diện.

Không cần thêm "use client" chỉ để gọi fetch trong Server Component. Bạn chỉ cần Client Component khi có nhu cầu tương tác phía trình duyệt, chẳng hạn sử dụng useState hoặc xử lý sự kiện onClick.

9. Hiểu luồng dữ liệu giữa hai ứng dụng

Sơ đồ dưới đây tóm tắt quá trình khi người dùng truy cập trang /posts.

flowchart TD
    A["Browser: truy cập /posts"] --> B["Next.js Server Component"]
    B --> C["fetch GET /api/posts"]
    C --> D["ASP.NET Core PostsController"]
    D --> E["Xử lý dữ liệu và serialize JSON"]
    E --> F["HTTP response: 200 OK"]
    F --> G["Next.js parse JSON"]
    G --> H["Render danh sách bài viết"]
    H --> I["Browser hiển thị HTML"]

Điểm cần nhớ: trong ví dụ này, Next.js Server Component gọi API từ server. Vì vậy, request fetch không nhất thiết xuất hiện trong tab Network của trình duyệt như một request trực tiếp từ JavaScript phía client. Hãy kiểm tra terminal của Next.js và log phía ASP.NET Core khi debug luồng server-to-server.

10. Xử lý lỗi khi fetch dữ liệu

Đoạn code hiện tại throw Error khi response không thành công. Đây là cách tối thiểu để không tiếp tục xử lý một response lỗi như dữ liệu thành công.

Trong Next.js App Router, bạn có thể thêm file app/posts/error.tsx để cung cấp giao diện lỗi cho route này. File error boundary phải là Client Component.

"use client";

export default function PostsError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <main>
      <h1>Không thể tải danh sách bài viết</h1>
      <p>Đã xảy ra lỗi khi tải dữ liệu.</p>
      <button onClick={() => reset()}>
        Thử lại
      </button>
    </main>
  );
}

Không nên hiển thị thông tin lỗi nội bộ nhạy cảm trực tiếp cho người dùng. Trong ví dụ trên, giao diện chỉ đưa ra thông báo chung.

Bạn cũng có thể thêm file app/posts/loading.tsx để Next.js hiển thị giao diện chờ trong lúc route đang tải.

Lưu ý: error.tsx và loading.tsx có phạm vi hoạt động theo route segment. Chúng không thay thế việc xử lý đầy đủ mọi loại lỗi nghiệp vụ hoặc mọi trường hợp API trả về dữ liệu không hợp lệ.

11. Những vấn đề thường gặp khi kết nối API

11.1. Sai URL hoặc port

Nếu Next.js gọi nhầm port, request có thể thất bại vì không kết nối được đến backend. Hãy xác nhận URL thực tế từ terminal hoặc launchSettings.json.

11.2. Lỗi HTTPS certificate khi chạy local

Nếu ASP.NET Core sử dụng HTTPS với chứng chỉ phát triển chưa được tin cậy, Node.js hoặc Next.js có thể từ chối kết nối. Hãy cấu hình và tin cậy development certificate đúng cách. Không nên tắt kiểm tra chứng chỉ TLS để làm cách khắc phục lâu dài.

11.3. Lỗi CORS

CORS chủ yếu ảnh hưởng đến request được thực hiện trực tiếp từ trình duyệt. Trong ví dụ Server Component gọi API từ Next.js server, CORS của trình duyệt không phải nguyên nhân thông thường gây lỗi cho request đó.

Nếu chuyển việc gọi API sang Client Component, bạn cần xem xét chính sách CORS của ASP.NET Core và cho phép origin frontend phù hợp.

11.4. API trả về lỗi nhưng fetch không tự throw

Hãy kiểm tra response.ok trước khi đọc JSON. Fetch có thể hoàn thành Promise bình thường ngay cả khi status là 404 hoặc 500.

11.5. Không thấy request trong Browser Network

Nếu request chạy trong Server Component, hãy xem terminal của Next.js. Browser Network chủ yếu phản ánh request mà trình duyệt thực hiện. Đây là khác biệt quan trọng giữa server-side fetching và client-side fetching.

12. Debug ứng dụng

Khi dữ liệu không hiển thị, nên kiểm tra theo thứ tự:

  1. Gọi API trực tiếp bằng Swagger hoặc công cụ HTTP để xác nhận backend hoạt động.
  2. Kiểm tra URL và port trong hàm fetch của Next.js.
  3. Đặt breakpoint hoặc log trong PostsController để xác nhận API có nhận request.
  4. Kiểm tra terminal của Next.js để tìm lỗi kết nối, HTTPS hoặc lỗi render.
  5. Kiểm tra response status và JSON structure.
  6. Kiểm tra TypeScript type và cách render dữ liệu trong JSX.

Có thể dùng console.log() trong Server Component để kiểm tra dữ liệu. Log này xuất hiện trong terminal của Next.js, không phải Browser Console.

13. POST — gửi dữ liệu lên backend (giới thiệu)

GET dùng để lấy dữ liệu. Khi muốn tạo một bài viết mới, client thường gửi POST request với JSON trong request body.

Ví dụ HTTP request:

POST /api/posts
Content-Type: application/json

{
  "title": "Learn Next.js",
  "description": "My first post"
}

Một endpoint POST thường thực hiện các bước:

  1. Nhận dữ liệu từ request body.
  2. Validate dữ liệu đầu vào.
  3. Tạo tài nguyên mới.
  4. Trả về HTTP 201 Created nếu tạo thành công.

Trong bài này, POST chỉ là phần giới thiệu. Để hoàn thiện chức năng tạo bài viết, cần bổ sung request model, validation, endpoint POST và form gửi dữ liệu từ frontend. Mình sẽ dành bài thực hành riêng cho phần đó.

14. Bài tập thực hành

Bài 1 — Lọc bài viết

  • Gọi /api/posts?search=Next.
  • Thử các từ khóa khác nhau.
  • Kiểm tra kết quả khi không có bài viết phù hợp.

Bài 2 — Tạo trang chi tiết

  • Tạo route Next.js /posts/[id].
  • Đọc ID từ URL.
  • Gọi API /api/posts/{id}.
  • Hiển thị tiêu đề và mô tả của bài viết.
  • Xử lý trường hợp API trả về 404.

Bài 3 — Tìm hiểu POST

  • Tạo request model để nhận tiêu đề và mô tả.
  • Viết endpoint POST trong ASP.NET Core.
  • Thử gửi JSON bằng Swagger hoặc công cụ HTTP.
  • Kiểm tra status code và response khi tạo thành công.

15. Câu hỏi tự kiểm tra

  1. Khác biệt giữa /posts và /api/posts là gì?
  2. Route parameter khác query parameter như thế nào?
  3. Tại sao phải kiểm tra response.ok sau khi gọi fetch?
  4. Vai trò của response.json() là gì?
  5. Tại sao TypeScript type không tự xác thực dữ liệu JSON lúc runtime?
  6. Vì sao fetch trong Server Component thường không xuất hiện như request trực tiếp của ứng dụng trong Browser Network?
  7. Khi nào cần dùng Client Component thay vì Server Component?
  8. HTTP 200, 201 và 404 có ý nghĩa gì?

16. Tổng kết

Qua Day 2, mình đã kết nối được Next.js 16 với ASP.NET Core Web API và hiểu các bước chính của luồng dữ liệu:

  • ASP.NET Core định nghĩa endpoint bằng routing và HTTP attributes.
  • Route parameter xác định tài nguyên; query parameter hỗ trợ tìm kiếm và lọc.
  • API trả về status code và JSON response.
  • Next.js sử dụng fetch() để lấy dữ liệu từ backend.
  • TypeScript mô tả cấu trúc dữ liệu và JSX hiển thị dữ liệu lên giao diện.
  • Server Component cho phép fetch dữ liệu từ phía server mà không cần chuyển trang thành Client Component.

Trong ứng dụng thực tế, backend có thể sử dụng database thay vì danh sách dữ liệu giả lập. Phần fetch cũng cần được thiết kế cùng với chiến lược cache, revalidation, bảo mật và xử lý lỗi phù hợp.

Bước tiếp theo là xây dựng trang chi tiết bằng dynamic route /posts/[id], sau đó tìm hiểu cách lấy nội dung từ một Headless CMS như Umbraco.

Tài liệu tham khảo

Nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.