Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Jegyzet
Ez nem a cikk legújabb verziója. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.
Figyelmeztetés
A ASP.NET Core ezen verziója már nem támogatott. További információ: .NET és .NET Core támogatási szabályzat. Az aktuális kiadásért tekintse meg ennek a cikknek .NET 9-es verzióját.
Ez a cikk azt ismerteti, hogyan hozhat létre válaszokat minimális API-végpontokhoz a ASP.NET Core-ban. A minimális API-k számos módot kínálnak az adatok és a HTTP-állapotkódok visszaadására.
A minimális végpontok a következő típusú visszatérési értékeket támogatják:
-
string– Ez magában foglaljaTask<string>ésValueTask<string>. -
T(Bármely más típus) – Ebbe beletartozikTask<T>ésValueTask<T>. -
IResultalapú – Ez magában foglaljaTask<IResult>ésValueTask<IResult>.
Fontos
A ASP.NET Core 10-től kezdve az ismert API-végpontok már nem lesznek átirányítva a bejelentkezési lapokra hitelesítés használatakor cookie . Ehelyett 401/403 állapotkódokat adnak vissza. További részletekért lásd az API-végpontok hitelesítési viselkedését a ASP.NET Core-ban.
string visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer közvetlenül a válaszba írja a sztringet. | text/plain |
Vegye figyelembe az alábbi útvonalkezelőt, amely egy Hello world szöveget ad vissza.
app.MapGet("/hello", () => "Hello World");
A 200 állapotkód text/plain Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
Hello World
T (bármely más típus) visszaadja az értékeket
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer JSON-szerializálja a választ. | application/json |
Vegye figyelembe a következő útvonalkezelőt, amely egy névtelen típust ad vissza, amely egy Message karakterlánc-tulajdonságot tartalmaz.
app.MapGet("/hello", () => new { Message = "Hello World" });
A 200 állapotkód application/json Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
{"message":"Hello World"}
IResult visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer meghívja IResult.ExecuteAsync. | A IResult megvalósítása határozza meg. |
A IResult felület egy HTTP-végpont eredményét képviselő szerződést határoz meg. A statikus Eredmények osztály és a statikus TypedResults különböző IResult típusú válaszok létrehozására szolgálnak.
TypedResults és Results
A Results és TypedResults statikus osztályok hasonló eredménysegítőket biztosítanak. A TypedResults osztály a típusú megfelelője a Results osztálynak. A Results segítők visszatérési típusa azonban IResult, míg az egyes TypedResults segítők visszatérési típusa az IResult implementálási típusok egyike. A különbség azt jelenti, hogy Results segítők esetében átalakításra van szükség, ha a konkrét típusra van szükség, például egységteszteléshez. A megvalósítási típusok a Microsoft.AspNetCore.Http.HttpResults névtérben vannak definiálva.
A TypedResults helyett a Results visszaadása a következő előnyökkel jár:
-
TypedResultssegítők erősen gépelt objektumokat adnak vissza, amelyek javítják a kód olvashatóságát, az egységtesztelést és csökkentik a futásidejű hibák esélyét. - A implementáció típusa automatikusan biztosítja az OpenAPI választípus-metaadatait a végpont leírásához.
Fontolja meg a következő végpontot, amelyhez egy 200 OK állapotkód jön létre a várt JSON-válaszsal.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Azonban nem szükséges meghívni Produces, ha TypedResults van használva Resultshelyett, ahogy az alábbi kódban látható.
TypedResults automatikusan biztosítja a végpont metaadatait.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
A választípusok leírásáról további információt a Minimális API-k OpenAPI-támogatásában talál.
A tesztelési eredménytípusokra vonatkozó példákért tekintse meg a tesztelési dokumentációt.
Mivel az Results összes metódusa IResult típust ad vissza az aláírásban, a fordító automatikusan ezt a típust állapítja meg a kérés delegált visszatérési típusaként, amikor egy végpont különböző eredményeket ad vissza.
TypedResults megköveteli a Results<T1, TN> használatát az ilyen típusú meghatalmazottaktól.
A következő módszer azért lefordul, mert Results.Ok és Results.NotFound is úgy vannak deklarálva, hogy IResult-t adnak vissza, annak ellenére, hogy a visszaadott objektumok tényleges konkrét típusai eltérőek:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
A következő metódus nem fordítható le, mert a TypedResults.Ok és a TypedResults.NotFound különböző típusok visszaadásaként van deklarálva, és a fordító nem próbálja meg a legjobb egyező típust kikövetkeztetni:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
A TypedResults használatához a visszatérési típust teljes mértékben deklarálni kell; ha a metódus aszinkron, a deklaráció során a visszatérési típust be kell csomagolni egy Task<>-be. A TypedResults használata bőbeszédűbb, de ez az a kompromisszum, amelyért cserébe a típusadatok statikusan elérhetők, így az OpenAPI-ban önmagukat leírhatják.
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());
Eredmények<TResult1, TResultN>
Használja a Results<TResult1, TResultN> a végpontkezelő visszatérési típusát IResult helyett, amikor:
- A rendszer több
IResultimplementációtípust ad vissza a végpontkezelőtől. - A statikus
TypedResultosztály aIResultobjektumok létrehozásához használható.
Ez az alternatíva jobb, mint a IResult visszaadása, mivel az általános egyesítési típusok automatikusan megőrzik a végpont metaadatait. Mivel az Results<TResult1, TResultN> egyesítő típusok implicit öntött operátorokat implementálnak, a fordító automatikusan konvertálhatja az általános argumentumokban megadott típusokat az egyesítő típus egy példányává.
Ez azzal a többletelőnnyel jár, hogy fordítási időt biztosít annak ellenőrzéséhez, hogy az útvonalkezelő valójában csak az általa deklarált eredményeket adja vissza. Ha olyan típust próbál visszaadni, amely nincs Results<> általános argumentumai között deklarálva, az fordítási hibát eredményez.
Vegye figyelembe a következő végpontot, amelynek 400 BadRequest állapotkódját adja vissza, ha a orderId nagyobb, mint 999. Ellenkező esetben létrehoz egy 200 OK-t a várt tartalommal.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Mivel azonban a TypedResults segéd automatikusan tartalmazza a végpont metaadatait, a Results<T1, Tn> egyesítési típust is visszaadhatja, ahogyan az az alábbi kódban is látható.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Beépített eredmények
A Results és TypedResults statikus osztályokban gyakori eredmény-segítők léteznek.
TypedResults visszaadása előnyösebb, mint Resultsvisszaadása. További információ: TypedResults vs Results.
Az alábbi szakaszok a gyakori eredménysegítők használatát mutatják be.
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync a JSON visszaadásának alternatív módja:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
Egyéni állapotkód
app.MapGet("/405", () => Results.StatusCode(405));
Belső kiszolgálóhiba
app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));
Az előző példa egy 500-ás állapotkódot ad vissza.
Probléma és validálási probléma
app.MapGet("/problem", () =>
{
var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
return TypedResults.Problem("This is an error with extensions",
extensions: extensions);
});
Érvényesítési hibaválaszok testreszabása az IProblemDetailsService használatával
Implementációval testre szabhatja a minimális API-érvényesítési logika hibaválaszait IProblemDetailsService . Regisztrálja ezt a szolgáltatást az alkalmazás szolgáltatásgyűjteményében a konzisztens és felhasználóspecifikus hibaválaszok engedélyezéséhez. A minimális API-ellenőrzés támogatása a .NET 10 ASP.NET Core-ban jelent meg.
Egyéni érvényesítési hibaválaszok implementálása:
- Az alapértelmezett implementáció implementálása IProblemDetailsService vagy használata
- A szolgáltatás regisztrálása a DI-tárolóban
- Az érvényesítési rendszer automatikusan a regisztrált szolgáltatást használja az érvényesítési hibaválaszok formázására
Az alábbi példa bemutatja, hogyan regisztrálhatja és konfigurálhatja az IProblemDetailsService érvényesítési hibaválaszok testreszabását:
using System.ComponentModel.DataAnnotations;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
if (context.ProblemDetails.Status == 400)
{
context.ProblemDetails.Title = "Validation error occurred";
context.ProblemDetails.Extensions["support"] = "Contact support@example.com";
context.ProblemDetails.Extensions["traceId"] = Guid.NewGuid().ToString();
}
};
});
Érvényesítési hiba esetén a rendszer a IProblemDetailsService hibaválasz létrehozására szolgál, beleértve a CustomizeProblemDetails visszahívásban hozzáadott testreszabásokat is.
Egy teljes alkalmazásért tekintse meg a Minimális API-mintaalkalmazást , amely bemutatja, hogyan szabhatja testre az érvényesítési hibaválaszokat a IProblemDetailsService ASP.NET Core Minimal API-k használatával.
Szöveg
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
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 túlterhelések pufferelés nélkül teszik lehetővé a mögöttes HTTP-válaszfolyam elérését. Az alábbi példa ImageSharp használatával adja vissza a megadott kép kisebb méretét:
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);
}
Az alábbi példa folyamatosan továbbít egy képet az Azure Blob tárhelyről.
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");
});
Az alábbi példa egy Azure Blobból streamel egy videót:
// 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);
});
Server-Sent események (SSE)
A TypedResults.ServerSentEvents API támogatja a ServerSentEvents eredmény visszaadását.
Server-Sent Események egy kiszolgáló leküldéses technológiája, amellyel a kiszolgáló egyetlen HTTP-kapcsolaton keresztül küldhet eseményüzeneteket egy ügyfélnek. A .NET-ben az eseményüzenetek objektumokként SseItem<T> jelennek meg, amelyek tartalmazhatnak eseménytípust, azonosítót és hasznos adatokat T.
A TypedResults osztály egy ServerSentEvents nevű statikus metódussal rendelkezik, amely az eredmény visszaadására ServerSentEvents használható. A metódus első paramétere az IAsyncEnumerable<SseItem<T>> ügyfélnek küldendő eseményüzenetek streamje.
Az alábbi példa bemutatja, hogyan használhatja az TypedResults.ServerSentEvents API-t a pulzusesemények JSON-objektumként való visszaadására az ügyfél számára:
app.MapGet("sse-item", (CancellationToken cancellationToken) =>
{
async IAsyncEnumerable<SseItem<int>> GetHeartRate(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var heartRate = Random.Shared.Next(60, 100);
yield return new SseItem<int>(heartRate, eventType: "heartRate")
{
ReconnectionInterval = TimeSpan.FromMinutes(1)
};
await Task.Delay(2000, cancellationToken);
}
}
return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken));
});
További információkért tekintse meg a Minimális API mintaalkalmazást az TypedResults.ServerSentEvents API használatával, amely szövegként ad vissza pulzuseseményeket, ServerSentEvents, és JSON-objektumokat az ügyfélnek.
Átirányít
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fájl
app.MapGet("/download", () => Results.File("myfile.text"));
HttpResult-felületek
A Microsoft.AspNetCore.Http névtér alábbi felületei lehetővé teszik a IResult típus észlelését futásidőben, ami a szűrő implementációk gyakori mintája:
- IContentTypeHttpResult
- IFileHttpResult
- INestedHttpResult
- IStatusCodeHttpResult
- IValueHttpResult
- IValueHttpResult<TValue>
Íme egy példa egy szűrőre, amely az alábbi felületek egyikét használja:
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
};
});
További információért lásd: Minimális API alkalmazások szűrői és IResult implementálási típusok.
Fejlécek módosítása
A válaszfejlécek módosításához használja a HttpResponse objektumot:
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";
});
Válaszok testreszabása
Az alkalmazások egyéni IResult típus implementálásával szabályozhatják a válaszokat. Az alábbi kód egy PÉLDA EGY HTML-eredménytípusra:
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);
}
}
Javasoljuk, hogy adjon hozzá egy bővítménymetódust a Microsoft.AspNetCore.Http.IResultExtensions az egyéni eredmények felderíthetőbbé tétele érdekében.
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();
Az egyéni IResult-típus a IEndpointMetadataProvider felület implementálásával saját annotációt is biztosíthat. A következő kód például egy megjegyzést ad hozzá az előző HtmlResult típushoz, amely leírja a végpont által létrehozott választ.
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());
}
}
A ProducesHtmlMetadata a IProducesResponseTypeMetadata implementációja, amely meghatározza a létrehozott választartalom típusát text/html és az állapotkódot 200 OK.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Egy másik módszer a Microsoft.AspNetCore.Mvc.ProducesAttribute használata a létrehozott válasz leírására. Az alábbi kód a PopulateMetadatahasználatára módosítja a ProducesAttribute metódust.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
JSON-szerializálási beállítások konfigurálása
A minimális API-alkalmazások alapértelmezés szerint a JSON szerializálása és deszerializálása során használnak Web defaults beállításokat.
JSON-szerializálási beállítások konfigurálása globálisan
A beállítások globálisan konfigurálhatók egy alkalmazáshoz a ConfigureHttpJsonOptionsmeghívásával. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
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
// }
Mivel a mezők szerepelnek, az előző kód beolvassa a NameField értéket, és belefoglalja a kimeneti JSON-fájlba.
JSON-szerializálási beállítások konfigurálása végponthoz
A végpont szerializálási beállításainak konfigurálásához hívja meg Results.Json, és adjon át neki egy JsonSerializerOptions objektumot, ahogyan az az alábbi példában látható:
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
// }
Alternatívaként használja a WriteAsJsonAsync túlterhelését, amely elfogadja a JsonSerializerOptions objektumot. Az alábbi példa ezt a túlterhelést használja a kimeneti JSON formázásához:
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
// }
További erőforrások
A minimális végpontok a következő típusú visszatérési értékeket támogatják:
-
string– Ez magában foglaljaTask<string>ésValueTask<string>. -
T(Bármely más típus) – Ebbe beletartozikTask<T>ésValueTask<T>. -
IResultalapú – Ez magában foglaljaTask<IResult>ésValueTask<IResult>.
string visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer közvetlenül a válaszba írja a sztringet. | text/plain |
Vegye figyelembe az alábbi útvonalkezelőt, amely egy Hello world szöveget ad vissza.
app.MapGet("/hello", () => "Hello World");
A 200 állapotkód text/plain Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
Hello World
T (bármely más típus) visszaadja az értékeket
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer JSON-szerializálja a választ. | application/json |
Vegye figyelembe a következő útvonalkezelőt, amely egy névtelen típust ad vissza, amely egy Message karakterlánc-tulajdonságot tartalmaz.
app.MapGet("/hello", () => new { Message = "Hello World" });
A 200 állapotkód application/json Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
{"message":"Hello World"}
IResult visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer meghívja IResult.ExecuteAsync. | A IResult megvalósítása határozza meg. |
A IResult felület egy HTTP-végpont eredményét képviselő szerződést határoz meg. A statikus Eredmények osztály és a statikus TypedResults különböző IResult típusú válaszok létrehozására szolgálnak.
TypedResults és Results
A Results és TypedResults statikus osztályok hasonló eredménysegítőket biztosítanak. A TypedResults osztály a típusú megfelelője a Results osztálynak. A Results segítők visszatérési típusa azonban IResult, míg az egyes TypedResults segítők visszatérési típusa az IResult implementálási típusok egyike. A különbség azt jelenti, hogy Results segítők esetében átalakításra van szükség, ha a konkrét típusra van szükség, például egységteszteléshez. A megvalósítási típusok a Microsoft.AspNetCore.Http.HttpResults névtérben vannak definiálva.
A TypedResults helyett a Results visszaadása a következő előnyökkel jár:
-
TypedResultssegítők erősen gépelt objektumokat adnak vissza, amelyek javítják a kód olvashatóságát, az egységtesztelést és csökkentik a futásidejű hibák esélyét. - A implementáció típusa automatikusan biztosítja az OpenAPI választípus-metaadatait a végpont leírásához.
Fontolja meg a következő végpontot, amelyhez egy 200 OK állapotkód jön létre a várt JSON-válaszsal.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Azonban nem szükséges meghívni Produces, ha TypedResults van használva Resultshelyett, ahogy az alábbi kódban látható.
TypedResults automatikusan biztosítja a végpont metaadatait.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
A választípusok leírásáról további információt a Minimális API-k OpenAPI-támogatásában talál.
Ahogy korábban említettük, TypedResultshasználatakor nincs szükség átalakításra. Fontolja meg a következő minimális API-t, amely egy osztályt TypedResults ad vissza
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
A következő vizsgálat a teljes betontípust ellenőrzi:
[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);
});
}
Mivel az Results összes metódusa IResult típust ad vissza az aláírásban, a fordító automatikusan ezt a típust állapítja meg a kérés delegált visszatérési típusaként, amikor egy végpont különböző eredményeket ad vissza.
TypedResults megköveteli a Results<T1, TN> használatát az ilyen típusú meghatalmazottaktól.
A következő módszer azért lefordul, mert Results.Ok és Results.NotFound is úgy vannak deklarálva, hogy IResult-t adnak vissza, annak ellenére, hogy a visszaadott objektumok tényleges konkrét típusai eltérőek:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
A következő metódus nem fordítható le, mert a TypedResults.Ok és a TypedResults.NotFound különböző típusok visszaadásaként van deklarálva, és a fordító nem próbálja meg a legjobb egyező típust kikövetkeztetni:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
A TypedResultshasználatához a visszatérési típust teljes mértékben deklarálni kell, amely aszinkron esetben a Task<> burkolót igényli. A TypedResults használata bőbeszédűbb, de ez az a kompromisszum, amelyért cserébe a típusadatok statikusan elérhetők, így az OpenAPI-ban önmagukat leírhatják.
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());
Eredmények<TResult1, TResultN>
Használja a Results<TResult1, TResultN> a végpontkezelő visszatérési típusát IResult helyett, amikor:
- A rendszer több
IResultimplementációtípust ad vissza a végpontkezelőtől. - A statikus
TypedResultosztály aIResultobjektumok létrehozásához használható.
Ez az alternatíva jobb, mint a IResult visszaadása, mivel az általános egyesítési típusok automatikusan megőrzik a végpont metaadatait. Mivel az Results<TResult1, TResultN> egyesítő típusok implicit öntött operátorokat implementálnak, a fordító automatikusan konvertálhatja az általános argumentumokban megadott típusokat az egyesítő típus egy példányává.
Ez azzal a többletelőnnyel jár, hogy fordítási időt biztosít annak ellenőrzéséhez, hogy az útvonalkezelő valójában csak az általa deklarált eredményeket adja vissza. Ha olyan típust próbál visszaadni, amely nincs Results<> általános argumentumai között deklarálva, az fordítási hibát eredményez.
Vegye figyelembe a következő végpontot, amelynek 400 BadRequest állapotkódját adja vissza, ha a orderId nagyobb, mint 999. Ellenkező esetben létrehoz egy 200 OK-t a várt tartalommal.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Mivel azonban a TypedResults segéd automatikusan tartalmazza a végpont metaadatait, a Results<T1, Tn> egyesítési típust is visszaadhatja, ahogyan az az alábbi kódban is látható.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Beépített eredmények
A Results és TypedResults statikus osztályokban gyakori eredmény-segítők léteznek.
TypedResults visszaadása előnyösebb, mint Resultsvisszaadása. További információ: TypedResults vs Results.
Az alábbi szakaszok a gyakori eredménysegítők használatát mutatják be.
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync a JSON visszaadásának alternatív módja:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
Egyéni állapotkód
app.MapGet("/405", () => Results.StatusCode(405));
Belső kiszolgálóhiba
app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));
Az előző példa egy 500-ás állapotkódot ad vissza.
Probléma és validálási probléma
app.MapGet("/problem", () =>
{
var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
return TypedResults.Problem("This is an error with extensions",
extensions: extensions);
});
Szöveg
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
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 túlterhelések pufferelés nélkül teszik lehetővé a mögöttes HTTP-válaszfolyam elérését. Az alábbi példa ImageSharp használatával adja vissza a megadott kép kisebb méretét:
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);
}
Az alábbi példa folyamatosan továbbít egy képet az Azure Blob tárhelyről.
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");
});
Az alábbi példa egy Azure Blobból streamel egy videót:
// 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);
});
Átirányít
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fájl
app.MapGet("/download", () => Results.File("myfile.text"));
HttpResult-felületek
A Microsoft.AspNetCore.Http névtér alábbi felületei lehetővé teszik a IResult típus észlelését futásidőben, ami a szűrő implementációk gyakori mintája:
- IContentTypeHttpResult
- IFileHttpResult
- INestedHttpResult
- IStatusCodeHttpResult
- IValueHttpResult
- IValueHttpResult<TValue>
Íme egy példa egy szűrőre, amely az alábbi felületek egyikét használja:
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
};
});
További információért lásd: Minimális API alkalmazások szűrői és IResult implementálási típusok.
Fejlécek módosítása
A válaszfejlécek módosításához használja a HttpResponse objektumot:
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";
});
Válaszok testreszabása
Az alkalmazások egyéni IResult típus implementálásával szabályozhatják a válaszokat. Az alábbi kód egy PÉLDA EGY HTML-eredménytípusra:
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);
}
}
Javasoljuk, hogy adjon hozzá egy bővítménymetódust a Microsoft.AspNetCore.Http.IResultExtensions az egyéni eredmények felderíthetőbbé tétele érdekében.
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();
Az egyéni IResult-típus a IEndpointMetadataProvider felület implementálásával saját annotációt is biztosíthat. A következő kód például egy megjegyzést ad hozzá az előző HtmlResult típushoz, amely leírja a végpont által létrehozott választ.
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());
}
}
A ProducesHtmlMetadata a IProducesResponseTypeMetadata implementációja, amely meghatározza a létrehozott választartalom típusát text/html és az állapotkódot 200 OK.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Egy másik módszer a Microsoft.AspNetCore.Mvc.ProducesAttribute használata a létrehozott válasz leírására. Az alábbi kód a PopulateMetadatahasználatára módosítja a ProducesAttribute metódust.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
JSON-szerializálási beállítások konfigurálása
A minimális API-alkalmazások alapértelmezés szerint a JSON szerializálása és deszerializálása során használnak Web defaults beállításokat.
JSON-szerializálási beállítások konfigurálása globálisan
A beállítások globálisan konfigurálhatók egy alkalmazáshoz a ConfigureHttpJsonOptionsmeghívásával. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
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
// }
Mivel a mezők szerepelnek, az előző kód beolvassa a NameField értéket, és belefoglalja a kimeneti JSON-fájlba.
JSON-szerializálási beállítások konfigurálása végponthoz
A végpont szerializálási beállításainak konfigurálásához hívja meg Results.Json, és adjon át neki egy JsonSerializerOptions objektumot, ahogyan az az alábbi példában látható:
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
// }
Alternatívaként használja a WriteAsJsonAsync túlterhelését, amely elfogadja a JsonSerializerOptions objektumot. Az alábbi példa ezt a túlterhelést használja a kimeneti JSON formázásához:
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
// }
További erőforrások
A minimális végpontok a következő típusú visszatérési értékeket támogatják:
-
string– Ez magában foglaljaTask<string>ésValueTask<string>. -
T(Bármely más típus) – Ebbe beletartozikTask<T>ésValueTask<T>. -
IResultalapú – Ez magában foglaljaTask<IResult>ésValueTask<IResult>.
string visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer közvetlenül a válaszba írja a sztringet. | text/plain |
Vegye figyelembe az alábbi útvonalkezelőt, amely egy Hello world szöveget ad vissza.
app.MapGet("/hello", () => "Hello World");
A 200 állapotkód text/plain Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
Hello World
T (bármely más típus) visszaadja az értékeket
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer JSON-szerializálja a választ. | application/json |
Vegye figyelembe a következő útvonalkezelőt, amely egy névtelen típust ad vissza, amely egy Message karakterlánc-tulajdonságot tartalmaz.
app.MapGet("/hello", () => new { Message = "Hello World" });
A 200 állapotkód application/json Content-Type fejléccel és az alábbi tartalommal lesz visszaadva.
{"message":"Hello World"}
IResult visszaadott értékek
| Magatartás | Tartalomtípus |
|---|---|
| A keretrendszer meghívja IResult.ExecuteAsync. | A IResult megvalósítása határozza meg. |
A IResult felület egy HTTP-végpont eredményét képviselő szerződést határoz meg. A statikus Eredmények osztály és a statikus TypedResults különböző IResult típusú válaszok létrehozására szolgálnak.
TypedResults és Results
A Results és TypedResults statikus osztályok hasonló eredménysegítőket biztosítanak. A TypedResults osztály a típusú megfelelője a Results osztálynak. A Results segítők visszatérési típusa azonban IResult, míg az egyes TypedResults segítők visszatérési típusa az IResult implementálási típusok egyike. A különbség azt jelenti, hogy Results segítők esetében átalakításra van szükség, ha a konkrét típusra van szükség, például egységteszteléshez. A megvalósítási típusok a Microsoft.AspNetCore.Http.HttpResults névtérben vannak definiálva.
A TypedResults helyett a Results visszaadása a következő előnyökkel jár:
-
TypedResultssegítők erősen gépelt objektumokat adnak vissza, amelyek javítják a kód olvashatóságát, az egységtesztelést és csökkentik a futásidejű hibák esélyét. - A implementáció típusa automatikusan biztosítja az OpenAPI választípus-metaadatait a végpont leírásához.
Fontolja meg a következő végpontot, amelyhez egy 200 OK állapotkód jön létre a várt JSON-válaszsal.
app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
.Produces<Message>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Azonban nem szükséges meghívni Produces, ha TypedResults van használva Resultshelyett, ahogy az alábbi kódban látható.
TypedResults automatikusan biztosítja a végpont metaadatait.
app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
A választípusok leírásáról további információt a Minimális API-k OpenAPI-támogatásában talál.
Ahogy korábban említettük, TypedResultshasználatakor nincs szükség átalakításra. Fontolja meg a következő minimális API-t, amely egy osztályt TypedResults ad vissza
public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
var todos = await database.Todos.ToArrayAsync();
return TypedResults.Ok(todos);
}
A következő vizsgálat a teljes betontípust ellenőrzi:
[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);
});
}
Mivel az Results összes metódusa IResult típust ad vissza az aláírásban, a fordító automatikusan ezt a típust állapítja meg a kérés delegált visszatérési típusaként, amikor egy végpont különböző eredményeket ad vissza.
TypedResults megköveteli a Results<T1, TN> használatát az ilyen típusú meghatalmazottaktól.
A következő módszer azért lefordul, mert Results.Ok és Results.NotFound is úgy vannak deklarálva, hogy IResult-t adnak vissza, annak ellenére, hogy a visszaadott objektumok tényleges konkrét típusai eltérőek:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
A következő metódus nem fordítható le, mert a TypedResults.Ok és a TypedResults.NotFound különböző típusok visszaadásaként van deklarálva, és a fordító nem próbálja meg a legjobb egyező típust kikövetkeztetni:
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound());
A TypedResultshasználatához a visszatérési típust teljes mértékben deklarálni kell, amely aszinkron esetben a Task<> burkolót igényli. A TypedResults használata bőbeszédűbb, de ez az a kompromisszum, amelyért cserébe a típusadatok statikusan elérhetők, így az OpenAPI-ban önmagukat leírhatják.
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());
Eredmények<TResult1, TResultN>
Használja a Results<TResult1, TResultN> a végpontkezelő visszatérési típusát IResult helyett, amikor:
- A rendszer több
IResultimplementációtípust ad vissza a végpontkezelőtől. - A statikus
TypedResultosztály aIResultobjektumok létrehozásához használható.
Ez az alternatíva jobb, mint a IResult visszaadása, mivel az általános egyesítési típusok automatikusan megőrzik a végpont metaadatait. Mivel az Results<TResult1, TResultN> egyesítő típusok implicit öntött operátorokat implementálnak, a fordító automatikusan konvertálhatja az általános argumentumokban megadott típusokat az egyesítő típus egy példányává.
Ez azzal a többletelőnnyel jár, hogy fordítási időt biztosít annak ellenőrzéséhez, hogy az útvonalkezelő valójában csak az általa deklarált eredményeket adja vissza. Ha olyan típust próbál visszaadni, amely nincs Results<> általános argumentumai között deklarálva, az fordítási hibát eredményez.
Vegye figyelembe a következő végpontot, amelynek 400 BadRequest állapotkódját adja vissza, ha a orderId nagyobb, mint 999. Ellenkező esetben létrehoz egy 200 OK-t a várt tartalommal.
app.MapGet("/orders/{orderId}", IResult (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
.Produces(400)
.Produces<Order>();
A végpont helyes dokumentálásához a bővítménymetódus Produces van meghívva. Mivel azonban a TypedResults segéd automatikusan tartalmazza a végpont metaadatait, a Results<T1, Tn> egyesítési típust is visszaadhatja, ahogyan az az alábbi kódban is látható.
app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
=> orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));
Beépített eredmények
A Results és TypedResults statikus osztályokban gyakori eredmény-segítők léteznek.
TypedResults visszaadása előnyösebb, mint Resultsvisszaadása. További információ: TypedResults vs Results.
Az alábbi szakaszok a gyakori eredménysegítők használatát mutatják be.
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
WriteAsJsonAsync a JSON visszaadásának alternatív módja:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
(new { Message = "Hello World" }));
Egyéni állapotkód
app.MapGet("/405", () => Results.StatusCode(405));
Szöveg
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
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 túlterhelések pufferelés nélkül teszik lehetővé a mögöttes HTTP-válaszfolyam elérését. Az alábbi példa ImageSharp használatával adja vissza a megadott kép kisebb méretét:
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);
}
Az alábbi példa folyamatosan továbbít egy képet az Azure Blob tárhelyről.
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");
});
Az alábbi példa egy Azure Blobból streamel egy videót:
// 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);
});
Átirányít
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fájl
app.MapGet("/download", () => Results.File("myfile.text"));
HttpResult-felületek
A Microsoft.AspNetCore.Http névtér alábbi felületei lehetővé teszik a IResult típus észlelését futásidőben, ami a szűrő implementációk gyakori mintája:
- IContentTypeHttpResult
- IFileHttpResult
- INestedHttpResult
- IStatusCodeHttpResult
- IValueHttpResult
- IValueHttpResult<TValue>
Íme egy példa egy szűrőre, amely az alábbi felületek egyikét használja:
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
};
});
További információért lásd: Minimális API alkalmazások szűrői és IResult implementálási típusok.
Válaszok testreszabása
Az alkalmazások egyéni IResult típus implementálásával szabályozhatják a válaszokat. Az alábbi kód egy PÉLDA EGY HTML-eredménytípusra:
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);
}
}
Javasoljuk, hogy adjon hozzá egy bővítménymetódust a Microsoft.AspNetCore.Http.IResultExtensions az egyéni eredmények felderíthetőbbé tétele érdekében.
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();
Az egyéni IResult-típus a IEndpointMetadataProvider felület implementálásával saját annotációt is biztosíthat. A következő kód például egy megjegyzést ad hozzá az előző HtmlResult típushoz, amely leírja a végpont által létrehozott választ.
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());
}
}
A ProducesHtmlMetadata a IProducesResponseTypeMetadata implementációja, amely meghatározza a létrehozott választartalom típusát text/html és az állapotkódot 200 OK.
internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
public Type? Type => null;
public int StatusCode => 200;
public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}
Egy másik módszer a Microsoft.AspNetCore.Mvc.ProducesAttribute használata a létrehozott válasz leírására. Az alábbi kód a PopulateMetadatahasználatára módosítja a ProducesAttribute metódust.
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}
JSON-szerializálási beállítások konfigurálása
A minimális API-alkalmazások alapértelmezés szerint a JSON szerializálása és deszerializálása során használnak Web defaults beállításokat.
JSON-szerializálási beállítások konfigurálása globálisan
A beállítások globálisan konfigurálhatók egy alkalmazáshoz a ConfigureHttpJsonOptionsmeghívásával. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
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
// }
Mivel a mezők szerepelnek, az előző kód beolvassa a NameField értéket, és belefoglalja a kimeneti JSON-fájlba.
JSON-szerializálási beállítások konfigurálása végponthoz
A végpont szerializálási beállításainak konfigurálásához hívja meg Results.Json, és adjon át neki egy JsonSerializerOptions objektumot, ahogyan az az alábbi példában látható:
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
// }
Alternatívaként használja a WriteAsJsonAsync túlterhelését, amely elfogadja a JsonSerializerOptions objektumot. Az alábbi példa ezt a túlterhelést használja a kimeneti JSON formázásához:
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
// }