Minimal API uygulamalarında yanıt oluşturma

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Uyarı

ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

En düşük uç noktalar aşağıdaki dönüş değeri türlerini destekler:

  1. string- Buna ve ValueTask<string>dahildirTask<string>.
  2. T(Başka herhangi bir tür) - Ve'i ValueTask<T>içerirTask<T>.
  3. IResultbased - Buna ve ValueTask<IResult>dahildirTask<IResult>.

string dönüş değerleri

Davranış İçerik Türü
Çerçeve, dizeyi doğrudan yanıta yazar. text/plain

Bir metin döndüren Hello world aşağıdaki yol işleyicisini göz önünde bulundurun.

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

Durum 200 kodu content-Type üst bilgisi ve aşağıdaki içerikle text/plain döndürülür.

Hello World

T (Başka bir tür) değer döndürür

Davranış İçerik Türü
JSON çerçevesi yanıtı seri hale getirmektedir. application/json

Dize özelliği içeren Message anonim bir tür döndüren aşağıdaki yol işleyicisini göz önünde bulundurun.

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

Durum 200 kodu content-Type üst bilgisi ve aşağıdaki içerikle application/json döndürülür.

{"message":"Hello World"}

IResult dönüş değerleri

Davranış İçerik Türü
Çerçeve IResult.ExecuteAsync'i çağırır. Uygulama tarafından karar verilmiştir IResult .

Arabirimi, IResult HTTP uç noktasının sonucunu temsil eden bir sözleşme tanımlar. Statik Results sınıfı ve statik TypedResults , farklı yanıt türlerini temsil eden çeşitli IResult nesneler oluşturmak için kullanılır.

TypedResults ve Sonuçlar

Results ve TypedResults statik sınıfları benzer sonuç yardımcı kümeleri sağlar. sınıfı TypedResults , sınıfın türüne eşdeğerdir Results . Ancak, yardımcıların Results dönüş türü olurken IResult, her TypedResults yardımcının dönüş türü uygulama türlerinden IResult biridir. Aradaki fark, yardımcılar için Results somut tür gerektiğinde ,örneğin birim testi için bir dönüştürme gerektiği anlamına gelir. Uygulama türleri ad alanında Microsoft.AspNetCore.Http.HttpResults tanımlanır.

Döndürmek TypedResults yerine Results aşağıdaki avantajlara sahiptir:

  • TypedResults yardımcıları, kod okunabilirliğini, birim testini geliştirebilen ve çalışma zamanı hatalarının olasılığını azaltabilen kesin olarak belirlenmiş nesneler döndürür.
  • Uygulama türü , uç noktayı açıklamak üzere OpenAPI için yanıt türü meta verilerini otomatik olarak sağlar.

Beklenen JSON yanıtına sahip bir 200 OK durum kodunun oluşturulduğu aşağıdaki uç noktayı göz önünde bulundurun.

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

Bu uç noktayı doğru bir şekilde belgelendirebilmek için uzantılar yöntemi Produces çağrılır. Ancak, aşağıdaki kodda gösterildiği gibi yerine Resultskullanılırsa TypedResults çağrısı Produces yapılması gerekmez. TypedResults otomatik olarak uç nokta için meta verileri sağlar.

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

Yanıt türünü açıklama hakkında daha fazla bilgi için bkz . Minimum API'lerde OpenAPI desteği.

Daha önce belirtildiği gibi, kullanırken TypedResultsbir dönüştürme gerekli değildir. Bir sınıf döndüren TypedResults aşağıdaki en düşük API'yi göz önünde bulundurun

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

Aşağıdaki test tam somut türü denetler:

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

üzerindeki tüm yöntemler Results imzalarını döndürdiğinden IResult , derleyici tek bir uç noktadan farklı sonuçlar döndürürken istek temsilcisi dönüş türü olarak bunu otomatik olarak çıkarsar. TypedResults bu tür temsilcilerden kullanımını Results<T1, TN> gerektirir.

Döndürülen nesnelerin gerçek somut türleri farklı olsa bile, hem hem de Results.Ok Results.NotFound döndüren IResultolarak bildirildiğinden aşağıdaki yöntem derlenir:

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

Aşağıdaki yöntem derlenmez, çünkü TypedResults.Ok ve TypedResults.NotFound farklı türler döndüren olarak bildirilir ve derleyici en iyi eşleşen türü çıkarsamaya çalışmaz:

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

kullanmak TypedResultsiçin dönüş türü tam olarak bildirilmelidir; zaman uyumsuz sarmalayıcı gerektirdiğinde Task<> . Kullanmak TypedResults daha ayrıntılıdır, ancak tür bilgilerinin statik olarak kullanılabilir olması ve bu nedenle OpenAPI'ye kendi kendini açıklayabilme özelliğine sahip olması için bu bir avantajdır:

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

Sonuçlar<TResult1, TResultN>

Aşağıdakiler yerine IResult uç nokta işleyicisi dönüş türü olarak kullanınResults<TResult1, TResultN>:

  • Uç nokta işleyicisinden birden çok IResult uygulama türü döndürülür.
  • Statik TypedResult sınıf nesneleri oluşturmak IResult için kullanılır.

Bu alternatif, genel birleşim türleri otomatik olarak uç nokta meta verilerini koruduğundan döndürmekten IResult daha iyidir. Results<TResult1, TResultN> Birleşim türleri örtük atama işleçleri uyguladığından, derleyici genel bağımsız değişkenlerde belirtilen türleri otomatik olarak birleşim türünün bir örneğine dönüştürebilir.

Bu, bir yol işleyicisinin aslında yalnızca bildirdiği sonuçları döndürdüğüne ilişkin derleme zamanı denetimi sağlamanın ek avantajına sahiptir. Derleme hatasıyla sonuçlanması için genel bağımsız değişkenlerden biri olarak bildirlenmemiş bir tür döndürülmeye Results<> çalışılıyor.

değerinden büyük 999olduğunda orderId durum kodunun döndürüldiği aşağıdaki uç noktayı 400 BadRequest göz önünde bulundurun. Aksi takdirde, beklenen içeriğe sahip bir 200 OK oluşturur.

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

Bu uç noktayı doğru bir şekilde belgelendirebilmek için uzantı yöntemi Produces çağrılır. Ancak, TypedResults yardımcı uç noktanın meta verilerini otomatik olarak içerdiğinden, aşağıdaki kodda gösterildiği gibi birleşim türünü döndürebilirsiniz Results<T1, Tn> .

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

Yerleşik sonuçlar

ve statik sınıflarında Results TypedResults ortak sonuç yardımcıları vardır. dönüş TypedResults , döndürmek Resultsiçin tercih edilir. Daha fazla bilgi için bkz . TypedResults vs Results.

Aşağıdaki bölümlerde ortak sonuç yardımcılarının kullanımı gösterilmektedir.

JSON

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

WriteAsJsonAsync JSON döndürmenin alternatif bir yoludur:

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

Özel Durum Kodu

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

İç Sunucu Hatası

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

Yukarıdaki örnek 500 durum kodu döndürür.

Metin

app.MapGet("/text", () => Results.Text("This is some text"));

Akış

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 aşırı yüklemeler, arabelleğe almadan temel alınan HTTP yanıt akışına erişim sağlar. Aşağıdaki örnek, belirtilen görüntünün daha küçük bir boyutunu döndürmek için ImageSharp'ı kullanır:

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

Aşağıdaki örnek, Azure Blob depolamadan bir görüntü akışı yapar:

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

Aşağıdaki örnek, Azure Blob'dan video akışı yapar:

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

Yönlendir

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

Dosya

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

HttpResult arabirimleri

Ad alanında Microsoft.AspNetCore.Http aşağıdaki arabirimler, filtre uygulamalarında yaygın olarak kullanılan bir desen olan çalışma zamanında türü algılamak IResult için bir yol sağlar:

Aşağıda şu arabirimlerden birini kullanan bir filtre örneği verilmiştir:

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

Daha fazla bilgi için bkz . Minimal API uygulamalarındaki filtreler ve IResult uygulama türleri.

Yanıtları özelleştirme

Uygulamalar özel IResult bir tür uygulayarak yanıtları denetleyebilir. Aşağıdaki kod, BIR HTML sonuç türü örneğidir:

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

Bu özel sonuçları daha bulunabilir hale getirmek için öğesine Microsoft.AspNetCore.Http.IResultExtensions bir uzantı yöntemi eklemenizi öneririz.

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

Ayrıca, özel IResult bir tür arabirimini uygulayarak IEndpointMetadataProvider kendi ek açıklamasını sağlayabilir. Örneğin, aşağıdaki kod önceki türe HtmlResult uç nokta tarafından oluşturulan yanıtı açıklayan bir ek açıklama ekler.

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, oluşturulan yanıt içerik türünü text/html ve durum kodunu 200 OKtanımlayan bir uygulamasıdırIProducesResponseTypeMetadata.

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

    public int StatusCode => 200;

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

Alternatif bir yaklaşım, üretilen yanıtı açıklamak için öğesini Microsoft.AspNetCore.Mvc.ProducesAttribute kullanmaktır. Aşağıdaki kod yöntemini kullanacak ProducesAttributeşekilde değiştirirPopulateMetadata.

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

JSON serileştirme seçeneklerini yapılandırma

Varsayılan olarak, minimum API uygulamaları JSON serileştirme ve seri durumdan çıkarma sırasında seçenekleri kullanır Web defaults .

JSON serileştirme seçeneklerini genel olarak yapılandırma

Seçenekler, çağrılarak ConfigureHttpJsonOptionsbir uygulama için genel olarak yapılandırılabilir. Aşağıdaki örnek, ortak alanları ve JSON çıkışını biçimlendirmektedir.

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

Alanlar dahil olduğundan, yukarıdaki kod bunu okur NameField ve çıkış JSON'una ekler.

Uç nokta için JSON serileştirme seçeneklerini yapılandırma

Bir uç nokta için serileştirme seçeneklerini yapılandırmak için aşağıdaki örnekte gösterildiği gibi çağırın Results.Json ve nesneye JsonSerializerOptions geçirin:

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

Alternatif olarak, nesnesini kabul JsonSerializerOptions eden bir aşırı yükleme WriteAsJsonAsync kullanın. Aşağıdaki örnek, çıkış JSON'unu biçimlendirmek için bu aşırı yüklemeyi kullanır:

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

Ek Kaynaklar

En düşük uç noktalar aşağıdaki dönüş değeri türlerini destekler:

  1. string- Buna ve ValueTask<string>dahildirTask<string>.
  2. T(Başka herhangi bir tür) - Ve'i ValueTask<T>içerirTask<T>.
  3. IResultbased - Buna ve ValueTask<IResult>dahildirTask<IResult>.

string dönüş değerleri

Davranış İçerik Türü
Çerçeve, dizeyi doğrudan yanıta yazar. text/plain

Bir metin döndüren Hello world aşağıdaki yol işleyicisini göz önünde bulundurun.

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

Durum 200 kodu content-Type üst bilgisi ve aşağıdaki içerikle text/plain döndürülür.

Hello World

T (Başka bir tür) değer döndürür

Davranış İçerik Türü
JSON çerçevesi yanıtı seri hale getirmektedir. application/json

Dize özelliği içeren Message anonim bir tür döndüren aşağıdaki yol işleyicisini göz önünde bulundurun.

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

Durum 200 kodu content-Type üst bilgisi ve aşağıdaki içerikle application/json döndürülür.

{"message":"Hello World"}

IResult dönüş değerleri

Davranış İçerik Türü
Çerçeve IResult.ExecuteAsync'i çağırır. Uygulama tarafından karar verilmiştir IResult .

Arabirimi, IResult HTTP uç noktasının sonucunu temsil eden bir sözleşme tanımlar. Statik Results sınıfı ve statik TypedResults , farklı yanıt türlerini temsil eden çeşitli IResult nesneler oluşturmak için kullanılır.

TypedResults ve Sonuçlar

Results ve TypedResults statik sınıfları benzer sonuç yardımcı kümeleri sağlar. sınıfı TypedResults , sınıfın türüne eşdeğerdir Results . Ancak, yardımcıların Results dönüş türü olurken IResult, her TypedResults yardımcının dönüş türü uygulama türlerinden IResult biridir. Aradaki fark, yardımcılar için Results somut tür gerektiğinde ,örneğin birim testi için bir dönüştürme gerektiği anlamına gelir. Uygulama türleri ad alanında Microsoft.AspNetCore.Http.HttpResults tanımlanır.

Döndürmek TypedResults yerine Results aşağıdaki avantajlara sahiptir:

  • TypedResults yardımcıları, kod okunabilirliğini, birim testini geliştirebilen ve çalışma zamanı hatalarının olasılığını azaltabilen kesin olarak belirlenmiş nesneler döndürür.
  • Uygulama türü , uç noktayı açıklamak üzere OpenAPI için yanıt türü meta verilerini otomatik olarak sağlar.

Beklenen JSON yanıtına sahip bir 200 OK durum kodunun oluşturulduğu aşağıdaki uç noktayı göz önünde bulundurun.

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

Bu uç noktayı doğru bir şekilde belgelendirebilmek için uzantılar yöntemi Produces çağrılır. Ancak, aşağıdaki kodda gösterildiği gibi yerine Resultskullanılırsa TypedResults çağrısı Produces yapılması gerekmez. TypedResults otomatik olarak uç nokta için meta verileri sağlar.

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

Yanıt türünü açıklama hakkında daha fazla bilgi için bkz . Minimum API'lerde OpenAPI desteği.

Daha önce belirtildiği gibi, kullanırken TypedResultsbir dönüştürme gerekli değildir. Bir sınıf döndüren TypedResults aşağıdaki en düşük API'yi göz önünde bulundurun

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

Aşağıdaki test tam somut türü denetler:

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

üzerindeki tüm yöntemler Results imzalarını döndürdiğinden IResult , derleyici tek bir uç noktadan farklı sonuçlar döndürürken istek temsilcisi dönüş türü olarak bunu otomatik olarak çıkarsar. TypedResults bu tür temsilcilerden kullanımını Results<T1, TN> gerektirir.

Döndürülen nesnelerin gerçek somut türleri farklı olsa bile, hem hem de Results.Ok Results.NotFound döndüren IResultolarak bildirildiğinden aşağıdaki yöntem derlenir:

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

Aşağıdaki yöntem derlenmez, çünkü TypedResults.Ok ve TypedResults.NotFound farklı türler döndüren olarak bildirilir ve derleyici en iyi eşleşen türü çıkarsamaya çalışmaz:

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

kullanmak TypedResultsiçin dönüş türü tam olarak bildirilmelidir; zaman uyumsuz sarmalayıcı gerektirdiğinde Task<> . Kullanmak TypedResults daha ayrıntılıdır, ancak tür bilgilerinin statik olarak kullanılabilir olması ve bu nedenle OpenAPI'ye kendi kendini açıklayabilme özelliğine sahip olması için bu bir avantajdır:

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

Sonuçlar<TResult1, TResultN>

Aşağıdakiler yerine IResult uç nokta işleyicisi dönüş türü olarak kullanınResults<TResult1, TResultN>:

  • Uç nokta işleyicisinden birden çok IResult uygulama türü döndürülür.
  • Statik TypedResult sınıf nesneleri oluşturmak IResult için kullanılır.

Bu alternatif, genel birleşim türleri otomatik olarak uç nokta meta verilerini koruduğundan döndürmekten IResult daha iyidir. Results<TResult1, TResultN> Birleşim türleri örtük atama işleçleri uyguladığından, derleyici genel bağımsız değişkenlerde belirtilen türleri otomatik olarak birleşim türünün bir örneğine dönüştürebilir.

Bu, bir yol işleyicisinin aslında yalnızca bildirdiği sonuçları döndürdüğüne ilişkin derleme zamanı denetimi sağlamanın ek avantajına sahiptir. Derleme hatasıyla sonuçlanması için genel bağımsız değişkenlerden biri olarak bildirlenmemiş bir tür döndürülmeye Results<> çalışılıyor.

değerinden büyük 999olduğunda orderId durum kodunun döndürüldiği aşağıdaki uç noktayı 400 BadRequest göz önünde bulundurun. Aksi takdirde, beklenen içeriğe sahip bir 200 OK oluşturur.

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

Bu uç noktayı doğru bir şekilde belgelendirebilmek için uzantı yöntemi Produces çağrılır. Ancak, TypedResults yardımcı uç noktanın meta verilerini otomatik olarak içerdiğinden, aşağıdaki kodda gösterildiği gibi birleşim türünü döndürebilirsiniz Results<T1, Tn> .

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

Yerleşik sonuçlar

ve statik sınıflarında Results TypedResults ortak sonuç yardımcıları vardır. dönüş TypedResults , döndürmek Resultsiçin tercih edilir. Daha fazla bilgi için bkz . TypedResults vs Results.

Aşağıdaki bölümlerde ortak sonuç yardımcılarının kullanımı gösterilmektedir.

JSON

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

WriteAsJsonAsync JSON döndürmenin alternatif bir yoludur:

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

Özel Durum Kodu

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

Metin

app.MapGet("/text", () => Results.Text("This is some text"));

Akış

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 aşırı yüklemeler, arabelleğe almadan temel alınan HTTP yanıt akışına erişim sağlar. Aşağıdaki örnek, belirtilen görüntünün daha küçük bir boyutunu döndürmek için ImageSharp'ı kullanır:

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

Aşağıdaki örnek, Azure Blob depolamadan bir görüntü akışı yapar:

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

Aşağıdaki örnek, Azure Blob'dan video akışı yapar:

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

Yönlendir

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

Dosya

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

HttpResult arabirimleri

Ad alanında Microsoft.AspNetCore.Http aşağıdaki arabirimler, filtre uygulamalarında yaygın olarak kullanılan bir desen olan çalışma zamanında türü algılamak IResult için bir yol sağlar:

Aşağıda şu arabirimlerden birini kullanan bir filtre örneği verilmiştir:

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

Daha fazla bilgi için bkz . Minimal API uygulamalarındaki filtreler ve IResult uygulama türleri.

Yanıtları özelleştirme

Uygulamalar özel IResult bir tür uygulayarak yanıtları denetleyebilir. Aşağıdaki kod, BIR HTML sonuç türü örneğidir:

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

Bu özel sonuçları daha bulunabilir hale getirmek için öğesine Microsoft.AspNetCore.Http.IResultExtensions bir uzantı yöntemi eklemenizi öneririz.

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

Ayrıca, özel IResult bir tür arabirimini uygulayarak IEndpointMetadataProvider kendi ek açıklamasını sağlayabilir. Örneğin, aşağıdaki kod önceki türe HtmlResult uç nokta tarafından oluşturulan yanıtı açıklayan bir ek açıklama ekler.

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, oluşturulan yanıt içerik türünü text/html ve durum kodunu 200 OKtanımlayan bir uygulamasıdırIProducesResponseTypeMetadata.

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

    public int StatusCode => 200;

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

Alternatif bir yaklaşım, üretilen yanıtı açıklamak için öğesini Microsoft.AspNetCore.Mvc.ProducesAttribute kullanmaktır. Aşağıdaki kod yöntemini kullanacak ProducesAttributeşekilde değiştirirPopulateMetadata.

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

JSON serileştirme seçeneklerini yapılandırma

Varsayılan olarak, minimum API uygulamaları JSON serileştirme ve seri durumdan çıkarma sırasında seçenekleri kullanır Web defaults .

JSON serileştirme seçeneklerini genel olarak yapılandırma

Seçenekler, çağrılarak ConfigureHttpJsonOptionsbir uygulama için genel olarak yapılandırılabilir. Aşağıdaki örnek, ortak alanları ve JSON çıkışını biçimlendirmektedir.

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

Alanlar dahil olduğundan, yukarıdaki kod bunu okur NameField ve çıkış JSON'una ekler.

Uç nokta için JSON serileştirme seçeneklerini yapılandırma

Bir uç nokta için serileştirme seçeneklerini yapılandırmak için aşağıdaki örnekte gösterildiği gibi çağırın Results.Json ve nesneye JsonSerializerOptions geçirin:

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

Alternatif olarak, nesnesini kabul JsonSerializerOptions eden bir aşırı yükleme WriteAsJsonAsync kullanın. Aşağıdaki örnek, çıkış JSON'unu biçimlendirmek için bu aşırı yüklemeyi kullanır:

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

Ek Kaynaklar