Results is a static helper type in Microsoft.AspNetCore.Http for creating common HTTP responses in ASP.NET Core minimal API endpoints. Its methods can return status codes with optional JSON bodies, and each result implements IResult for endpoint middleware to execute. section 5.3.1
Use Results methods to communicate what happened to the client:
- Results.Ok() returns 200 OK, optionally with a response body. section 5.3.1
- Results.Created() returns 201 Created, commonly with a resource location and JSON representation. section 5.3.1
- Results.NoContent(), Results.BadRequest(), and Results.NotFound() represent 204, 400, and 404 responses. section 5.3.1
- Results.Problem() and Results.ValidationProblem() provide concise Problem Details responses for general and validation errors. section 5.6
- Results.File(), Results.Bytes(), and Results.Stream() return files, binary data, or streams, with optional content types, filenames, and range processing. section 5.3.4
Returning a missing or newly 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 GET endpoint returns the fruit with 200 OK or reports a missing entity with 404 Not Found. The POST endpoint returns 201 Created with a location and body, or 400 Bad Request when the identifier already exists. 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
Browse the complete minimal API listing to compare Results and TypedResults across GET, POST, and other endpoint responses. section 5.3.1
TypedResults performs the same response-generation work but returns concrete generic types instead of IResult. Those types are easier to use in unit tests and OpenAPI documentation; TypedResults was added in .NET 7. For unusual responses, inject HttpResponse and write the status, content type, and body manually, or create a custom IResult when the same response logic repeats. section 5.3.1 section 5.3.4
