API Demo – ProductController

Page này trình bày kiến thức nền về ASP.NET Core Web API, đồng thời dùng ProductController làm ví dụ thực tế để phân tích các endpoint từ GET, POST, PUT đến DELETE.


1️⃣ Tổng quan về ASP.NET Core Web API

ASP.NET Core Web API là framework dùng để xây dựng các dịch vụ HTTP cho phép client (Blazor, Angular, Mobile, Postman...) giao tiếp với server thông qua các request như GET, POST, PUT, DELETE.

Web API hoạt động theo mô hình RESTful, trong đó mỗi endpoint đại diện cho một tài nguyên (resource), ví dụ như Product, User, Order.

Trong ASP.NET Core, Web API được xây dựng thông qua Controller. Mỗi controller sẽ chứa các method tương ứng với từng HTTP method.

  • GET → lấy dữ liệu
  • POST → tạo mới dữ liệu
  • PUT → cập nhật dữ liệu
  • DELETE → xóa dữ liệu

Một API endpoint thường có cấu trúc:


https://domain/api/{controller}/{action}

Ví dụ:


GET https://localhost:5001/api/product/getproducts

Client sẽ gửi request đến API, server xử lý và trả về dữ liệu dưới dạng JSON cùng với HTTP Status Code (200, 404, 401, 500...).

Xem code phần khai báo controller
Controller Header

using Microsoft.AspNetCore.Mvc;

namespace WebAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
    }
}
Ý nghĩa
  • [Route("api/[controller]")] → route gốc sẽ là /api/product.
  • [ApiController] → hỗ trợ tự động binding, validation, response lỗi chuẩn hơn cho API.
  • ControllerBase → lớp nền cho Web API controller, không cần view như MVC Controller.


2️⃣ Mã nguồn và các phần liên quan

Để một Web API hoạt động, ngoài controller ra còn có các phần liên quan như:

  • Program.cs để đăng ký service và cấu hình pipeline
  • AddControllers() để bật hỗ trợ API Controller
  • MapControllers() để ánh xạ route đến controller
  • Swagger để test API trực tiếp trên trình duyệt
  • Model để định nghĩa dữ liệu trao đổi
  • Controller để xử lý request và trả response

Trong ví dụ này, phần trung tâm là ProductController, nhưng để controller hoạt động đúng thì cần có phần đăng ký service và cấu hình route trong Program.cs.

Xem code Program.cs
Program.cs

var builder = WebApplication.CreateBuilder(args);

// Đăng ký service cho Web API
builder.Services.AddControllers();

// Đăng ký Swagger để test API
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Bật Swagger trong môi trường development
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Map các controller API
app.MapControllers();

app.Run();
Giải thích phần DI và cấu hình
  • builder.Services.AddControllers(): đăng ký service cần thiết để dùng API Controller.
  • builder.Services.AddEndpointsApiExplorer(): hỗ trợ khám phá endpoint cho Swagger.
  • builder.Services.AddSwaggerGen(): tạo tài liệu Swagger/OpenAPI cho API.
  • app.MapControllers(): ánh xạ các request đến đúng controller/action tương ứng.
  • Nếu sau này có service riêng như IProductService, IRepository hoặc DbContext thì cũng sẽ đăng ký trong phần builder.Services.
Xem code model Product

public class Product
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public double Price { get; set; }
    public string? Description { get; set; }
}
Xem code ProductController

using Microsoft.AspNetCore.Mvc;

namespace WebAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        public static List<Product> products = new List<Product>()
        {
            new Product() { Id = 1, Name = "Product A", Price = 10.0, Description = "Description for Product A" },
            new Product() { Id = 2, Name = "Product B", Price = 20.0, Description = "Description for Product B" },
            new Product() { Id = 3, Name = "Product C", Price = 30.0, Description = "Description for Product C" }
        };

        [HttpGet("getproducts")]
        public async Task<IActionResult> GetProducts()
        {
            return Ok(products);
        }

        [HttpGet("getproduct-id/{id}")]
        public async Task<IActionResult> GetProduct([FromRoute] int id)
        {
            var product = products.FirstOrDefault(p => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }

        [HttpGet("findproduct-formQuery")]
        public async Task<IActionResult> FindProduct([FromQuery] string name)
        {
            var matchedProducts = products
                .Where(p => p.Name != null && p.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
                .ToList();

            return Ok(matchedProducts);
        }

        [HttpGet("getproduct-token")]
        public async Task<IActionResult> GetProductWithToken([FromHeader] string token)
        {
            if (token != "valid-token")
            {
                return Unauthorized("Invalid token");
            }

            return Ok(products);
        }

        [HttpPost("addproduct")]
        public async Task<IActionResult> AddProduct([FromBody] Product newProduct)
        {
            products.Add(newProduct);
            return Created(nameof(AddProduct), newProduct);
        }

        [HttpPost("addproduct-token")]
        public async Task<IActionResult> AddProductWithToken([FromHeader] string token, [FromBody] Product newProduct)
        {
            if (token != "valid-token")
            {
                return Unauthorized("Invalid token");
            }

            products.Add(newProduct);
            return Created(nameof(AddProduct), newProduct);
        }

        [HttpPut("updateproduct/{id}")]
        public async Task<IActionResult> UpdateProduct([FromRoute] int id, [FromBody] Product updatedProduct)
        {
            var existingProduct = products.FirstOrDefault(p => p.Id == id);
            if (existingProduct == null)
            {
                return NotFound();
            }

            existingProduct.Name = updatedProduct.Name;
            existingProduct.Price = updatedProduct.Price;
            existingProduct.Description = updatedProduct.Description;

            return NoContent();
        }

        [HttpDelete("deleteproduct/{id}")]
        public async Task<IActionResult> DeleteProduct([FromRoute] int id)
        {
            var product = products.FirstOrDefault(p => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }

            products.Remove(product);
            return NoContent();
        }
    }
}

3️⃣ GET /api/product/getproducts

Mục đích: Lấy toàn bộ danh sách sản phẩm hiện có.

HTTP Method: GET

Endpoint: /api/product/getproducts

Dữ liệu đầu vào: Không có.

Endpoint này trả về toàn bộ danh sách products bằng Ok(products), tương ứng với status code 200 OK.

Xem code endpoint
C#

[HttpGet("getproducts")]
public async Task<IActionResult> GetProducts()
{
    return Ok(products);
}
Ví dụ gọi

GET /api/product/getproducts
Response mẫu

[
  {
    "id": 1,
    "name": "Product A",
    "price": 10.0,
    "description": "Description for Product A"
  },
  {
    "id": 2,
    "name": "Product B",
    "price": 20.0,
    "description": "Description for Product B"
  }
]

4️⃣ GET /api/product/getproduct-id/{id}

Mục đích: Lấy chi tiết một sản phẩm theo id.

HTTP Method: GET

Endpoint: /api/product/getproduct-id/{id}

Kiểu nhận dữ liệu: [FromRoute]

Ví dụ: /api/product/getproduct-id/2

API sẽ tìm sản phẩm có Id tương ứng. Nếu không tìm thấy thì trả về 404 NotFound, ngược lại trả về 200 OK.

Xem code endpoint
C#

[HttpGet("getproduct-id/{id}")]
public async Task<IActionResult> GetProduct([FromRoute] int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product);
}
Ví dụ gọi

GET /api/product/getproduct-id/1
Giải thích
  • [FromRoute] lấy giá trị id từ URL.
  • FirstOrDefault tìm phần tử đầu tiên khớp điều kiện.
  • Nếu không có sản phẩm phù hợp thì trả về NotFound().

5️⃣ GET /api/product/findproduct-formQuery?name=...

Mục đích: Tìm sản phẩm theo tên.

HTTP Method: GET

Endpoint: /api/product/findproduct-formQuery?name=Product

Kiểu nhận dữ liệu: [FromQuery]

API sẽ lấy giá trị name từ query string và lọc ra các sản phẩm có tên chứa chuỗi đó, không phân biệt hoa thường.

Xem code endpoint
C#

[HttpGet("findproduct-formQuery")]
public async Task<IActionResult> FindProduct([FromQuery] string name)
{
    var matchedProducts = products
        .Where(p => p.Name != null && p.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
        .ToList();

    return Ok(matchedProducts);
}
Ví dụ gọi

GET /api/product/findproduct-formQuery?name=A
Giải thích
  • [FromQuery] lấy dữ liệu sau dấu ? trong URL.
  • Dùng Contains(..., StringComparison.OrdinalIgnoreCase) để tìm kiếm không phân biệt hoa thường.
  • Kết quả luôn là một danh sách, kể cả khi chỉ có 1 phần tử hoặc rỗng.

6️⃣ GET /api/product/getproduct-token

Mục đích: Lấy danh sách sản phẩm nhưng yêu cầu token trong header.

HTTP Method: GET

Endpoint: /api/product/getproduct-token

Kiểu nhận dữ liệu: [FromHeader]

Header yêu cầu: token: valid-token

Đây là ví dụ đơn giản để minh họa cách lấy dữ liệu từ request header. Nếu token không đúng, API trả về 401 Unauthorized.

Xem code endpoint
C#

[HttpGet("getproduct-token")]
public async Task<IActionResult> GetProductWithToken([FromHeader] string token)
{
    if (token != "valid-token")
    {
        return Unauthorized("Invalid token");
    }

    return Ok(products);
}
Ví dụ gọi

GET /api/product/getproduct-token
token: valid-token
Giải thích
  • [FromHeader] lấy dữ liệu từ HTTP header.
  • Đây chỉ là demo token thủ công, chưa phải authentication thật.
  • Nếu token hợp lệ → trả dữ liệu, nếu sai → trả lỗi xác thực.

7️⃣ POST /api/product/addproduct

Mục đích: Thêm một sản phẩm mới vào danh sách.

HTTP Method: POST

Endpoint: /api/product/addproduct

Kiểu nhận dữ liệu: [FromBody]

API nhận một object Product từ request body, thêm vào danh sách rồi trả về 201 Created.

Xem code endpoint
C#

[HttpPost("addproduct")]
public async Task<IActionResult> AddProduct([FromBody] Product newProduct)
{
    products.Add(newProduct);
    return Created(nameof(AddProduct), newProduct);
}
Body mẫu

{
  "id": 4,
  "name": "Product D",
  "price": 40.0,
  "description": "Description for Product D"
}
Giải thích
  • [FromBody] map JSON trong body thành object C#.
  • products.Add(newProduct) thêm phần tử vào danh sách.
  • Created(...) biểu thị tạo dữ liệu thành công.

8️⃣ POST /api/product/addproduct-token

Mục đích: Thêm sản phẩm mới nhưng yêu cầu token trong header.

HTTP Method: POST

Endpoint: /api/product/addproduct-token

Kiểu nhận dữ liệu: Kết hợp [FromHeader][FromBody]

Endpoint này minh họa cách vừa nhận token từ header, vừa nhận object từ body trong cùng một request.

Xem code endpoint
C#

[HttpPost("addproduct-token")]
public async Task<IActionResult> AddProductWithToken([FromHeader] string token, [FromBody] Product newProduct)
{
    if (token != "valid-token")
    {
        return Unauthorized("Invalid token");
    }

    products.Add(newProduct);
    return Created(nameof(AddProduct), newProduct);
}
Ví dụ gọi

POST /api/product/addproduct-token
token: valid-token
Content-Type: application/json

9️⃣ PUT /api/product/updateproduct/{id}

Mục đích: Cập nhật thông tin sản phẩm theo id.

HTTP Method: PUT

Endpoint: /api/product/updateproduct/{id}

Kiểu nhận dữ liệu: [FromRoute] cho id[FromBody] cho dữ liệu cập nhật.

Nếu tìm thấy sản phẩm thì cập nhật các thuộc tính và trả về 204 NoContent. Nếu không tìm thấy thì trả về 404 NotFound.

Xem code endpoint
C#

[HttpPut("updateproduct/{id}")]
public async Task<IActionResult> UpdateProduct([FromRoute] int id, [FromBody] Product updatedProduct)
{
    var existingProduct = products.FirstOrDefault(p => p.Id == id);
    if (existingProduct == null)
    {
        return NotFound();
    }

    existingProduct.Name = updatedProduct.Name;
    existingProduct.Price = updatedProduct.Price;
    existingProduct.Description = updatedProduct.Description;

    return NoContent();
}
Body mẫu

{
  "id": 2,
  "name": "Product B Updated",
  "price": 99.0,
  "description": "Updated description"
}

🔟 DELETE /api/product/deleteproduct/{id}

Mục đích: Xóa sản phẩm theo id.

HTTP Method: DELETE

Endpoint: /api/product/deleteproduct/{id}

Kiểu nhận dữ liệu: [FromRoute]

API tìm sản phẩm theo id, nếu có thì xóa khỏi danh sách và trả về 204 NoContent. Nếu không có thì trả về 404 NotFound.

Xem code endpoint
C#

[HttpDelete("deleteproduct/{id}")]
public async Task<IActionResult> DeleteProduct([FromRoute] int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);
    if (product == null)
    {
        return NotFound();
    }

    products.Remove(product);
    return NoContent();
}
Ví dụ gọi

DELETE /api/product/deleteproduct/3

1️⃣1️⃣ Mẫu mở rộng: Tách logic sang Service + Dependency Injection (DI)

Trong thực tế, bạn sẽ không muốn để toàn bộ logic xử lý trong controller. Thay vào đó, nên tách phần xử lý nghiệp vụ ra một service riêng, rồi dùng Dependency Injection (DI) để đưa service đó vào controller.

Xem Code
1. Interface (định nghĩa contract)

public interface IProductService
{
    List<Product> GetAll();
    Product? GetById(int id);
    List<Product> FindByName(string name);
    void Add(Product product);
    bool Update(int id, Product product);
    bool Delete(int id);
}

Interface định nghĩa các chức năng mà service phải có. Controller sẽ làm việc thông qua interface thay vì implementation cụ thể.


2. Implementation (logic thật)

public class ProductService : IProductService
{
    private static List<Product> products = new List<Product>()
    {
        new Product() { Id = 1, Name = "Product A", Price = 10.0, Description = "Description for Product A" },
        new Product() { Id = 2, Name = "Product B", Price = 20.0, Description = "Description for Product B" },
        new Product() { Id = 3, Name = "Product C", Price = 30.0, Description = "Description for Product C" }
    };

    public List<Product> GetAll()
    {
        return products;
    }

    public Product? GetById(int id)
    {
        return products.FirstOrDefault(p => p.Id == id);
    }

    public List<Product> FindByName(string name)
    {
        return products
            .Where(p => p.Name != null && p.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
            .ToList();
    }

    public void Add(Product product)
    {
        products.Add(product);
    }

    public bool Update(int id, Product updatedProduct)
    {
        var existing = products.FirstOrDefault(p => p.Id == id);
        if (existing == null) return false;

        existing.Name = updatedProduct.Name;
        existing.Price = updatedProduct.Price;
        existing.Description = updatedProduct.Description;

        return true;
    }

    public bool Delete(int id)
    {
        var product = products.FirstOrDefault(p => p.Id == id);
        if (product == null) return false;

        products.Remove(product);
        return true;
    }
}

Đây là nơi chứa toàn bộ logic xử lý dữ liệu (CRUD). Controller sẽ không còn thao tác trực tiếp với List nữa.


3. Đăng ký DI trong Program.cs

builder.Services.AddScoped<IProductService, ProductService>();

Khi controller cần IProductService, hệ thống sẽ tự động tạo ProductService và inject vào.


4. Inject & sử dụng trong Controller

using Microsoft.AspNetCore.Mvc;

namespace WebAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private readonly IProductService _productService;

        public ProductController(IProductService productService)
        {
            _productService = productService;
        }

        [HttpGet("getproducts")]
        public IActionResult GetProducts()
        {
            return Ok(_productService.GetAll());
        }

        [HttpGet("getproduct-id/{id}")]
        public IActionResult GetProduct([FromRoute] int id)
        {
            var product = _productService.GetById(id);
            if (product == null)
            {
                return NotFound();
            }

            return Ok(product);
        }

        [HttpGet("findproduct-formQuery")]
        public IActionResult FindProduct([FromQuery] string name)
        {
            var matchedProducts = _productService.FindByName(name);
            return Ok(matchedProducts);
        }

        [HttpPost("addproduct")]
        public IActionResult AddProduct([FromBody] Product newProduct)
        {
            _productService.Add(newProduct);
            return Created(nameof(AddProduct), newProduct);
        }

        [HttpPut("updateproduct/{id}")]
        public IActionResult UpdateProduct([FromRoute] int id, [FromBody] Product updatedProduct)
        {
            var updated = _productService.Update(id, updatedProduct);
            if (!updated)
            {
                return NotFound();
            }

            return NoContent();
        }

        [HttpDelete("deleteproduct/{id}")]
        public IActionResult DeleteProduct([FromRoute] int id)
        {
            var deleted = _productService.Delete(id);
            if (!deleted)
            {
                return NotFound();
            }

            return NoContent();
        }
    }
}

Controller chỉ nhận request, gọi service để xử lý logic, rồi trả response phù hợp về cho client. Đây là cách tổ chức code sạch hơn so với việc viết toàn bộ CRUD trực tiếp trong controller.


5. Flow hoạt động

Request → Controller → IProductService → ProductService → Data → Response
  • Controller không cần biết chi tiết logic bên trong service.
  • DI container sẽ tự inject ProductService khi controller cần IProductService.
  • Logic xử lý được tách riêng, giúp dễ bảo trì, dễ test và dễ nâng cấp.


1️⃣2️⃣ Tổng kết

  • ASP.NET Core Web API dùng Controller để xử lý HTTP request.
  • API hỗ trợ nhiều cách nhận dữ liệu: FromRoute, FromQuery, FromHeader, FromBody.
  • Controller nên trả về dữ liệu dạng JSON cùng với HTTP Status Code phù hợp.
  • Trong thực tế, nên tách business logic sang Service và inject bằng Dependency Injection (DI).
  • API thường được thiết kế theo chuẩn RESTful để dễ hiểu, dễ mở rộng và dễ tích hợp.

Các HTTP Status Code phổ biến trong RESTful API:

  • 200 OK → Request thành công, trả về dữ liệu.
  • 201 Created → Tạo dữ liệu thành công (POST).
  • 204 No Content → Thành công nhưng không trả dữ liệu (PUT/DELETE).
  • 400 Bad Request → Request không hợp lệ.
  • 401 Unauthorized → Token sai hoặc chưa xác thực.
  • 403 Forbidden → Không có quyền truy cập.
  • 404 Not Found → Không tìm thấy tài nguyên.
  • 500 Internal Server Error → Lỗi phía server.
  • 409 Conflict → Dữ liệu bị trùng hoặc xung đột.
  • 422 Unprocessable Entity → Dữ liệu hợp lệ về format nhưng sai logic.
  • 405 Method Not Allowed → Gọi sai HTTP method.

Mapping Status Code theo từng API trong ProductController:

API Method Success Error Mô tả
/getproducts GET 200 OK - Lấy toàn bộ danh sách sản phẩm
/getproduct-id/{id} GET 200 OK 404 NotFound Lấy sản phẩm theo id
/findproduct-formQuery GET 200 OK - Tìm theo tên (query)
/getproduct-token GET 200 OK 401 Unauthorized Yêu cầu token
/addproduct POST 201 Created 400 BadRequest Thêm sản phẩm
/addproduct-token POST 201 Created 401 Unauthorized Thêm có token
/updateproduct/{id} PUT 204 NoContent 404 NotFound Cập nhật sản phẩm
/deleteproduct/{id} DELETE 204 NoContent 404 NotFound Xóa sản phẩm

Việc sử dụng đúng HTTP Status Code giúp API rõ ràng hơn, dễ debug và giúp frontend/mobile xử lý response chính xác hơn.


An error has occurred. This application may no longer respond until reloaded. Reload 🗙
Web hosting by Somee.com