ASP.NET Core
C#
IActionResult
ActionResult
web development

Can't decide between TaskIActionResult, IActionResult and ActionResultThing

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

These return types solve different problems, not competing style preferences. In ASP.NET Core, the real questions are: is the action asynchronous, does it return a strongly typed body, and can it also return non-success results such as NotFound or BadRequest.

The Three Options

Here is the simple breakdown:

  • 'IActionResult means "this action returns an HTTP result"'
  • 'ActionResult<T> means "this action usually returns T, but may also return an HTTP result"'
  • 'Task<...> means "this action is asynchronous"'

So Task<IActionResult> and Task<ActionResult<T>> are just async versions of the same idea.

When IActionResult Fits

Use IActionResult when the action may return different result types and there is no single obvious response body type:

csharp
1[HttpPost]
2public IActionResult Create(OrderDto dto)
3{
4    if (!ModelState.IsValid)
5        return BadRequest(ModelState);
6
7    return RedirectToAction(nameof(GetById), new { id = 123 });
8}

This is a good fit for MVC-style actions or APIs with varied response shapes.

When ActionResult<T> Fits

Use ActionResult<T> when the happy path returns a concrete type, but error paths still need regular HTTP results:

csharp
1[HttpGet("{id}")]
2public ActionResult<ProductDto> GetById(int id)
3{
4    var product = repository.Find(id);
5    if (product is null)
6        return NotFound();
7
8    return product;
9}

This is often the nicest API style because:

  • success responses are strongly typed
  • OpenAPI and tooling can infer the main body type more clearly
  • you still keep the flexibility of NotFound(), BadRequest(), and similar results

For Web API endpoints, this is often the best default when a main response type exists.

When Task<...> Matters

If the action awaits asynchronous I/O such as database calls or HTTP calls, return Task<...>:

csharp
1[HttpGet("{id}")]
2public async Task<ActionResult<ProductDto>> GetById(int id)
3{
4    var product = await repository.FindAsync(id);
5    if (product is null)
6        return NotFound();
7
8    return product;
9}

The Task is not about HTTP semantics. It is about asynchronous execution.

That means:

  • 'Task<IActionResult> is for async actions with varied response shapes'
  • 'Task<ActionResult<T>> is for async actions with a main typed response body'

A Practical Rule of Thumb

Use this default decision tree:

  1. Is the action asynchronous
  2. Does the success case return a specific body type

If yes to both, Task<ActionResult<T>> is usually the best fit.

If async but no obvious success-body type, Task<IActionResult> is fine.

If synchronous and typed, ActionResult<T> is fine.

If synchronous and mixed-result oriented, IActionResult is fine.

What About Returning T Directly

If an action always returns a successful body and never returns alternate HTTP results, returning T directly can be reasonable. But in real APIs, validation failures, missing records, and authorization checks are common, so ActionResult<T> often gives a better balance. It also documents the "normal success body" more clearly to readers of the controller code and generated API descriptions.

Common Pitfalls

The biggest mistake is using Task<IActionResult> everywhere just because the action is async, even when Task<ActionResult<T>> would make the success response clearer.

Another mistake is returning IActionResult for strongly typed APIs and then losing useful metadata for documentation and tooling.

A third issue is making actions async without actually awaiting anything. If the work is purely synchronous, do not add Task just for style.

Summary

  • 'IActionResult is for general HTTP-result flexibility.'
  • 'ActionResult<T> is for typed success responses plus normal HTTP error results.'
  • Wrap either one in Task<...> when the action is asynchronous.
  • 'Task<ActionResult<T>> is often the best default for async Web API endpoints.'
  • Choose the return type based on action semantics, not habit.

Course illustration
Course illustration

All Rights Reserved.