Error Handling
Relevant source files
The following files were used as context for generating this wiki page:Introduction
Error handling in the WEBAPI project is implemented on a per-endpoint basis within the controller layer. The primary strategy involves returning specific HTTP status codes to the client to indicate the outcome of a request. Controllers are responsible for validating input, calling service-layer logic, and translating the results—or failures—into appropriate IActionResult responses like Ok, BadRequest, NotFound, or StatusCode(500).
The service layer assists this process by using return types like bool or nullable objects to signal success or failure to the calling controller. For operations that can fail unexpectedly, such as database interactions or external API calls (e.g., sending an email), try-catch blocks are used to handle exceptions gracefully. There is no global exception handling middleware configured in Program.cs, meaning exceptions not caught at the service or controller level will result in a default server error.
Sources: Controllers/Login/UserController.cs, Service/user/UserService.cs, Program.cs
Controller-Level Error Handling
Controllers are the first line of defense and the primary location for handling errors. They interpret service layer responses and client requests to generate meaningful HTTP responses.
Input and State Validation
Before processing a request, controllers perform validation checks.
- Model State Validation: Endpoints like
CreateOrdercheckModelState.IsValidto ensure the incoming request body conforms to the DTO's validation attributes. If invalid, a400 Bad Requestis returned with the model state errors.
Sources: Controllers/Order/OrderController.cs:28-31 - Null/Empty Checks: Simple checks are performed for required parameters. For example, the
Loginendpoint verifies thatUserNameandPasswordare not empty.
Sources: Controllers/Login/UserController.cs:100-103 - Business Logic Validation: Controllers query services to validate business rules. For instance, the
Registerendpoint checks if a username or email already exists before attempting to create a new user, returning a400 Bad Requestif they do.
Sources: Controllers/Login/UserController.cs:68-79
The following diagram shows the validation flow for a new user registration.
HTTP Status Code Responses
Controllers use various IActionResult types to communicate request outcomes.
| Status Code | IActionResult Method | Description & Example | Source File |
|---|---|---|---|
| 400 Bad Request | BadRequest(object error) | Used for model validation failures, missing required fields, or business rule violations like a password mismatch. | Controllers/Login/UserController.cs:113-116 |
| 404 Not Found | NotFound(object value) | Returned when a specific resource (e.g., a user, product, or order) cannot be found by its ID. | Controllers/LoadProduct/ProductController.cs:30-32 |
| 500 Internal Server Error | StatusCode(500, object value) | Used in try-catch blocks to handle unexpected exceptions, such as a failure to send an email during password reset. | Controllers/Login/UserController.cs:146-149 |
General Error Flow in Controllers
This diagram illustrates the typical decision-making process within a controller endpoint when handling a request.
Service-Level Error Handling
The service layer signals success or failure to the controllers, which then create the final HTTP response. It also contains logic for handling exceptions from external dependencies like the database.
Boolean and Null Return Types
Service methods use return types to indicate outcomes without throwing exceptions for predictable failures.
- Boolean Flags: Methods like
AddUserAsyncandUpdateUserAsyncreturntrueon success andfalseon failure. The implementation wraps the database operation in atry-catchblock. If an exception occurs, it's logged to the console andfalseis returned.
Sources: Service/user/UserService.cs:42-52, Service/user/IUserService.cs:9 - Null Objects: Methods that are supposed to return an object, like
CreateUserAsync, return the created object on success andnullif an error occurs during creation.
Sources: Service/user/UserService.cs:54-69, Service/user/IUserService.cs:15
// Example from UserService.cs showing a try-catch block with a boolean return
public async Task<bool> AddUserAsync(User newUser)
{
try
{
_userDBContext.User.Add(newUser);
await _userDBContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Error adding user: {ex.Message}");
return false;
}
}
Sources: Service/user/UserService.cs:42-52
Exception Handling with try-catch
For operations that are inherently risky or involve external systems, try-catch blocks are used to prevent the application from crashing and to allow for a controlled error response.
A prime example is the ResetPassword method in UserController, which wraps the entire logic in a try-catch block. If any part of the process fails (finding the user, validating the token, updating the database), the catch block returns a 500 Internal Server Error with the exception message.
// Example from UserController.cs showing a try-catch for a complex operation
[HttpPost("reset-password")]
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordRequest request)
{
try
{
// ... logic to find user, check token, update password ...
await _userService.UpdateUserAsync(user);
return Ok(new { message = "Mật khẩu đã được đặt lại thành công." });
}
catch (Exception ex)
{
return StatusCode(500, new { message = "Đã xảy ra lỗi trong quá trình xử lý.", error = ex.Message });
}
}
Sources: Controllers/Login/UserController.cs:152-183
Similarly, the SendResetPasswordEmailAsync method in UserService wraps the SmtpClient logic in a try-catch block, returning false if the email fails to send for any reason. The controller then translates this false return value into a 500 Internal Server Error.
Sources: Service/user/UserService.cs:113-136, Controllers/Login/UserController.cs:143-149
Summary
The project's error handling is decentralized, residing within individual controller actions. It relies on returning specific HTTP status codes based on input validation and the success or failure signals (booleans, nulls) from the service layer. While this approach is effective for handling expected errors like invalid data or missing resources, the use of try-catch blocks for more complex or external operations provides a necessary safety net. The lack of a global exception handler means developers must be diligent in catching potential exceptions in service and controller methods to avoid unhandled application crashes.