# Returning ActionResult&lt;T>

ActionResult&lt;T> lets an action return either its data or a status code result, such as NotFound(). Return the item to answer 200 with it, or a helper such as NotFound(), BadRequest() or Conflict() to answer with that status instead.

Journeyman · ASP.NET Core · REST API · Updated November 3, 2026

## After this lesson you can

- Return ActionResult&lt;T> from an action, choosing between the item and a status such as NotFound()
- Choose the ControllerBase helper for a status code, such as Ok, CreatedAtAction, NoContent, BadRequest, NotFound or Conflict

## Key takeaways

- `ActionResult<Category>` lets an action return either a `Category`, answered with `200 OK`, or a status result such as `NotFound()`.
- Every action that returns one item should answer `404 Not Found` when there's no such item.
- `ControllerBase` has a helper for each common answer: `Ok`, `CreatedAtAction`, `NoContent`, `BadRequest`, `NotFound` and `Conflict`, plus `StatusCode` for any other code.
- Use `ActionResult<T>` when the action sends data back, and `IActionResult` when it only ever sends a status.
- Don't wrap every action in `try` and `catch` to return `500`. An action handles only what it expects, such as a missing item.

`GetById` still has the problem from *HTTP Status Codes: an overview*: a missing category answers `204 No Content`, when it should be `404 Not Found`. The action needs to return **either** a category **or** a "not found". That's what **`ActionResult<T>`** is for.

## Either the item, or a status

```csharp
[HttpGet("{id}")]
public ActionResult<Category> GetById(int id)
{
    Category? category = categoryDao.GetById(id);
    if (category == null)
    {
        return NotFound();
    }
    return category;
}
```

`ActionResult<Category>` accepts two kinds of return:

- **A `Category`**: ASP.NET Core answers `200 OK` with it as JSON, exactly as before.
- **A status code result**, such as `NotFound()`: ASP.NET Core answers with that status.

```http
GET {{HostAddress}}/api/categories/300
```

```http
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
  "title": "Not Found",
  "status": 404
}
```

A proper `404`, with a problem details body the client can read. Every action that returns one item should work this way.

## The status helpers

`ControllerBase` has a method for each common answer:

| Helper | Status | Use it for |
|---|---|---|
| `Ok(item)` (or just `return item;`) | `200 OK` | The data the client asked for, or the item a `PUT` just updated |
| `CreatedAtAction(nameof(GetById), new { id }, item)` | `201 Created` | A new item, with its address in `Location` |
| `NoContent()` | `204 No Content` | Success with nothing to send back: a `DELETE` |
| `BadRequest("message")` | `400 Bad Request` | A request your own code finds wrong, beyond the annotations |
| `NotFound()` | `404 Not Found` | No item at that address |
| `Conflict("message")` | `409 Conflict` | The request clashes with the data, such as a name that's already taken |
| `StatusCode(503)` | Any code | Anything without its own helper |

For example, Northwind's category names should be unique. No annotation can check that, so the action does, and answers `409 Conflict`:

```csharp
[HttpPost]
public ActionResult<Category> Add(Category category)
{
    if (categoryDao.GetAll().Any(c => c.CategoryName == category.CategoryName))
    {
        return Conflict($"There's already a category named {category.CategoryName}.");
    }

    Category added = categoryDao.Add(category);
    return CreatedAtAction(nameof(GetById), new { id = added.CategoryId }, added);
}
```

## ActionResult&lt;T> or IActionResult?

You've now seen both:

| Return type | When | Example |
|---|---|---|
| `ActionResult<Category>` | The action sends data back, but sometimes a status instead | `GetById`, `Add`, `Update` |
| `IActionResult` | The action only ever sends a status | `Delete` |
| `List<Category>`, `Category` | The action always succeeds with data | `GetAll` |

Prefer `ActionResult<T>` whenever there's data: the `T` tells readers, and tools that read your API's description at `/openapi/v1.json`, exactly what a successful answer contains.

## What about 500?

You could wrap every action in a `try` and `catch (Exception)` and `return StatusCode(500)`. Don't: that's the same few lines in every action of every controller, and it's easy to forget one. Errors you don't expect are better handled **once**, for the whole API, as *Handling Global Exceptions in ASP.NET Core* shows. An action should only handle what it expects, such as a missing item.

## Check yourself

2 questions to try on the lesson page.

## So what

`ActionResult<T>` lets each action tell the whole truth: the data when there is some, and the right status when there isn't. Pick the helper that says what happened, and clients can trust your status codes.

## Key terms

- ActionResult&lt;T>
- IActionResult
- [409 Conflict](https://www.ask-codey.com/glossary/409-conflict)

## Cheat sheets

- [Returning ActionResult&lt;T>](https://www.ask-codey.com/cheat-sheets/web-apis-with-controllers-returning-actionresult-t-cheat-sheet)

Source: https://www.ask-codey.com/lessons/web-apis-with-controllers-returning-actionresult-t
