Action Results - Returning Correct HTTP Responses
Level: Beginner to Intermediate
Action results decide what the API sends back to the client. After learning controllers and actions, this is the next important step for writing professional Web APIs.
- Action result purpose: Tell client whether request succeeded and what to do next (status code matters!)
IActionResult: Return any response type (View, Json, Redirect, error)ActionResult<T>: Generic version, returns data of type T or error (returns Student or BadRequest)- Status 200 OK: Request succeeded, returning data (GET student returns student)
- Status 201 Created: Resource created successfully, include Location header with new URL (POST student)
- Status 204 No Content: Request succeeded but no data to return (DELETE student, PUT student with no response body)
- Status 400 Bad Request: Client error (invalid input, missing required field like student name)
- Status 401 Unauthorized: User not logged in (missing/invalid JWT token)
- Status 403 Forbidden: User logged in but lacks permission (student trying to access admin report)
- Status 404 Not Found: Resource doesn't exist (GET /students/999 when 999 doesn't exist)
- Status 409 Conflict: Data conflict (try to create student with duplicate roll number)
- Status 500 Server Error: App error, not client's fault (database connection failed, null reference exception)
- School Management examples: Create student returns 201, Update returns 200, Delete returns 204, Missing student returns 404
- Common pattern: Always return appropriate status code + clear error message for debugging
- Problem Details: RFC 7807 format for errors (type, title, status, detail, instance)
- Common mistakes in API result handling
What is an Action Result?
An action result tells ASP.NET Core what HTTP response to send.
It can control:
- status code
- response body
- headers
- redirects
- files
- error messages
Example:
return Ok(student);
This means:
HTTP 200 OK
Response body contains student data
Why Status Codes Matter
APIs are used by browsers, mobile apps, frontend apps, and other backend systems.
Status codes help clients understand what happened.
| Status Code | Meaning |
|---|---|
| 200 | Request succeeded |
| 201 | New resource created |
| 204 | Success, no content returned |
| 400 | Client sent invalid data |
| 401 | User is not authenticated |
| 403 | User is authenticated but not allowed |
| 404 | Resource not found |
| 500 | Server error |
Good APIs do not always return 200.
Basic IActionResult Example
This one action can return:
| Condition | Result |
|---|---|
| Invalid id | 400 Bad Request |
| Student not found | 404 Not Found |
| Student found | 200 OK |
Common Result Methods
| Method | Status Code | Use When |
|---|---|---|
Ok(data) | 200 | Data was found or operation succeeded |
Created(...) | 201 | New record was created |
CreatedAtAction(...) | 201 | New record created and client should know where to fetch it |
NoContent() | 204 | Operation succeeded but no body is needed |
BadRequest(error) | 400 | Input is invalid |
Unauthorized() | 401 | User has not logged in |
Forbid() | 403 | User logged in but has no permission |
NotFound() | 404 | Record does not exist |
StatusCode(500) | 500 | Unexpected server error |
Ok
Use Ok when returning successful data.
[HttpGet]
public IActionResult GetStudents()
{
var students = _studentService.GetAllStudents();
return Ok(students);
}
Response:
200 OK
NotFound
Use NotFound when a requested resource does not exist.
[HttpGet("{id}")]
public IActionResult GetStudent(int id)
{
var student = _studentService.GetStudent(id);
if (student == null)
{
return NotFound(new { message = "Student not found." });
}
return Ok(student);
}
Response:
404 Not Found
BadRequest
Use BadRequest when the client sends invalid input.
[HttpGet("{id}")]
public IActionResult GetStudent(int id)
{
if (id <= 0)
{
return BadRequest(new { message = "Invalid student id." });
}
return Ok();
}
Response:
400 Bad Request
CreatedAtAction
When creating a new resource, return 201 Created.
[HttpPost]
public IActionResult CreateStudent(CreateStudentRequest request)
{
var student = _studentService.CreateStudent(request);
return CreatedAtAction(
nameof(GetStudent),
new { id = student.Id },
student);
}
This tells the client:
Student created successfully.
You can fetch it from the GetStudent action.
NoContent
Use NoContent when the operation succeeds but no response body is needed.
[HttpDelete("{id}")]
public IActionResult DeleteStudent(int id)
{
var deleted = _studentService.DeleteStudent(id);
if (!deleted)
{
return NotFound();
}
return NoContent();
}
Response:
204 No Content
This is common for delete and update operations.
IActionResult vs ActionResult<T>
IActionResult is flexible.
public IActionResult GetStudent(int id)
ActionResult<T> is also flexible, but tells readers the success response type.
public ActionResult<StudentDto> GetStudent(int id)
Example:
[HttpGet("{id}")]
public ActionResult<StudentDto> GetStudent(int id)
{
var student = _studentService.GetStudent(id);
if (student == null)
{
return NotFound();
}
return Ok(student);
}
For Web APIs, ActionResult<T> is often clearer.
School API Example
[ApiController]
[Route("api/admissions")]
public class AdmissionsController : ControllerBase
{
private readonly IAdmissionService _admissionService;
public AdmissionsController(IAdmissionService admissionService)
{
_admissionService = admissionService;
}
[HttpPost]
public ActionResult<AdmissionResponse> SubmitApplication(
CreateAdmissionRequest request)
{
if (request.DateOfBirth > DateTime.Today)
{
return BadRequest(new { message = "Date of birth is invalid." });
}
var response = _admissionService.Submit(request);
return CreatedAtAction(
nameof(GetApplication),
new { id = response.Id },
response);
}
[HttpGet("{id}")]
public ActionResult<AdmissionResponse> GetApplication(int id)
{
var application = _admissionService.GetApplication(id);
if (application == null)
{
return NotFound();
}
return Ok(application);
}
}
Common Mistakes
| Mistake | Better Approach |
|---|---|
Returning Ok() for every situation | Return correct status codes |
Returning null when record is missing | Return NotFound() |
Returning created data with Ok() | Use Created or CreatedAtAction |
| Throwing exceptions for validation errors | Return BadRequest or validation response |
| Returning database entities directly | Return DTOs |
Hiding all errors as 500 | Use accurate client/server status codes |
Practice Task
Create a FeesController.
GET /api/fees/{studentId}returnsOk(fees)when found.- Return
BadRequest()whenstudentId <= 0. - Return
NotFound()when no fee record exists. POST /api/fees/paymentsreturnsCreatedAtAction().DELETE /api/fees/payments/{id}returnsNoContent().
Quick Recap
| Question | Answer |
|---|---|
| Success with data? | Ok(data) |
| New resource created? | CreatedAtAction(...) |
| Invalid input? | BadRequest(...) |
| Missing record? | NotFound() |
| Success without body? | NoContent() |
Q: What are action results in ASP.NET Core?
Good Answer: "Action results are return types from controller actions that tell ASP.NET Core what HTTP response to send. They control status code, response body, headers, files, and redirects. Common results include Ok for 200, CreatedAtAction for 201, BadRequest for 400, Unauthorized for 401, Forbid for 403, NotFound for 404, and NoContent for 204. Correct action results make APIs clear and easier for clients to consume."
Use ChatGPT, Claude, or Copilot to go deeper on Action Results. Try these prompts:
"Explain IActionResult using a school API example.""When should I return Ok, CreatedAtAction, BadRequest, and NotFound?""What is the difference between IActionResult and ActionResult<T>?""Design proper responses for create, update, delete, and get APIs."
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.