Så här skapar du svar i minimala API-appar
Notera
Det här är inte den senaste versionen av den här artikeln. För den aktuella utgåvan, se version .NET 9 av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Minimala slutpunkter stöder följande typer av returvärden:
-
string
– Detta inkluderarTask<string>
ochValueTask<string>
. -
T
(alla andra typer) – Detta inkluderarTask<T>
ochValueTask<T>
. -
IResult
baserad – Detta inkluderarTask<IResult>
ochValueTask<IResult>
.
string
Returnera värden
Uppförande | Innehållstyp |
---|---|
Ramverket skriver strängen direkt till svaret. | text/plain |
Överväg följande routningshanterare, som returnerar en Hello world
text.
app.MapGet("/hello", () => "Hello World");
Statuskoden 200
returneras med text/plain
innehållstypsrubrik och följande innehåll.
Hello World
T
(alla andra typer) returnerar värden
Uppförande | Innehållstyp |
---|---|
Ramverket JSON-serialiserar svaret. | application/json |
Överväg följande routningshanterare, som returnerar en anonym typ som innehåller en Message
strängegenskap.
app.MapGet("/hello", () => new { Message = "Hello World" });
Statuskoden 200
returneras med application/json
innehållstypsrubrik och följande innehåll.
{"message":"Hello World"}
IResult
returnera värden
Uppförande | Innehållstyp |
---|---|
Ramverket anropar IResult.ExecuteAsync. | Bestäms av IResult implementeringen. |
Gränssnittet IResult
definierar ett kontrakt som representerar resultatet av en HTTP-slutpunkt. Den statiska Results-klassen och den statiska TypedResults- används för att skapa olika IResult
objekt som representerar olika typer av svar.
TypedResults vs Resultat
De Results- och TypedResults statiska klasserna ger liknande uppsättningar resultathjälpare. Klassen TypedResults
är den av typen motsvarigheten till klassen Results
.
Results
-hjälpens returtyp är dock IResult, medan varje TypedResults
-hjälpens returtyp är en av de IResult
implementeringstyperna. Skillnaden innebär att en konvertering krävs för Results
hjälpverktyg när den konkreta typen behövs, till exempel för enhetstestning. Implementeringstyperna definieras i namnområdet Microsoft.AspNetCore.Http.HttpResults.
Att returnera TypedResults
i stället för Results
har följande fördelar:
-
TypedResults
hjälpare returnerar starkt skrivna objekt, vilket kan förbättra kodens läsbarhet, enhetstestning och minska risken för körningsfel. - Implementeringstypen tillhandahåller automatiskt svarstypmetadata för OpenAPI- för att beskriva slutpunkten.
Överväg följande slutpunkt, för vilken en 200 OK
statuskod med det förväntade JSON-svaret skapas.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
För att kunna dokumentera den här slutpunkten korrekt anropas tilläggsmetoden Produces
. Det är dock inte nödvändigt att anropa Produces
om TypedResults
används i stället för Results
, enligt följande kod.
TypedResults
tillhandahåller automatiskt metadata för slutpunkten.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Mer information om hur du beskriver en svarstyp finns i OpenAPI-stöd i minimala API:er.
Som tidigare nämnts behövs ingen konvertering när du använder TypedResults
. Överväg följande minimala API som returnerar en TypedResults
-klass
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
Följande test kontrollerar den fullständiga betongtypen:
[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title 1",
Description = "Test description 1",
IsDone = false
});
context.Todos.Add(new Todo
{
Id = 2,
Title = "Test title 2",
Description = "Test description 2",
IsDone = true
});
await context.SaveChangesAsync();
// Act
var result = await TodoEndpointsV1.GetAllTodos(context);
//Assert
Assert.IsType<Ok<Todo[]>>(result);
Assert.NotNull(result.Value);
Assert.NotEmpty(result.Value);
Assert.Collection(result.Value, todo1 =>
{
Assert.Equal(1, todo1.Id);
Assert.Equal("Test title 1", todo1.Title);
Assert.False(todo1.IsDone);
}, todo2 =>
{
Assert.Equal(2, todo2.Id);
Assert.Equal("Test title 2", todo2.Title);
Assert.True(todo2.IsDone);
});
}
Eftersom alla metoder på Results
returnerar IResult
i sina signaturer, drar kompilatorn automatiskt slutsatsen att detta ska vara returneringstypen för begärandedelegaten när olika resultat returneras från en enda slutpunkt.
TypedResults
kräver att Results<T1, TN>
används av sådana ombud.
Följande metod kompileras eftersom både Results.Ok
och Results.NotFound
deklareras som returvärden av IResult
, även om de konkreta typerna för de returnerade objekten är olika:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Följande metod kompileras inte eftersom TypedResults.Ok
och TypedResults.NotFound
deklareras som returnerade olika typer och kompilatorn inte försöker härleda den bästa matchningstypen:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
För att använda TypedResults
måste returtypen vara helt deklarerad, vilket när den används asynkront kräver wrappern Task<>
. Att använda TypedResults
är mer utförligt, men det är kompromissen för att typinformationen ska vara statiskt tillgänglig och därmed kunna självbeskriva till OpenAPI:
app.MapGet("/todoitems/{id}", async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Resultat<TResult1, TResultN>
Använd Results<TResult1, TResultN>
som slutpunktshanterarens returtyp i stället för IResult
när:
- Flera
IResult
implementeringstyper returneras från slutpunktshanteraren. - Den statiska
TypedResult
-klassen används för att skapaIResult
objekt.
Det här alternativet är bättre än att returnera IResult
eftersom de allmänna unionstyperna automatiskt behåller slutpunktsmetadata. Och eftersom Results<TResult1, TResultN>
-unionstyperna implementerar implicita överföringsoperatorer kan kompilatorn automatiskt konvertera de typer som anges i de generiska argumenten till en instans av uniontypen.
Detta har den extra fördelen med att tillhandahålla kompileringstidskontroll att en routningshanterare faktiskt bara returnerar de resultat som den deklarerar att den gör. Försök att returnera en typ som inte deklareras som ett av de allmänna argumenten för att Results<>
resulterar i ett kompileringsfel.
Tänk på följande slutpunkt, för vilken en 400 BadRequest
statuskod returneras när orderId
är större än 999
. Annars producerar den en 200 OK
med det förväntade innehållet.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
För att kunna dokumentera den här slutpunkten korrekt anropas tilläggsmetoden Produces
. Men, eftersom TypedResults
-hjälparskriptet automatiskt innehåller metadata för slutpunkten, kan du i stället returnera Results<T1, Tn>
-unionstypen, som visas i följande kod.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Inbyggda resultat
Vanliga resultathjälpare finns i Results- och TypedResults statiska klasser. Att returnera TypedResults
föredras framför att returnera Results
. Mer information finns i TypedResults vs Results.
Följande avsnitt visar användningen av vanliga resultathjälpare.
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync är ett alternativt sätt att returnera JSON:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
Anpassad statuskod
app.MapGet("/405", () => Results.StatusCode(405));
Internt serverfel
app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));
Föregående exempel returnerar en statuskod på 500.
Problem och valideringsproblem
app.MapGet("/problem", () =>
{
var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
return TypedResults.Problem("This is an error with extensions",
extensions: extensions);
});
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Ström
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Results.Stream
överlagringar ger åtkomst till den underliggande HTTP-svarsströmmen utan buffring. I följande exempel används ImageSharp- för att returnera en reducerad storlek på den angivna bilden:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}
I följande exempel strömmas en avbildning från Azure Blob Storage-:
app.MapGet("/stream-image/{containerName}/{blobName}",
async (string blobName, string containerName, CancellationToken token) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token), "image/jpeg");
});
I följande exempel strömmas en video från en Azure Blob:
// GET /stream-video/videos/earth.mp4
app.MapGet("/stream-video/{containerName}/{blobName}",
async (HttpContext http, CancellationToken token, string blobName, string containerName) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
var properties = await blobClient.GetPropertiesAsync(cancellationToken: token);
DateTimeOffset lastModified = properties.Value.LastModified;
long length = properties.Value.ContentLength;
long etagHash = lastModified.ToFileTime() ^ length;
var entityTag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token),
contentType: "video/mp4",
lastModified: lastModified,
entityTag: entityTag,
enableRangeProcessing: true);
});
Omdirigera
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fil
app.MapGet("/download", () => Results.File("myfile.text"));
HttpResult-gränssnitt
Följande gränssnitt i namnområdet Microsoft.AspNetCore.Http ger ett sätt att upptäcka typen IResult
vid körning, vilket är ett vanligt mönster i filterimplementeringar.
- IContentTypeHttpResult
- IFileHttpResult
- INestedHttpResult
- IStatusCodeHttpResult
- IValueHttpResult
- IValueHttpResult<TValue>
Här är ett exempel på ett filter som använder något av följande gränssnitt:
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
Mer information finns i filter i minimala API-appar och IResult-implementeringstyper.
Ändra rubriker
Använd HttpResponse
-objektet för att ändra svarshuvuden:
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
Anpassa svar
Program kan styra svar genom att implementera en anpassad IResult typ. Följande kod är ett exempel på en HTML-resultattyp:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
Vi rekommenderar att du lägger till en tilläggsmetod till Microsoft.AspNetCore.Http.IResultExtensions för att göra dessa anpassade resultat mer upptäckbara.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Dessutom kan en anpassad IResult
typ ge en egen anteckning genom att implementera IEndpointMetadataProvider-gränssnittet. Följande kod lägger till en annotation till den föregående HtmlResult
-typen som beskriver svaret som genereras av slutpunkten.
class HtmlResult : IResult, IEndpointMetadataProvider
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesHtmlMetadata());
}
}
ProducesHtmlMetadata
är en implementering av IProducesResponseTypeMetadata som definierar den producerade svarsinnehållstypen text/html
och statuskoden 200 OK
.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
En alternativ metod är att använda Microsoft.AspNetCore.Mvc.ProducesAttribute för att beskriva det producerade svaret. Följande kod ändrar metoden PopulateMetadata
för att använda ProducesAttribute
.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
Konfigurera alternativ för JSON-serialisering
Som standard använder minimala API-appar Web defaults
alternativ under JSON-serialisering och deserialisering.
Konfigurera JSON-serialiseringsalternativ globalt
Alternativ kan konfigureras globalt för en app genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Eftersom fält ingår läser koden ovan NameField
och innehåller den i JSON-utdata.
Konfigurera JSON-serialiseringsalternativ för en slutpunkt
Om du vill konfigurera serialiseringsalternativ för en slutpunkt anropar du Results.Json och skickar ett JsonSerializerOptions objekt till den, som du ser i följande exempel:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/", () =>
Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Du kan istället använda en överbelastning av WriteAsJsonAsync som tar emot en JsonSerializerOptions-objekt. I följande exempel används den här överlagringen för att formatera utdata-JSON:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
WriteIndented = true };
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsJsonAsync<Todo>(
new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Ytterligare resurser
Minimala slutpunkter stöder följande typer av returvärden:
-
string
– Detta inkluderarTask<string>
ochValueTask<string>
. -
T
(alla andra typer) – Detta inkluderarTask<T>
ochValueTask<T>
. -
IResult
-baserat – Detta inkluderarTask<IResult>
ochValueTask<IResult>
.
string
returnera värden
Uppförande | Innehållstyp |
---|---|
Ramverket skriver strängen direkt till svaret. | text/plain |
Överväg följande routningshanterare, som returnerar en Hello world
text.
app.MapGet("/hello", () => "Hello World");
Statuskoden 200
returneras med text/plain
innehållstypsrubrik och följande innehåll.
Hello World
T
(alla andra typer) returnerar värden
Uppförande | Innehållstyp |
---|---|
Ramverket JSON-serialiserar svaret. | application/json |
Överväg följande routningshanterare, som returnerar en anonym typ som innehåller en Message
strängegenskap.
app.MapGet("/hello", () => new { Message = "Hello World" });
Statuskoden 200
returneras med application/json
innehållstypsrubrik och följande innehåll.
{"message":"Hello World"}
IResult
returnera värden
Uppförande | Innehållstyp |
---|---|
Ramverket anropar IResult.ExecuteAsync. | Bestäms genom implementeringen av IResult . |
Gränssnittet IResult
definierar ett kontrakt som representerar resultatet av en HTTP-slutpunkt. Den statiska Results-klassen och den statiska TypedResults- används för att skapa olika IResult
objekt som representerar olika typer av svar.
TypedResults vs Resultat
De Results- och TypedResults statiska klasserna ger liknande uppsättningar resultathjälpare. Klassen TypedResults
är den motsvarigheten av typen till klassen Results
.
Results
-hjälpens returtyp är dock IResult, medan varje TypedResults
-hjälpens returtyp är en av de IResult
implementeringstyperna. Skillnaden innebär att en konvertering krävs för Results
assistenter när den konkreta typen behövs, till exempel för enhetstestning. Implementeringstyperna definieras i namnområdet Microsoft.AspNetCore.Http.HttpResults.
Att returnera TypedResults
i stället för Results
har följande fördelar:
-
TypedResults
hjälpverktyg returnerar starkt typade objekt, vilket kan förbättra kodens läsbarhet, enhetstester och minska risken för runtime-fel. - Implementeringstypen tillhandahåller automatiskt svarstypmetadata för OpenAPI- för att beskriva slutpunkten.
Överväg följande slutpunkt, för vilken en 200 OK
statuskod med det förväntade JSON-svaret skapas.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
För att kunna dokumentera den här slutpunkten korrekt anropas tilläggsmetoden Produces
. Det är dock inte nödvändigt att anropa Produces
om TypedResults
används i stället för Results
, enligt följande kod.
TypedResults
tillhandahåller automatiskt metadata för slutpunkten.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Mer information om hur du beskriver en svarstyp finns i OpenAPI-stöd i minimala API:er.
Som tidigare nämnts behövs ingen konvertering när du använder TypedResults
. Överväg följande minimala API som returnerar en TypedResults
-klass
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
Följande test kontrollerar den fullständiga betongtypen:
[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title 1",
Description = "Test description 1",
IsDone = false
});
context.Todos.Add(new Todo
{
Id = 2,
Title = "Test title 2",
Description = "Test description 2",
IsDone = true
});
await context.SaveChangesAsync();
// Act
var result = await TodoEndpointsV1.GetAllTodos(context);
//Assert
Assert.IsType<Ok<Todo[]>>(result);
Assert.NotNull(result.Value);
Assert.NotEmpty(result.Value);
Assert.Collection(result.Value, todo1 =>
{
Assert.Equal(1, todo1.Id);
Assert.Equal("Test title 1", todo1.Title);
Assert.False(todo1.IsDone);
}, todo2 =>
{
Assert.Equal(2, todo2.Id);
Assert.Equal("Test title 2", todo2.Title);
Assert.True(todo2.IsDone);
});
}
Eftersom alla metoder på Results
returnerar IResult
i sina signaturer, härleder kompilatorn automatiskt att detta är begärandedelegatens returtyp när olika resultat returneras från en enda slutpunkt.
TypedResults
kräver användning av Results<T1, TN>
av sådana ombud.
Följande metod kompilerar eftersom både Results.Ok
och Results.NotFound
deklareras som att de returnerar IResult
, även om de faktiska konkreta typerna för de objekt som returneras skiljer sig åt.
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Följande metod kompileras inte eftersom TypedResults.Ok
och TypedResults.NotFound
deklareras som returnerade olika typer och kompilatorn inte försöker härleda den bästa matchningstypen:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Om du vill använda TypedResults
måste returtypen vara helt deklarerad, och när det är asynkront krävs Task<>
-wrapper. Att använda TypedResults
är mer utförligt, men det är kompromissen för att typinformationen ska vara statiskt tillgänglig och därmed kunna självbeskriva till OpenAPI:
app.MapGet("/todoitems/{id}", async Task<Results<Ok<Todo>, NotFound>> (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
Resultat<TResult1, TResultN>
Använd Results<TResult1, TResultN>
som slutpunktshanterarens returtyp i stället för IResult
när:
- Flera
IResult
implementeringstyper returneras från slutpunktshanteraren. - Den statiska
TypedResult
-klassen används för att skapaIResult
objekt.
Det här alternativet är bättre än att returnera IResult
eftersom de allmänna unionstyperna automatiskt behåller slutpunktsmetadata. Och eftersom dessa Results<TResult1, TResultN>
unionstyper implementerar implicita cast-operatorer kan kompilatorn automatiskt konvertera de typer som anges i de generiska argumenten till en instans av unionstypen.
Detta har den extra fördelen med att tillhandahålla kompileringstidskontroll att en routningshanterare faktiskt bara returnerar de resultat som den deklarerar att den gör. Försök att returnera en typ som inte deklareras som ett av de allmänna argumenten för att Results<>
resulterar i ett kompileringsfel.
Tänk på följande slutpunkt, för vilken en 400 BadRequest
statuskod returneras när orderId
är större än 999
. Om inte, skapar den en 200 OK
med det förväntade innehållet.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
För att kunna dokumentera den här slutpunkten korrekt anropas tilläggsmetoden Produces
. Men eftersom TypedResults
-hjälpen automatiskt innehåller metadata för slutpunkten kan du returnera Results<T1, Tn>
unionstyp i stället, som du ser i följande kod.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Inbyggda resultat
Vanliga resultathjälpare finns i Results- och TypedResults statiska klasser. Att returnera TypedResults
föredras framför att returnera Results
. Mer information finns i TypedResults vs Results.
Följande avsnitt visar användningen av vanliga resultathjälpare.
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync är ett alternativt sätt att returnera JSON:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
Anpassad statuskod
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Ström
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Results.Stream
överlagringar ger åtkomst till den underliggande HTTP-svarsströmmen utan buffring. I följande exempel används ImageSharp- för att returnera en reducerad storlek på den angivna bilden:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}
I följande exempel strömmas en avbildning från Azure Blob Storage-:
app.MapGet("/stream-image/{containerName}/{blobName}",
async (string blobName, string containerName, CancellationToken token) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token), "image/jpeg");
});
I följande exempel strömmas en video från en Azure Blob:
// GET /stream-video/videos/earth.mp4
app.MapGet("/stream-video/{containerName}/{blobName}",
async (HttpContext http, CancellationToken token, string blobName, string containerName) =>
{
var conStr = builder.Configuration["blogConStr"];
BlobContainerClient blobContainerClient = new BlobContainerClient(conStr, containerName);
BlobClient blobClient = blobContainerClient.GetBlobClient(blobName);
var properties = await blobClient.GetPropertiesAsync(cancellationToken: token);
DateTimeOffset lastModified = properties.Value.LastModified;
long length = properties.Value.ContentLength;
long etagHash = lastModified.ToFileTime() ^ length;
var entityTag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(await blobClient.OpenReadAsync(cancellationToken: token),
contentType: "video/mp4",
lastModified: lastModified,
entityTag: entityTag,
enableRangeProcessing: true);
});
Omdirigera
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fil
app.MapGet("/download", () => Results.File("myfile.text"));
HttpResult-gränssnitt
Följande gränssnitt i namnområdet Microsoft.AspNetCore.Http ger ett sätt att identifiera IResult
-typen vid körning, vilken är en vanlig struktur vid filterimplementeringar.
- IContentTypeHttpResult
- IFileHttpResult
- INestedHttpResult
- IStatusCodeHttpResult
- IValueHttpResult
- IValueHttpResult<TValue>
Här är ett exempel på ett filter som använder något av följande gränssnitt:
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
Mer information finns i filter i minimala API-appar och IResult-implementeringstyper.
Anpassa svar
Program kan styra svar genom att implementera en anpassad IResult typ. Följande kod är ett exempel på en HTML-resultattyp:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
Vi rekommenderar att du lägger till en tilläggsmetod till Microsoft.AspNetCore.Http.IResultExtensions för att göra dessa anpassade resultat lättare att upptäcka.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Dessutom kan en anpassad IResult
typ ge en egen anteckning genom att implementera IEndpointMetadataProvider-gränssnittet. Följande kod lägger till en anteckning till den föregående HtmlResult
-typen som beskriver det svar som genereras av slutpunkten.
class HtmlResult : IResult, IEndpointMetadataProvider
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesHtmlMetadata());
}
}
ProducesHtmlMetadata
är en implementering av IProducesResponseTypeMetadata som definierar den producerade svarsinnehållstypen text/html
och statuskoden 200 OK
.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
En alternativ metod är att använda Microsoft.AspNetCore.Mvc.ProducesAttribute för att beskriva det producerade svaret. Följande kod ändrar metoden PopulateMetadata
för att använda ProducesAttribute
.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
Konfigurera alternativ för JSON-serialisering
Som standard använder minimala API-appar Web defaults
alternativ under JSON-serialisering och deserialisering.
Konfigurera JSON-serialiseringsalternativ globalt
Alternativ kan konfigureras globalt för en app genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Eftersom fält ingår läser koden ovan NameField
och innehåller den i JSON-utdata.
Konfigurera JSON-serialiseringsalternativ för en slutpunkt
Om du vill konfigurera serialiseringsalternativ för en slutpunkt anropar du Results.Json och skickar ett JsonSerializerOptions objekt till den, som du ser i följande exempel:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/", () =>
Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Du kan också använda en överbelastning av WriteAsJsonAsync som accepterar ett JsonSerializerOptions-objekt. I följande exempel används den här överlagringen för att formatera utdata-JSON:
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
WriteIndented = true };
app.MapGet("/", (HttpContext context) =>
context.Response.WriteAsJsonAsync<Todo>(
new Todo { Name = "Walk dog", IsComplete = false }, options));
app.Run();
class Todo
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Ytterligare resurser
ASP.NET Core