IResult represents the response produced by a minimal API endpoint. When an endpoint returns an IResult, endpoint middleware calls ExecuteAsync, allowing the result to choose the status code and response details. section 5.3
Use IResult when an endpoint must describe more than a default successful response:
- Return Results.NotFound() when a requested entity does not exist instead of throwing an exception and producing a 500 response. section 5.3
- Return Results and TypedResults helper results for common responses such as 200 OK, 201 Created, 204 No Content, 400 Bad Request, and 404 Not Found. section 5.3.1
- Use TypedResults when concrete generic result types are useful for unit tests or OpenAPI documentation; use Results when an IResult return type is sufficient. section 5.3.1
Returning a missing or created fruit
app.MapGet("/fruit/{id}", (string id) => _fruit.TryGetValue(id, out var fruit) ? TypedResults.Ok(fruit) : Results.NotFound()); app.MapPost("/fruit/{id}", (string id, Fruit fruit) => _fruit.TryAdd(id, fruit) ? TypedResults.Created($"/fruit/{id}", fruit) : Results.BadRequest(new { id = "A fruit with this id already exists" })); The handlers return success, not-found, created, or bad-request responses without manually writing the HTTP response. section 5.3.1
Listing 5.3 Using Results and TypedResults in a minimal API
using System.Collections.Concurrent; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplication app = builder.Build(); var _fruit = new ConcurrentDictionary<string, Fruit>(); #1 app.MapGet("/fruit", () => _fruit); app.MapGet("/fruit/{id}", (string id) => _fruit.TryGetValue(id, out var fruit) #2 ? TypedResults.Ok(fruit) #3 : Results.NotFound()); #4 app.MapPost("/fruit/{id}", (string id, Fruit fruit) => _fruit.TryAdd(id, fruit) #5 ? TypedResults.Created($"/fruit/{id}", fruit) #6 : Results.BadRequest(new #7 { id = "A fruit with this id already exists" })); #7 app.MapPut("/fruit/{id}", (string id, Fruit fruit) => { _fruit[id] = fruit; return Results.NoContent(); #8 }); app.MapDelete("/fruit/{id}", (string id) => { _fruit.TryRemove(id, out _); #9 return Results.NoContent(); #9 }); app.Run(); record Fruit(string Name, int stock); #1 Uses a concurrent dictionary to make the API thread-safe #2 Tries to get the fruit from the dictionary. If the ID exists in the dictionary, this returns true . . . #3 . . . and we return a 200 OK response, serializing the fruit in the body as JSON. #4 If the ID doesn’t exist, returns a 404 Not Found response #5 Tries to add the fruit to the dictionary. If the ID hasn’t been added yet. this returns true . . . #6 . . . and we return a 201 response with a JSON body and set the Location header to the given path. #7 If the ID already exists, returns a 400 Bad Request response with an error message #8 After adding or replacing the fruit, returns a 204 No Content response #9 After deleting the fruit, always returns a 204 No Content response
A complete minimal API listing shows IResult-producing branches for GET, POST, and other fruit operations. section 5.3.1
IResult is not the only response technique. Returning void, a string, or another object uses framework conventions, while injecting HttpResponse supports complex manual responses. Repeated manual response logic can be encapsulated in a custom IResult type. section 5.3 section 5.3.4

