Skip to main content

Command Palette

Search for a command to run...

IActionResult vs TypedResults vs Results in ASP.NET Core: Enterprise API Response Design Decision Guide

A senior architect's guide to IActionResult, TypedResults, and Results โ€” with a decision matrix for enterprise .NET teams in 2026.

Published
โ€ข11 min read
IActionResult vs TypedResults vs Results in ASP.NET Core: Enterprise API Response Design Decision Guide

When your ASP.NET Core API returns a response, three distinct abstractions compete for your attention: IActionResult, TypedResults, and the untyped Results helper class. In .NET 10, with Minimal APIs now considered production-ready for enterprise workloads, this choice has real consequences โ€” for OpenAPI documentation accuracy, testability, startup performance, and long-term maintainability. Getting the response model right from day one saves your team from painful refactoring as the codebase scales.


๐ŸŽ Want implementation-ready .NET source code you can drop straight into your project? Join Coding Droplets on Patreon for exclusive tutorials, premium code samples, and early access to new content. ๐Ÿ‘‰ https://www.patreon.com/CodingDroplets


Understanding the Three Abstractions

Before making a team-wide decision, it is essential to understand what each abstraction actually is and where it came from.

IActionResult: The Controller Veteran

IActionResult is the return type that has anchored controller-based APIs since the early days of ASP.NET Core MVC. Every method on ControllerBase โ€” Ok(), BadRequest(), NotFound(), CreatedAtAction() โ€” returns an object implementing IActionResult. It is a loosely typed interface: the compiler does not know at the method signature level what the actual HTTP response shape will be.

This lack of compile-time specificity is both its strength and its limitation. Flexibility is excellent for legacy codebases with complex branching logic. The downside surfaces in OpenAPI tooling: without explicit [ProducesResponseType] attributes, Swagger and Scalar cannot infer what the endpoint actually produces, so documentation becomes a manual tax.

Results: The Minimal API Shorthand

When Minimal APIs arrived in .NET 6, Microsoft introduced the static Results helper class as the lightweight alternative to the controller helper methods. Results.Ok(data), Results.NotFound(), Results.Problem() โ€” these produce objects implementing IResult, the interface for Minimal API responses. The return type, however, is still IResult (untyped), which means the OpenAPI contract is still invisible at the type level unless you add explicit metadata via .Produces<T>().

TypedResults: The Type-Safe Evolution

TypedResults arrived in .NET 7 as the strongly typed counterpart to Results. Where Results.Ok(data) returns IResult, TypedResults.Ok(data) returns Ok<T> โ€” a concrete, generic type. This seemingly small shift unlocks significant downstream benefits:

  • OpenAPI inference works automatically: TypedResults.Ok<ProductDto>(product) tells the framework that this endpoint produces a ProductDto on 200.
  • Unit testing becomes more precise: you can assert result is Ok<ProductDto> without needing to inspect the response body.
  • Compile-time correctness: if you change the DTO type, mismatches surface at build time, not at runtime or in manual Swagger reviews.

When to Use IActionResult

IActionResult remains the right choice in specific scenarios. Do not treat it as automatically legacy.

Use IActionResult when:

  • You are working in an existing controller-based codebase and the migration cost to Minimal APIs does not justify the benefit.
  • The endpoint has highly complex conditional branching where 5+ different status codes are returned based on different runtime paths and each branch has a different DTO shape.
  • You depend on action filters, model binding conventions, or ControllerBase helper methods that have no Minimal API equivalent in your current .NET version.
  • Your team operates in a large organisation with strong MVC conventions and standardised controller base classes that wrap cross-cutting concerns.

Anti-patterns to avoid:

  • Using IActionResult in new Minimal API endpoints: there is no technical reason to mix the abstraction into Minimal API delegates.
  • Returning IActionResult from controller actions without [ProducesResponseType] attributes: this silently degrades your OpenAPI quality.
  • Using IActionResult simply because it is familiar: familiarity is not an architecture decision.

When to Use Results (Untyped)

The untyped Results helper class is useful for rapid prototyping and for endpoints where OpenAPI type inference is not required.

Use Results when:

  • You are writing a quick proof-of-concept or internal tooling endpoint where OpenAPI documentation is not a concern.
  • The endpoint returns a file, a redirect, or a raw stream โ€” cases where typed response shaping adds no value.
  • You are mixing return types within a single delegate and the combination is not supported by Results<T1, T2, T3> union types yet.

When NOT to use Results:

  • Any endpoint that is part of a public or internal-facing API contract where OpenAPI accuracy matters.
  • Endpoints that will be unit tested and need response type assertions โ€” the untyped IResult return makes assertions weaker.

When to Use TypedResults

TypedResults is the recommended default for all new Minimal API development in .NET 10. Microsoft's own guidance and the ASP.NET Core team explicitly recommend it over the untyped Results in production scenarios.

Use TypedResults when:

  • You are building new Minimal API endpoints โ€” this is the baseline standard.
  • OpenAPI documentation accuracy is a requirement (it almost always is).
  • You want unit tests that can assert response types without deserialising the HTTP response.
  • You are using the Results<T1, T2> union return type to declare a multi-status endpoint contract at the type level.

The union return type pattern for enterprise APIs:

One of the most powerful enterprise use cases for TypedResults is the union return type. By declaring a return type of Results<Ok<ProductDto>, NotFound, ValidationProblem>, you communicate the full contract of the endpoint at the method signature โ€” no attributes, no documentation comments required. OpenAPI tooling reads this directly and generates accurate schemas.

This pattern is particularly valuable in teams that practise API-first design or generate client SDKs from OpenAPI specs, because any change to the response contract immediately breaks the build if existing callers are not updated.


Side-By-Side Comparison

Dimension IActionResult Results (Untyped) TypedResults
API Surface Controller-based APIs Minimal APIs Minimal APIs
Return type IActionResult IResult Ok<T>, NotFound, etc.
OpenAPI inference โŒ Manual attributes โŒ Manual .Produces<T>() โœ… Automatic
Unit testability Weak (needs HTTP context) Weak (IResult) โœ… Strong (type assertions)
Compile-time safety โŒ โŒ โœ…
Migration effort Zero (existing code) Low Low (new code only)
.NET version All .NET 6+ .NET 7+
Enterprise recommendation Legacy / migration path Prototypes only โœ… Default for new work

The Enterprise Decision Matrix

For greenfield projects starting in .NET 10: Standardise on TypedResults for all Minimal API endpoints. Use the Results<T1, T2> union type for multi-status endpoints. Enforce this via code review guidelines or a custom Roslyn analyser.

For existing controller-based projects: Do not force a migration for its own sake. Evaluate each service boundary: if a module is being rewritten or a new service is being extracted, Minimal APIs with TypedResults is the right landing zone. Leave stable, well-tested controller code alone.

For teams running a hybrid architecture (controllers + Minimal APIs): Establish a clear boundary policy. Controllers handle legacy domain areas. New endpoints use Minimal APIs with TypedResults. Document this in your architectural decision record (ADR) so new engineers do not introduce inconsistency.

For teams generating client SDKs: TypedResults is non-negotiable if you auto-generate SDKs from OpenAPI specs. Inaccurate schemas produce broken SDKs. The cost of going back and annotating every controller endpoint with [ProducesResponseType] is far higher than adopting TypedResults from day one.


Is What I'm Using Good Enough? How to Audit Your Current Codebase

If you have an existing codebase and want to assess how healthy your response type strategy is, check these signals:

  1. OpenAPI schema completeness: Open your Swagger or Scalar UI. Count how many endpoints show string or object as the response schema. Each one is a gap โ€” either missing attributes or missing TypedResults adoption.
  2. Unit test assertions: Search your test files for response.Content.ReadFromJsonAsync<T>(). If tests are deserialising HTTP responses to assert response types, your response model is not typed strongly enough.
  3. Controller return types: Search for IActionResult return types. Any that lack [ProducesResponseType] annotations are silent OpenAPI hazards.
  4. Mixed patterns: Look for Results.Ok() (untyped) in Minimal API endpoints. Each one is a candidate for TypedResults.Ok<T>() upgrade.

Anti-Patterns to Stamp Out at Code Review

The untyped controller catch-all:

public IActionResult Get(int id) { ... }

Without [ProducesResponseType(typeof(ProductDto), 200)], this endpoint documents itself as returning nothing useful.

The untyped Results helper in a serious endpoint:

app.MapGet("/products/{id}", (int id) => Results.Ok(product));

The OpenAPI schema for this endpoint will show {} as the response โ€” useless for consumers and SDK generators.

The unnecessary IActionResult in Minimal API:

app.MapGet("/ping", () => new OkObjectResult("pong"));

There is no reason to use the MVC object result model in a Minimal API delegate. Use TypedResults.Ok("pong") instead.


What Does This Mean for .NET 10 Specifically?

.NET 10 brings further improvements to Minimal API OpenAPI support, including tighter integration between TypedResults and the new built-in Microsoft.AspNetCore.OpenApi package (which replaces Swashbuckle as the recommended tool). The TypedResults union return type pattern works seamlessly with this new package to produce accurate, automatically maintained OpenAPI documents.

If your team is planning a .NET 10 migration or greenfield build, locking in TypedResults as the standard early avoids a painful annotation backfill later when you inevitably need OpenAPI-driven tooling.

You can explore the full details on response types in the official ASP.NET Core documentation and the Minimal API response documentation.

For practical implementation examples including union return types, the related .NET 10 Minimal APIs in 2026: Enterprise Adoption Playbook is a good companion read.

Also check out the deep-dive into ASP.NET Core API Versioning: Which Strategy Fits Enterprise Systems? for a complementary architectural decision in the same space.


โ˜• Prefer a one-time tip? Buy us a coffee โ€” every bit helps keep the content coming!


Frequently Asked Questions

Can I use TypedResults in controller-based APIs, not just Minimal APIs? No โ€” TypedResults is part of the Microsoft.AspNetCore.Http.TypedResults class, which is designed for the IResult pipeline used by Minimal APIs. Controller-based actions use IActionResult and the MVC result types (OkObjectResult, etc.). They are separate pipelines. If you want type-safe responses in controllers, use ActionResult<T> as the return type.

Does TypedResults actually improve OpenAPI documentation automatically? Yes, in .NET 7+ with the Microsoft.AspNetCore.OpenApi package. When a Minimal API endpoint returns TypedResults.Ok<ProductDto>(product), the OpenAPI generator reads the concrete return type and produces the correct response schema without any additional attributes. This is one of the primary reasons the ASP.NET Core team recommends it.

What is the difference between Results.Ok() and TypedResults.Ok()? Results.Ok(data) returns IResult โ€” the compiler knows nothing about the response shape. TypedResults.Ok(data) returns Ok<T> โ€” a concrete generic type the compiler and OpenAPI tooling can inspect. The runtime behaviour (HTTP 200 with JSON body) is identical. The typing difference only matters for OpenAPI inference and unit testing.

Should I migrate existing IActionResult controllers to TypedResults? Not as a priority task. Migrating existing, stable controller code to Minimal APIs + TypedResults carries risk with limited day-to-day benefit unless you have a concrete trigger: rewriting the module, adding SDK generation, or hitting test quality problems. Apply TypedResults to all new work and let legacy controllers evolve naturally.

What is the Results<T1, T2> union type and when should I use it? Results<Ok<ProductDto>, NotFound, ValidationProblem> is a union return type for Minimal API endpoints that can return multiple distinct response shapes. Each type in the union is independently typed, so OpenAPI tooling generates schemas for all possible responses without any extra annotations. Use it whenever an endpoint has more than one success or failure response shape. It is the enterprise-grade alternative to IActionResult with multiple [ProducesResponseType] attributes.

Does using TypedResults affect runtime performance? The impact is negligible. TypedResults methods are thin wrappers that create the same underlying result objects as their Results counterparts. The performance difference between Results.Ok(data) and TypedResults.Ok(data) at runtime is effectively zero. The benefits are entirely at development time: type safety, OpenAPI accuracy, and testability.

What about ActionResult โ€” where does that fit? ActionResult<T> is the controller-based way to get some type safety: the return type signals to OpenAPI tooling that the success response is of type T. It is a reasonable middle ground for controller APIs that cannot yet move to Minimal APIs. However, it only types the success response โ€” multi-status endpoints still require [ProducesResponseType] attributes for non-200 responses.

More from this blog

C

Coding Droplets

119 posts