SSD .NET Controller — มาตรฐานการเขียน API Controller
บริบท
Controller ของ SSD เป็น ASP.NET Core Controller ที่ทำหน้าที่รับ HTTP request และส่งต่อไปยัง Service layer ทุก method ต้องคืนค่าเป็น ServiceResponse ผ่าน ResponseResult และต้องมี permission check เมื่อจำเป็น
กฎหลัก
- Route ต้องขึ้นต้นด้วย
api/ ตามด้วยชื่อ controller ตัวพิมพ์เล็ก ใช้ - แทน space
- ทุก HTTP method attribute ต้องกำหนด
Name ด้วยเสมอ
- ต้อง return ด้วย
ResponseResult และ ServiceResponse ทุกครั้ง (ยกเว้นกรณีที่ return ไม่ใช่ JSON)
- ต้องใส่ XML comment สำหรับทุก method ถ้าเป็นไปได้
- Method ที่อ่านข้อมูล — Log ระดับ Debug อย่างน้อย 1 ครั้ง
- Method ที่เปลี่ยนแปลงข้อมูล — Log ระดับ Information และเก็บ body ทั้งหมด
- ใช้
ClaimPermission attribute เมื่อต้องการจำกัดสิทธิ์
โครงสร้าง Controller มาตรฐาน
namespace MyProject.Controllers
{
// Route ขึ้นต้นด้วย api/ ตามด้วยชื่อ controller ตัวเล็ก ใช้ - แทน space
[Route("api/call-result")]
[ApiController]
[Authorize] // ต้อง login ก่อนเข้าถึง controller นี้ทั้งหมด
public class CallResultController : ControllerBase
{
private readonly ICallResultService _service;
private readonly Serilog.ILogger _logger;
private const string _controllerName = nameof(CallResultController);
// Inject service และ logger ผ่าน constructor
public CallResultController(
ICallResultService service,
Serilog.ILogger? logger = null)
{
_service = service;
// logger optional เพื่อให้ทำ unit test ได้ง่าย
_logger = logger is null
? Serilog.Log.ForContext("ControllerName", _controllerName)
: logger.ForContext("ControllerName", _controllerName);
}
/// <summary>
/// Get all call results
/// </summary>
/// <param name="paginationDto">Pagination parameters</param>
/// <param name="filter">Filter criteria</param>
/// <remarks>
/// Agent: สามารถดูได้เฉพาะของตัวเอง
/// Team Lead: สามารถดูของสมาชิกทีม
/// Manager/Admin: สามารถดูทั้งหมด
/// </remarks>
[HttpGet(Name = "CallResultGetAll")] // ต้องมี Name ทุกครั้ง
public async Task<ServiceResponse<List<CallResultTableDto>>?> GetAll(
[FromQuery] PaginationDto paginationDto,
[FromQuery] CallResultFilterDto filter,
CancellationToken cancellationToken = default)
{
_logger.Debug("[{ControllerName}] GetAll", _controllerName);
var response = await _service.GetAllAsync(paginationDto, filter, cancellationToken);
var pagination = await _service.GetPaginationAsync(paginationDto, filter, cancellationToken);
return ResponseResult.Success(response, pagination, "Get all call result success");
}
[HttpGet("{callResultId}", Name = "CallResultGetById")]
public async Task<ServiceResponse<CallResultResponseDto?>> GetById(
int callResultId,
CancellationToken cancellationToken = default)
{
_logger.Debug("[{ControllerName}] GetById", _controllerName);
var response = await _service.GetByIdAsync(callResultId, cancellationToken);
if (response is null)
return ResponseResult.NotFound<CallResultResponseDto>(
$"Call result id {callResultId} not found");
return ResponseResult.Success(response, $"Get call result by id {callResultId} success");
}
// ใช้ ClaimPermission เมื่อต้องการจำกัดสิทธิ์เฉพาะ method
[HttpPost(Name = "CallResultCreate")]
[ClaimPermission(Permission.Admin, Permission.Manager)]
public async Task<ServiceResponse<CallResultResponseDto>> Create(
[FromBody] CallResultRequestDto request,
CancellationToken cancellationToken = default)
{
_logger.Debug("[{ControllerName}] Create", _controllerName);
var response = await _service.CreateAsync(request, cancellationToken);
// Log Information เมื่อมีการเปลี่ยนแปลงข้อมูล
_logger.Information(
"[{ControllerName}] Create success, id {CallResultId}",
_controllerName, response.CallResultId);
// Log Debug สำหรับ response body (ไม่จำเป็นต้องทำทุก method)
_logger.Debug(
"[{ControllerName}] Create response {@Response}",
_controllerName, response);
return ResponseResult.Success(response, "Create call result success");
}
[HttpPut("{callResultId}", Name = "CallResultUpdate")]
[ClaimPermission(Permission.Admin, Permission.Manager)]
public async Task<ServiceResponse<CallResultResponseDto?>> Update(
int callResultId,
[FromBody] CallResultRequestDto request,
CancellationToken cancellationToken = default)
{
_logger.Debug("[{ControllerName}] Update [{CallResultId}]", _controllerName, callResultId);
var response = await _service.UpdateAsync(callResultId, request, cancellationToken);
if (response is null)
return ResponseResult.NotFound<CallResultResponseDto>(
$"Call result id {callResultId} not found");
_logger.Information(
"[{ControllerName}] Update success [{CallResultId}]",
_controllerName, callResultId);
return ResponseResult.Success(response, $"Update call result {callResultId} success");
}
[HttpDelete("{callResultId}", Name = "CallResultDelete")]
[ClaimPermission(Permission.Admin)]
public async Task<ServiceResponse<CallResultResponseDto?>> Delete(
int callResultId,
CancellationToken cancellationToken = default)
{
_logger.Debug("[{ControllerName}] Delete [{CallResultId}]", _controllerName, callResultId);
var response = await _service.DeleteAsync(callResultId, cancellationToken);
if (response is null)
return ResponseResult.NotFound<CallResultResponseDto>(
$"Call result id {callResultId} not found");
_logger.Information(
"[{ControllerName}] Delete success [{CallResultId}]",
_controllerName, callResultId);
return ResponseResult.Success(response, $"Delete call result {callResultId} success");
}
}
}
Authorization ด้วย Permission
ตั้งค่า Permission.cs
public class Permission
{
public const string Admin = "call_result_api:admin";
public const string Manager = "call_result_api:manager";
public const string Agent = "call_result_api:agent";
public static AuthorizationPolicy AdminPermission =
new AuthorizationPolicyBuilder()
.RequireClaim("permission", Admin)
.Build();
}
ขั้นตอนการตั้งค่า Permission
- ปรึกษา Dev Lead เพื่อสร้าง Google Sheet สำหรับ API Registration
- Copy ข้อมูลจาก Sheet ไปยัง
appsettings.json และ Permission.cs
Route Naming Convention
| รูปแบบ |
ตัวอย่าง |
| Controller route |
api/call-result |
| Get all |
[HttpGet(Name = "CallResultGetAll")] |
| Get by id |
[HttpGet("{id}", Name = "CallResultGetById")] |
| Create |
[HttpPost(Name = "CallResultCreate")] |
| Update |
[HttpPut("{id}", Name = "CallResultUpdate")] |
| Delete |
[HttpDelete("{id}", Name = "CallResultDelete")] |
Logging Guidelines ใน Controller
| สถานการณ์ |
Log Level |
| เข้า method ทุกครั้ง |
Debug |
| อ่านข้อมูล (GET) |
Debug |
| เปลี่ยนแปลงข้อมูล (POST/PUT/DELETE) — สำเร็จ |
Information |
| Log body ของ request/response |
Debug |
| ข้อมูลส่วนบุคคล |
ห้าม log ทุกกรณี |