Sdílet prostřednictvím


Jak vytvářet odpovědi v aplikacích Minimal API

Poznámka

Toto není nejnovější verze tohoto článku Aktuální verzi najdete ve verzi .NET 10 tohoto článku.

Varování

Tato verze ASP.NET Core již není podporována. Pro více informací se podívejte na Zásady podpory .NET a .NET Core. Pro aktuální vydání viz verze tohoto článku pro .NET 9.

Tento článek vysvětluje, jak vytvářet odpovědi pro minimální koncové body rozhraní API v ASP.NET Core. Minimální rozhraní API poskytují několik způsobů, jak vrátit data a stavové kódy HTTP.

Minimální koncové body podporují následující typy návratových hodnot:

  1. string - To zahrnuje Task<string> a ValueTask<string>.
  2. T (jakýkoli jiný typ) - To zahrnuje Task<T> a ValueTask<T>.
  3. IResult založené - To zahrnuje Task<IResult> a ValueTask<IResult>.

Důležité

Počínaje ASP.NET Core 10 už známé koncové body rozhraní API při ověřování cookie nepřesměrovávají na přihlašovací stránky. Místo toho vrátí stavové kódy 401/403. Podrobnosti najdete v tématu Chování ověřování koncových bodů rozhraní API v ASP.NET Core.

string vratné hodnoty

Chování Typ obsahu
Framework zapíše řetězec přímo do odpovědi. text/plain

Zvažte následující obslužnou rutinu trasy, která vrací text Hello world.

app.MapGet("/hello", () => "Hello World");

Stavový kód 200 je vrácen s hlavičkou text/plain Content-Type a následujícím obsahem.

Hello World

T (Jakýkoli jiný typ) návratové hodnoty

Chování Typ obsahu
Framework serializuje odpověď do JSON formátu. application/json

Zvažte následující obslužnou funkci trasy, která vrací anonymní typ obsahující vlastnost řetězce Message.

app.MapGet("/hello", () => new { Message = "Hello World" });

Stavový kód 200 je vrácen s hlavičkou application/json Content-Type a následujícím obsahem.

{"message":"Hello World"}

IResult vratné hodnoty

Chování Typ obsahu
Framework volá IResult.ExecuteAsync. Určeno implementací IResult

IResult rozhraní definuje kontrakt, který představuje výsledek HTTP koncového bodu. Statická třída Results a statická třída TypedResults jsou použity k vytváření různých objektů IResult, které představují různé typy odpovědí.

TypedResults vs. výsledky

Statické třídy Results a TypedResults poskytují podobné sady pomocníků pro výsledky. Třída TypedResults je typový ekvivalent Results třídy. Návratový typ Results pomocníků je IResult, zatímco typ návratu každého TypedResults pomocníka je jedním z typů implementace IResult. Rozdíl znamená, že pro Results pomocníky je potřebná konverze, když je vyžadován konkrétní typ, například pro jednotkové testování. Typy implementace jsou definovány v Microsoft.AspNetCore.Http.HttpResults oboru názvů.

Vrácení TypedResults místo Results má následující výhody:

Zvažte následující koncový bod, pro který je vytvořen stavový kód 200 OK s očekávanou odpovědí ve formátu JSON.

app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
    .Produces<Message>();

Aby bylo možné správně dokumentovat tento koncový bod, je volána metoda rozšíření Produces. Není však nutné volat Produces , pokud TypedResults se používá místo Results, jak je znázorněno v následujícím kódu. TypedResults automaticky poskytuje metadata pro koncový bod.

app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));

Pro více informací o popisu typu odpovědi si přečtěte OpenAPI support in minimal APIs.

Příklady typů výsledků testování najdete v dokumentaci k testování.

Vzhledem k tomu, že všechny metody na Results vracejí IResult ve své signatuře, kompilátor automaticky usuzuje, že tento typ návratové hodnoty je typem návratové hodnoty delegáta požadavku při vracení různých výsledků z jednoho koncového bodu. TypedResults vyžaduje použití Results<T1, TN> od takových delegátů.

Následující metoda se zkompiluje, protože jak Results.Ok, tak Results.NotFound jsou deklarovány jako vracející IResult, přestože skutečné konkrétní typy vrácených objektů jsou různé.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Následující metoda se nedaří zkompilovat, protože TypedResults.Ok a TypedResults.NotFound jsou deklarovány jako vracející různé typy, a kompilátor se nepokouší odvodit nejlépe odpovídající typ.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
     await db.Todos.FindAsync(id)
     is Todo todo
        ? TypedResults.Ok(todo)
        : TypedResults.NotFound());

Chcete-li použít TypedResults, návratový typ musí být plně deklarován; pokud je metoda asynchronní, deklarace vyžaduje, aby byl návratový typ zabalen do Task<>. Použití TypedResults je sice rozvláčnější, ale to je daň za to, že informace o typu jsou staticky dostupné a mohou se samy popsat pro 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());

Výsledky<TResult1, TResultN>

Použijte Results<TResult1, TResultN> jako návratový typ obslužné rutiny koncového bodu místo IResult když:

  • Z koncového bodu je vráceno několik typů implementace.
  • Statická třída TypedResult se používá k vytvoření objektů IResult.

Tato alternativa je lepší než vrácení IResult, protože typy obecných sjednocení automaticky uchovávají metadata koncového bodu. A protože typy sjednocení implementují implicitní operátory převodu, kompilátor může automaticky převést typy uvedené v obecných argumentech na instanci typu sjednocení.

Tím se přidává další výhoda v podobě kontroly při kompilaci s cílem zkontrolovat, že obslužná funkce skutečně vrací pouze ty výsledky, které deklaruje, že vrací. Pokúsíte-li se vrátit typ, který není deklarován jako jeden z generických argumentů Results<>, dojde k chybě při kompilaci.

Zvažte následující koncový bod, pro který je vrácen stavový kód 400 BadRequest, když je orderId větší než 999. Jinak vytvoří 200 OK s očekávaným obsahem.

app.MapGet("/orders/{orderId}", IResult (int orderId)
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
    .Produces(400)
    .Produces<Order>();

Aby bylo možné tento koncový bod správně zdokumentovat, je volána metoda rozšíření Produces. Vzhledem k tomu, že pomocník TypedResults automaticky zahrnuje metadata pro koncový bod, můžete místo toho vrátit typ sjednocení Results<T1, Tn>, jak je ukázáno v následujícím kódu.

app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));

Vestavěné výsledky

Běžní pomocníci výsledků existují ve statických třídách Results a TypedResults. Vrátit TypedResults je preferováno před vrácením Results. Pro více informací vizte TypedResults vs Results.

Následující části ukazují použití běžných výsledkových pomocníků.

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

WriteAsJsonAsync je alternativní způsob, jak vrátit JSON.

app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
    (new { Message = "Hello World" }));

Vlastní stavový kód

app.MapGet("/405", () => Results.StatusCode(405));

Vnitřní chyba serveru

app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));

Předchozí příklad vrací stavový kód 500.

Problém a ověření problému

app.MapGet("/problem", () =>
{
    var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions", 
                                                extensions: extensions);
});

Přizpůsobení odpovědí na chyby ověřování pomocí IProblemDetailsService

Přizpůsobte odpovědi na chyby z minimální logiky ověřování rozhraní API pomocí IProblemDetailsService implementace. Zaregistrujte tuto službu v kolekci služeb vaší aplikace, abyste povolili konzistentnější odpovědi na chyby specifické pro uživatele. Podpora minimálního ověření rozhraní API byla zavedena v ASP.NET Core v .NET 10.

Implementace vlastních odpovědí na chyby ověřování:

  • Implementace IProblemDetailsService nebo použití výchozí implementace
  • Registrace služby v kontejneru DI
  • Ověřovací systém automaticky používá zaregistrovanou službu k formátování odpovědí na chyby ověřování.

Následující příklad ukazuje, jak zaregistrovat a nakonfigurovat IProblemDetailsService odpovědi na chyby ověření přizpůsobení:

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();
        }
    };
});

Pokud dojde k chybě ověření, IProblemDetailsService použije se k vygenerování odpovědi na chybu, včetně všech vlastních nastavení přidaných do zpětného CustomizeProblemDetails volání.

Kompletní příklad aplikace najdete v ukázkové aplikaci s minimálním rozhraním API , která ukazuje, jak přizpůsobit odpovědi na chyby ověřování pomocí rozhraní IProblemDetailsService API v ASP.NET Core Minimal API.

Text

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();

Přetížení umožňují přístup k základnímu streamu HTTP odpovědi bez vyrovnávací paměti. Následující příklad používá ImageSharp ke zmenšení velikosti určeného obrázku.

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);
}

Následující příklad přenáší obraz z 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");
});

Následující příklad streamuje video z 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);
});

události Server-Sent (SSE)

Rozhraní API TypedResults.ServerSentEvents podporuje vrácení výsledku ServerSentEvents .

Server-Sent Events je technologie nabízených oznámení serveru, která umožňuje serveru odesílat stream zpráv událostí klientovi přes jedno připojení HTTP. V .NET jsou zprávy událostí reprezentovány jako SseItem<T> objekty, které mohou obsahovat typ události, ID a datovou část typu T.

TypedResults třída má statickou metodu s názvem ServerSentEvents, kterou lze použít k vrácení výsledkuServerSentEvents. Prvním parametrem této metody je IAsyncEnumerable<SseItem<T>>, který představuje datový proud zpráv událostí určených k odeslání klientovi.

Následující příklad ukazuje, jak pomocí TypedResults.ServerSentEvents rozhraní API vrátit stream událostí srdeční frekvence jako objekty JSON klientovi:

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));
});

Další informace najdete v ukázkové aplikaci s minimálním rozhraním API , která pomocí TypedResults.ServerSentEvents rozhraní API vrátí datový proud událostí srdeční frekvence jako řetězec ServerSentEventsa objekty JSON klientovi.

Přesměrování

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Soubor

app.MapGet("/download", () => Results.File("myfile.text"));

HttpResult rozhraní

Následující rozhraní v oboru názvů Microsoft.AspNetCore.Http poskytují způsob, jak detekovat typ IResult za běhu, což je běžný vzor v implementacích filtrů.

Zde je příklad filtru, který používá jedno z těchto rozhraní:

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
    };
});

Pro více informací se podívejte na Filtry v aplikacích s minimálním API a typy implementace IResult.

Úprava hlaviček

Pomocí objektu HttpResponse upravte hlavičky odpovědi:

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";
});

Přizpůsobení odpovědí

Aplikace mohou ovládat odpovědi implementací vlastního typu IResult. Následující kód je příkladem typu výsledku HTML.

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);
    }
}

Doporučujeme přidat ke Microsoft.AspNetCore.Http.IResultExtensions rozšiřující metodu, aby byly tyto vlastní výsledky snadněji k nalezení.

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();

Také vlastní typ IResult může poskytnout vlastní anotaci implementací rozhraní IEndpointMetadataProvider. Například následující kód přidává anotaci k předchozímu typu HtmlResult, která popisuje odpověď generovanou koncovým bodem.

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 je implementací IProducesResponseTypeMetadata, která určuje typ obsahu text/html a statusový kód 200 OK produkované odpovědi.

internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
    public Type? Type => null;

    public int StatusCode => 200;

    public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}

Alternativní přístup je použití Microsoft.AspNetCore.Mvc.ProducesAttribute k popisu vytvořené odpovědi. Následující kód mění metodu PopulateMetadata tak, aby používala ProducesAttribute.

public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
    builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}

Nastavení možností serializace JSON

Ve výchozím nastavení používají aplikace typu minimální API Web defaults možnosti při serializaci a deserializaci JSONu.

Nastavit možnosti serializace JSON globálně

Možnosti lze globálně konfigurovat pro aplikaci vyvoláním ConfigureHttpJsonOptions. Následující příklad zahrnuje veřejná pole a formátuje výstup JSON.

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
// }

Protože jsou zahrnuty pole, předchozí kód čte NameField a zahrnuje ho do výstupního JSONu.

Nakonfigurujte možnosti serializace JSON pro koncový bod

Chcete-li konfigurovat možnosti serializace pro koncový bod, zavolejte Results.Json a předejte mu objekt JsonSerializerOptions, jak je uvedeno v následujícím příkladu.

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
// }

Jako alternativu použijte přetížení WriteAsJsonAsync, které přijímá objekt JsonSerializerOptions. Následující příklad používá toto přetížení k formátování výstupního JSONu:

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
// }

Další zdroje

Minimální koncové body podporují následující typy návratových hodnot:

  1. string - To zahrnuje Task<string> a ValueTask<string>.
  2. T (jakýkoli jiný typ) - To zahrnuje Task<T> a ValueTask<T>.
  3. IResult založené - To zahrnuje Task<IResult> a ValueTask<IResult>.

string vratné hodnoty

Chování Typ obsahu
Framework zapíše řetězec přímo do odpovědi. text/plain

Zvažte následující obslužnou rutinu trasy, která vrací text Hello world.

app.MapGet("/hello", () => "Hello World");

Stavový kód 200 je vrácen s hlavičkou text/plain Content-Type a následujícím obsahem.

Hello World

T (Jakýkoli jiný typ) návratové hodnoty

Chování Typ obsahu
Framework serializuje odpověď do JSON formátu. application/json

Zvažte následující obslužnou funkci trasy, která vrací anonymní typ obsahující vlastnost řetězce Message.

app.MapGet("/hello", () => new { Message = "Hello World" });

Stavový kód 200 je vrácen s hlavičkou application/json Content-Type a následujícím obsahem.

{"message":"Hello World"}

IResult vratné hodnoty

Chování Typ obsahu
Framework volá IResult.ExecuteAsync. Určeno implementací IResult

IResult rozhraní definuje kontrakt, který představuje výsledek HTTP koncového bodu. Statická třída Results a statická třída TypedResults jsou použity k vytváření různých objektů IResult, které představují různé typy odpovědí.

TypedResults vs. výsledky

Statické třídy Results a TypedResults poskytují podobné sady pomocníků pro výsledky. Třída TypedResults je typový ekvivalent Results třídy. Návratový typ Results pomocníků je IResult, zatímco typ návratu každého TypedResults pomocníka je jedním z typů implementace IResult. Rozdíl znamená, že pro Results pomocníky je potřebná konverze, když je vyžadován konkrétní typ, například pro jednotkové testování. Typy implementace jsou definovány v Microsoft.AspNetCore.Http.HttpResults oboru názvů.

Vrácení TypedResults místo Results má následující výhody:

Zvažte následující koncový bod, pro který je vytvořen stavový kód 200 OK s očekávanou odpovědí ve formátu JSON.

app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
    .Produces<Message>();

Aby bylo možné správně dokumentovat tento koncový bod, je volána metoda rozšíření Produces. Není však nutné volat Produces , pokud TypedResults se používá místo Results, jak je znázorněno v následujícím kódu. TypedResults automaticky poskytuje metadata pro koncový bod.

app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));

Pro více informací o popisu typu odpovědi si přečtěte OpenAPI support in minimal APIs.

Jak již bylo zmíněno, při použití TypedResults není potřeba žádná konverze. Zvažte následující minimální API, které vrací TypedResults třídu

public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
    var todos = await database.Todos.ToArrayAsync();
    return TypedResults.Ok(todos);
}

Následující test kontroluje plný konkrétní typ:

[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);
    });
}

Vzhledem k tomu, že všechny metody na Results vracejí IResult ve své signatuře, kompilátor automaticky usuzuje, že tento typ návratové hodnoty je typem návratové hodnoty delegáta požadavku při vracení různých výsledků z jednoho koncového bodu. TypedResults vyžaduje použití Results<T1, TN> od takových delegátů.

Následující metoda se zkompiluje, protože jak Results.Ok, tak Results.NotFound jsou deklarovány jako vracející IResult, přestože skutečné konkrétní typy vrácených objektů jsou různé.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Následující metoda se nedaří zkompilovat, protože TypedResults.Ok a TypedResults.NotFound jsou deklarovány jako vracející různé typy, a kompilátor se nepokouší odvodit nejlépe odpovídající typ.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
     await db.Todos.FindAsync(id)
     is Todo todo
        ? TypedResults.Ok(todo)
        : TypedResults.NotFound());

Pro použití TypedResults musí být návratový typ plně deklarován, což v případě asynchronního volání vyžaduje obal Task<>. Použití TypedResults je sice rozvláčnější, ale to je daň za to, že informace o typu jsou staticky dostupné a mohou se samy popsat pro 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());

Výsledky<TResult1, TResultN>

Použijte Results<TResult1, TResultN> jako návratový typ obslužné rutiny koncového bodu místo IResult když:

  • Z koncového bodu je vráceno několik typů implementace.
  • Statická třída TypedResult se používá k vytvoření objektů IResult.

Tato alternativa je lepší než vrácení IResult, protože typy obecných sjednocení automaticky uchovávají metadata koncového bodu. A protože typy sjednocení implementují implicitní operátory převodu, kompilátor může automaticky převést typy uvedené v obecných argumentech na instanci typu sjednocení.

Tím se přidává další výhoda v podobě kontroly při kompilaci s cílem zkontrolovat, že obslužná funkce skutečně vrací pouze ty výsledky, které deklaruje, že vrací. Pokúsíte-li se vrátit typ, který není deklarován jako jeden z generických argumentů Results<>, dojde k chybě při kompilaci.

Zvažte následující koncový bod, pro který je vrácen stavový kód 400 BadRequest, když je orderId větší než 999. Jinak vytvoří 200 OK s očekávaným obsahem.

app.MapGet("/orders/{orderId}", IResult (int orderId)
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
    .Produces(400)
    .Produces<Order>();

Aby bylo možné tento koncový bod správně zdokumentovat, je volána metoda rozšíření Produces. Vzhledem k tomu, že pomocník TypedResults automaticky zahrnuje metadata pro koncový bod, můžete místo toho vrátit typ sjednocení Results<T1, Tn>, jak je ukázáno v následujícím kódu.

app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId)
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));

Vestavěné výsledky

Běžní pomocníci výsledků existují ve statických třídách Results a TypedResults. Vrátit TypedResults je preferováno před vrácením Results. Pro více informací vizte TypedResults vs Results.

Následující části ukazují použití běžných výsledkových pomocníků.

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

WriteAsJsonAsync je alternativní způsob, jak vrátit JSON.

app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
    (new { Message = "Hello World" }));

Vlastní stavový kód

app.MapGet("/405", () => Results.StatusCode(405));

Vnitřní chyba serveru

app.MapGet("/500", () => Results.InternalServerError("Something went wrong!"));

Předchozí příklad vrací stavový kód 500.

Problém a ověření problému

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"));

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();

Přetížení umožňují přístup k základnímu streamu HTTP odpovědi bez vyrovnávací paměti. Následující příklad používá ImageSharp ke zmenšení velikosti určeného obrázku.

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);
}

Následující příklad přenáší obraz z 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");
});

Následující příklad streamuje video z 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);
});

Přesměrování

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Soubor

app.MapGet("/download", () => Results.File("myfile.text"));

HttpResult rozhraní

Následující rozhraní v oboru názvů Microsoft.AspNetCore.Http poskytují způsob, jak detekovat typ IResult za běhu, což je běžný vzor v implementacích filtrů.

Zde je příklad filtru, který používá jedno z těchto rozhraní:

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
    };
});

Pro více informací se podívejte na Filtry v aplikacích s minimálním API a typy implementace IResult.

Úprava hlaviček

Pomocí objektu HttpResponse upravte hlavičky odpovědi:

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";
});

Přizpůsobení odpovědí

Aplikace mohou ovládat odpovědi implementací vlastního typu IResult. Následující kód je příkladem typu výsledku HTML.

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);
    }
}

Doporučujeme přidat ke Microsoft.AspNetCore.Http.IResultExtensions rozšiřující metodu, aby byly tyto vlastní výsledky snadněji k nalezení.

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();

Také vlastní typ IResult může poskytnout vlastní anotaci implementací rozhraní IEndpointMetadataProvider. Například následující kód přidává anotaci k předchozímu typu HtmlResult, která popisuje odpověď generovanou koncovým bodem.

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 je implementací IProducesResponseTypeMetadata, která určuje typ obsahu text/html a statusový kód 200 OK produkované odpovědi.

internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
    public Type? Type => null;

    public int StatusCode => 200;

    public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}

Alternativní přístup je použití Microsoft.AspNetCore.Mvc.ProducesAttribute k popisu vytvořené odpovědi. Následující kód mění metodu PopulateMetadata tak, aby používala ProducesAttribute.

public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
    builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}

Nastavení možností serializace JSON

Ve výchozím nastavení používají aplikace typu minimální API Web defaults možnosti při serializaci a deserializaci JSONu.

Nastavit možnosti serializace JSON globálně

Možnosti lze globálně konfigurovat pro aplikaci vyvoláním ConfigureHttpJsonOptions. Následující příklad zahrnuje veřejná pole a formátuje výstup JSON.

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
// }

Protože jsou zahrnuty pole, předchozí kód čte NameField a zahrnuje ho do výstupního JSONu.

Nakonfigurujte možnosti serializace JSON pro koncový bod

Chcete-li konfigurovat možnosti serializace pro koncový bod, zavolejte Results.Json a předejte mu objekt JsonSerializerOptions, jak je uvedeno v následujícím příkladu.

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
// }

Jako alternativu použijte přetížení WriteAsJsonAsync, které přijímá objekt JsonSerializerOptions. Následující příklad používá toto přetížení k formátování výstupního JSONu:

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
// }

Další zdroje

Minimální koncové body podporují následující typy návratových hodnot:

  1. string - To zahrnuje Task<string> a ValueTask<string>.
  2. T (jakýkoli jiný typ) - To zahrnuje Task<T> a ValueTask<T>.
  3. IResult založené - To zahrnuje Task<IResult> a ValueTask<IResult>.

string vratné hodnoty

Chování Typ obsahu
Framework zapíše řetězec přímo do odpovědi. text/plain

Zvažte následující obslužnou rutinu trasy, která vrací text Hello world.

app.MapGet("/hello", () => "Hello World");

Stavový kód 200 je vrácen s hlavičkou text/plain Content-Type a následujícím obsahem.

Hello World

T (Jakýkoli jiný typ) návratové hodnoty

Chování Typ obsahu
Framework serializuje odpověď do JSON formátu. application/json

Zvažte následující obslužnou funkci trasy, která vrací anonymní typ obsahující vlastnost řetězce Message.

app.MapGet("/hello", () => new { Message = "Hello World" });

Stavový kód 200 je vrácen s hlavičkou application/json Content-Type a následujícím obsahem.

{"message":"Hello World"}

IResult vratné hodnoty

Chování Typ obsahu
Framework volá IResult.ExecuteAsync. Určeno implementací IResult

IResult rozhraní definuje kontrakt, který představuje výsledek HTTP koncového bodu. Statická třída Results a statická třída TypedResults jsou použity k vytváření různých objektů IResult, které představují různé typy odpovědí.

TypedResults vs. výsledky

Statické třídy Results a TypedResults poskytují podobné sady pomocníků pro výsledky. Třída TypedResults je typový ekvivalent Results třídy. Návratový typ Results pomocníků je IResult, zatímco typ návratu každého TypedResults pomocníka je jedním z typů implementace IResult. Rozdíl znamená, že pro Results pomocníky je potřebná konverze, když je vyžadován konkrétní typ, například pro jednotkové testování. Typy implementace jsou definovány v Microsoft.AspNetCore.Http.HttpResults oboru názvů.

Vrácení TypedResults místo Results má následující výhody:

Zvažte následující koncový bod, pro který je vytvořen stavový kód 200 OK s očekávanou odpovědí ve formátu JSON.

app.MapGet("/hello", () => Results.Ok(new Message() { Text = "Hello World!" }))
    .Produces<Message>();

Aby bylo možné správně dokumentovat tento koncový bod, je volána metoda rozšíření Produces. Není však nutné volat Produces , pokud TypedResults se používá místo Results, jak je znázorněno v následujícím kódu. TypedResults automaticky poskytuje metadata pro koncový bod.

app.MapGet("/hello2", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));

Pro více informací o popisu typu odpovědi si přečtěte OpenAPI support in minimal APIs.

Jak již bylo zmíněno, při použití TypedResults není potřeba žádná konverze. Zvažte následující minimální API, které vrací TypedResults třídu

public static async Task<Ok<Todo[]>> GetAllTodos(TodoGroupDbContext database)
{
    var todos = await database.Todos.ToArrayAsync();
    return TypedResults.Ok(todos);
}

Následující test kontroluje plný konkrétní typ:

[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);
    });
}

Vzhledem k tomu, že všechny metody na Results vracejí IResult ve své signatuře, kompilátor automaticky usuzuje, že tento typ návratové hodnoty je typem návratové hodnoty delegáta požadavku při vracení různých výsledků z jednoho koncového bodu. TypedResults vyžaduje použití Results<T1, TN> od takových delegátů.

Následující metoda se zkompiluje, protože jak Results.Ok, tak Results.NotFound jsou deklarovány jako vracející IResult, přestože skutečné konkrétní typy vrácených objektů jsou různé.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

Následující metoda se nedaří zkompilovat, protože TypedResults.Ok a TypedResults.NotFound jsou deklarovány jako vracející různé typy, a kompilátor se nepokouší odvodit nejlépe odpovídající typ.

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
     await db.Todos.FindAsync(id)
     is Todo todo
        ? TypedResults.Ok(todo)
        : TypedResults.NotFound());

Pro použití TypedResults musí být návratový typ plně deklarován, což v případě asynchronního volání vyžaduje obal Task<>. Použití TypedResults je sice rozvláčnější, ale to je daň za to, že informace o typu jsou staticky dostupné a mohou se samy popsat pro 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());

Výsledky<TResult1, TResultN>

Použijte Results<TResult1, TResultN> jako návratový typ obslužné rutiny koncového bodu místo IResult když:

  • Z koncového bodu je vráceno několik typů implementace.
  • Statická třída TypedResult se používá k vytvoření objektů IResult.

Tato alternativa je lepší než vrácení IResult, protože typy obecných sjednocení automaticky uchovávají metadata koncového bodu. A protože typy sjednocení implementují implicitní operátory převodu, kompilátor může automaticky převést typy uvedené v obecných argumentech na instanci typu sjednocení.

Tím se přidává další výhoda v podobě kontroly při kompilaci s cílem zkontrolovat, že obslužná funkce skutečně vrací pouze ty výsledky, které deklaruje, že vrací. Pokúsíte-li se vrátit typ, který není deklarován jako jeden z generických argumentů Results<>, dojde k chybě při kompilaci.

Zvažte následující koncový bod, pro který je vrácen stavový kód 400 BadRequest, když je orderId větší než 999. Jinak vytvoří 200 OK s očekávaným obsahem.

app.MapGet("/orders/{orderId}", IResult (int orderId)
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)))
    .Produces(400)
    .Produces<Order>();

Aby bylo možné tento koncový bod správně zdokumentovat, je volána metoda rozšíření Produces. Vzhledem k tomu, že pomocník TypedResults automaticky zahrnuje metadata pro koncový bod, můžete místo toho vrátit typ sjednocení Results<T1, Tn>, jak je ukázáno v následujícím kódu.

app.MapGet("/orders/{orderId}", Results<BadRequest, Ok<Order>> (int orderId) 
    => orderId > 999 ? TypedResults.BadRequest() : TypedResults.Ok(new Order(orderId)));

Vestavěné výsledky

Běžní pomocníci výsledků existují ve statických třídách Results a TypedResults. Vrátit TypedResults je preferováno před vrácením Results. Pro více informací vizte TypedResults vs Results.

Následující části ukazují použití běžných výsledkových pomocníků.

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

WriteAsJsonAsync je alternativní způsob, jak vrátit JSON.

app.MapGet("/", (HttpContext context) => context.Response.WriteAsJsonAsync
    (new { Message = "Hello World" }));

Vlastní stavový kód

app.MapGet("/405", () => Results.StatusCode(405));

Text

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();

Přetížení umožňují přístup k základnímu streamu HTTP odpovědi bez vyrovnávací paměti. Následující příklad používá ImageSharp ke zmenšení velikosti určeného obrázku.

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);
}

Následující příklad přenáší obraz z 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");
});

Následující příklad streamuje video z 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);
});

Přesměrování

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Soubor

app.MapGet("/download", () => Results.File("myfile.text"));

HttpResult rozhraní

Následující rozhraní v oboru názvů Microsoft.AspNetCore.Http poskytují způsob, jak detekovat typ IResult za běhu, což je běžný vzor v implementacích filtrů.

Zde je příklad filtru, který používá jedno z těchto rozhraní:

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
    };
});

Pro více informací se podívejte na Filtry v aplikacích s minimálním API a typy implementace IResult.

Přizpůsobení odpovědí

Aplikace mohou ovládat odpovědi implementací vlastního typu IResult. Následující kód je příkladem typu výsledku HTML.

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);
    }
}

Doporučujeme přidat ke Microsoft.AspNetCore.Http.IResultExtensions rozšiřující metodu, aby byly tyto vlastní výsledky snadněji k nalezení.

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();

Také vlastní typ IResult může poskytnout vlastní anotaci implementací rozhraní IEndpointMetadataProvider. Například následující kód přidává anotaci k předchozímu typu HtmlResult, která popisuje odpověď generovanou koncovým bodem.

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 je implementací IProducesResponseTypeMetadata, která určuje typ obsahu text/html a statusový kód 200 OK produkované odpovědi.

internal sealed class ProducesHtmlMetadata : IProducesResponseTypeMetadata
{
    public Type? Type => null;

    public int StatusCode => 200;

    public IEnumerable<string> ContentTypes { get; } = new[] { MediaTypeNames.Text.Html };
}

Alternativní přístup je použití Microsoft.AspNetCore.Mvc.ProducesAttribute k popisu vytvořené odpovědi. Následující kód mění metodu PopulateMetadata tak, aby používala ProducesAttribute.

public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder)
{
    builder.Metadata.Add(new ProducesAttribute(MediaTypeNames.Text.Html));
}

Nastavení možností serializace JSON

Ve výchozím nastavení používají aplikace typu minimální API Web defaults možnosti při serializaci a deserializaci JSONu.

Nastavit možnosti serializace JSON globálně

Možnosti lze globálně konfigurovat pro aplikaci vyvoláním ConfigureHttpJsonOptions. Následující příklad zahrnuje veřejná pole a formátuje výstup JSON.

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
// }

Protože jsou zahrnuty pole, předchozí kód čte NameField a zahrnuje ho do výstupního JSONu.

Nakonfigurujte možnosti serializace JSON pro koncový bod

Chcete-li konfigurovat možnosti serializace pro koncový bod, zavolejte Results.Json a předejte mu objekt JsonSerializerOptions, jak je uvedeno v následujícím příkladu.

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
// }

Jako alternativu použijte přetížení WriteAsJsonAsync, které přijímá objekt JsonSerializerOptions. Následující příklad používá toto přetížení k formátování výstupního JSONu:

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
// }

Další zdroje