Minimum API uygulamalarında Parametre Bağlama

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 9 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 9 sürümüne bakın.

Parametre bağlama, istek verilerini yol işleyicileri tarafından ifade edilen kesin olarak belirlenmiş parametrelere dönüştürme işlemidir. Bağlama kaynağı, parametrelerin nereye bağlandığını belirler. Bağlama kaynakları HTTP yöntemine ve parametre türüne göre açık veya çıkarılabilir.

Desteklenen bağlama kaynakları:

  • Yol değerleri
  • Sorgu dizesi
  • Üst bilgi
  • Gövde (JSON olarak)
  • Form değerleri
  • Bağımlılık ekleme tarafından sağlanan hizmetler
  • Özel

Aşağıdaki GET yol işleyicisi bu parametre bağlama kaynaklarından bazılarını kullanır:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Aşağıdaki tabloda, önceki örnekte kullanılan parametrelerle ilişkili bağlama kaynakları arasındaki ilişki gösterilmektedir.

Parametre Bağlama Kaynağı
id yol değeri
page sorgu dizesi
customHeader üst bilgi
service Bağımlılık ekleme tarafından sağlanır

, , HEADOPTIONSve DELETE HTTP yöntemleri GETgövdeden örtük olarak bağlanmaz. Bu HTTP yöntemleri için gövdeden (JSON olarak) bağlanmak için ile [FromBody] açıkça bağlayın veya dosyasından HttpRequestokuyun.

Aşağıdaki örnek POST yol işleyicisi, parametresi için person bir bağlama gövdesi kaynağı (JSON olarak) kullanır:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Yukarıdaki örneklerde yer alan parametrelerin tümü istek verilerinden otomatik olarak bağlıdır. Parametre bağlamanın sağladığı kolaylığı göstermek için aşağıdaki yol işleyicileri istek verilerini doğrudan istekten nasıl okuyacaklarını gösterir:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Açık Parametre Bağlama

Öznitelikler, parametrelerin nereye bağlı olduğunu açıkça bildirmek için kullanılabilir.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parametre Bağlama Kaynağı
id adıyla yol değeri id
page adlı sorgu dizesi "p"
service Bağımlılık ekleme tarafından sağlanır
contentType adıyla üst bilgi "Content-Type"

Form değerlerinden açık bağlama

[FromForm] özniteliği form değerlerini bağlar:

app.MapPost("/todos", async ([FromForm] string name,
    [FromForm] Visibility visibility, IFormFile? attachment, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = name,
        Visibility = visibility
    };

    if (attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await attachment.CopyToAsync(stream);
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.

Bunun alternatifi, özniteliğini [AsParameters] ile ek açıklamalı özelliklere sahip özel bir türle kullanmaktır [FromForm]. Örneğin, aşağıdaki kod form değerlerinden kayıt yapısının özelliklerine NewTodoRequest bağlanır:

app.MapPost("/ap/todos", async ([AsParameters] NewTodoRequest request, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = request.Name,
        Visibility = request.Visibility
    };

    if (request.Attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await request.Attachment.CopyToAsync(stream);

        todo.Attachment = attachmentName;
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.
public record struct NewTodoRequest([FromForm] string Name,
    [FromForm] Visibility Visibility, IFormFile? Attachment);

Daha fazla bilgi için bu makalenin devamında yer alan AsParameters bölümüne bakın.

Örnek kodun tamamı AspNetCore.Docs.Samples deposundadır.

IFormFile ve IFormFileCollection'dan güvenli bağlama

Karmaşık form bağlama, kullanılarak IFormFile ve IFormFileCollection kullanılarak [FromForm]desteklenir:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

// Generate a form with an anti-forgery token and an /upload endpoint.
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = MyUtils.GenerateHtmlForm(token.FormFieldName, token.RequestToken!);
    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>, BadRequest<string>>>
    ([FromForm] FileUploadForm fileUploadForm, HttpContext context,
                                                IAntiforgery antiforgery) =>
{
    await MyUtils.SaveFileWithName(fileUploadForm.FileDocument!,
              fileUploadForm.Name!, app.Environment.ContentRootPath);
    return TypedResults.Ok($"Your file with the description:" +
        $" {fileUploadForm.Description} has been uploaded successfully");
});

app.Run();

ile [FromForm] isteğe bağlı parametreler, bir sahte belirteç içerir. İstek işlendiğinde, kötü amaçlı yazılımdan koruma belirteci doğrulanır. Daha fazla bilgi için bkz . Minimal API'lerle sahteciliğe karşı koruma.

Daha fazla bilgi için bkz. Minimum API'lerde form bağlama.

Örnek kodun tamamı AspNetCore.Docs.Samples deposundadır.

Bağımlılık ekleme ile parametre bağlama

En düşük API'ler için parametre bağlama, tür hizmet olarak yapılandırıldığında bağımlılık ekleme yoluyla parametreleri bağlar. Özniteliğin bir parametreye açıkça uygulanması [FromServices] gerekmez. Aşağıdaki kodda, her iki eylem de saati döndürür:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

İsteğe bağlı parametreler

Yol işleyicilerinde bildirilen parametreler gerekli olarak değerlendirilir:

  • Bir istek yolla eşleşiyorsa, yol işleyicisi yalnızca istekte tüm gerekli parametreler sağlandığında çalışır.
  • Tüm gerekli parametrelerin sağlanamaması hatayla sonuçlanır.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI result
/products?pageNumber=3 3 döndürüldü
/products BadHttpRequestException: Sorgu dizesinden gerekli "int pageNumber" parametresi sağlanmadı.
/products/1 HTTP 404 hatası, eşleşen yol yok

İsteğe bağlı yapmak pageNumber için türü isteğe bağlı olarak tanımlayın veya varsayılan bir değer sağlayın:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI result
/products?pageNumber=3 3 döndürüldü
/products 1 döndürüldü
/products2 1 döndürüldü

Yukarıdaki null atanabilir ve varsayılan değer tüm kaynaklar için geçerlidir:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

Yukarıdaki kod, istek gövdesi gönderilmezse null bir ürünle yöntemini çağırır.

NOT: Geçersiz veri sağlanırsa ve parametre null atanabilirse, yol işleyicisi çalıştırılmaz .

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI result
/products?pageNumber=3 3 Döndürülen
/products 1 Döndürülen
/products?pageNumber=two BadHttpRequestException: "two" parametresi "Nullable<int> pageNumber" bağlanamadı.
/products/two HTTP 404 hatası, eşleşen yol yok

Daha fazla bilgi için Bağlama Hataları bölümüne bakın.

Özel türler

Aşağıdaki türler açık öznitelikler olmadan bağlanır:

  • HttpContext: Geçerli HTTP isteği veya yanıtı hakkındaki tüm bilgileri barındıran bağlam:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest ve HttpResponse: HTTP isteği ve HTTP yanıtı:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Geçerli HTTP isteğiyle ilişkili iptal belirteci:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: İstekle ilişkili kullanıcı, ile HttpContext.Userilişkili:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

İstek gövdesini veya Stream olarak bağlama PipeReader

İstek gövdesi, kullanıcının verileri işlemesi gereken senaryoları verimli bir Stream şekilde desteklemek için veya PipeReader olarak bağlanabilir ve:

  • Verileri blob depolamaya depolayın veya bir kuyruk sağlayıcısına sıralayın.
  • Depolanan verileri bir çalışan işlemi veya bulut işleviyle işleme.

Örneğin, veriler Azure Kuyruk depolama alanına sıralanabilir veya Azure Blob depolamada depolanabilir.

Aşağıdaki kod bir arka plan kuyruğu uygular:

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

Aşağıdaki kod istek gövdesini bir Streamile bağlar:

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

Aşağıdaki kod dosyanın tamamını Program.cs gösterir:

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • Verileri okurken, Stream ile aynı nesnedir HttpRequest.Body.
  • İstek gövdesi varsayılan olarak arabelleğe alınmıyor. Ceset okunduktan sonra geri alınamaz. Akış birden çok kez okunamaz.
  • Stream PipeReader ve, temel alınan arabellekler atılacağı veya yeniden kullanılacağı için en düşük eylem işleyicisi dışında kullanılamaz.

IFormFile ve IFormFileCollection kullanılarak dosya yüklemeleri

Aşağıdaki kod dosyayı karşıya yüklemek için ve IFormFileCollection kullanırIFormFile:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Kimliği doğrulanmış dosya yükleme istekleri Yetkilendirme üst bilgisi, istemci sertifikası veya cookie üst bilgi kullanılarak desteklenir.

IFormCollection, IFormFile ve IFormFileCollection ile formlara bağlama

, IFormFileve IFormFileCollection kullanarak IFormCollectionform tabanlı parametrelerden bağlama desteklenir. Swagger kullanıcı arabirimiyle tümleştirmeyi desteklemek üzere form parametreleri için OpenAPI meta verileri çıkarılır.

Aşağıdaki kod, türdeki IFormFile çıkarımlı bağlamayı kullanarak dosyaları karşıya yükler:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

Uyarı: Formlar uygulanırken uygulamanın Siteler Arası İstek Sahteciliği (XSRF/CSRF) saldırılarını engellemesi gerekir. Yukarıdaki kodda IAntiforgery hizmet, bir sahte belirteç oluşturup doğrulayarak XSRF saldırılarını önlemek için kullanılır:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

XSRF saldırıları hakkında daha fazla bilgi için bkz . Minimal API'lerle antiforgery

Daha fazla bilgi için bkz. Minimum API'lerde form bağlama;

Formlardan koleksiyonlara ve karmaşık türlere bağlama

Bağlama aşağıdakiler için desteklenir:

  • Koleksiyonlar, örneğin Liste ve Sözlük
  • Karmaşık türler, örneğin veya TodoProject

Aşağıdaki kod şunları gösterir:

  • Çok parçalı form girişini karmaşık bir nesneye bağlayan en düşük uç nokta.
  • Antiforgery belirteçlerinin oluşturulmasını ve doğrulanması için antiforgery hizmetlerini kullanma.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
        <html><body>
           <form action="/todo" method="POST" enctype="multipart/form-data">
               <input name="{token.FormFieldName}" 
                                type="hidden" value="{token.RequestToken}" />
               <input type="text" name="name" />
               <input type="date" name="dueDate" />
               <input type="checkbox" name="isCompleted" value="true" />
               <input type="submit" />
               <input name="isCompleted" type="hidden" value="false" /> 
           </form>
        </body></html>
    """;
    return Results.Content(html, "text/html");
});

app.MapPost("/todo", async Task<Results<Ok<Todo>, BadRequest<string>>> 
               ([FromForm] Todo todo, HttpContext context, IAntiforgery antiforgery) =>
{
    try
    {
        await antiforgery.ValidateRequestAsync(context);
        return TypedResults.Ok(todo);
    }
    catch (AntiforgeryValidationException e)
    {
        return TypedResults.BadRequest("Invalid antiforgery token");
    }
});

app.Run();

class Todo
{
    public string Name { get; set; } = string.Empty;
    public bool IsCompleted { get; set; } = false;
    public DateTime DueDate { get; set; } = DateTime.Now.Add(TimeSpan.FromDays(1));
}

Önceki kodda:

  • JSON gövdesinden okunması gereken parametrelerden ayırt etmek için hedef parametreye özniteliğiyle [FromForm] açıklama eklenmelidir.
  • İstek Temsilcisi Oluşturucu ile derlenen en düşük API'ler için karmaşık veya koleksiyon türlerinden bağlama desteklenmez .
  • İşaretlemeyi adı ve değeri falseile ek bir isCompleted gizli giriş gösterir. isCompleted Form gönderildiğinde onay kutusu işaretlenirse, hem değerler true false hem de değer olarak gönderilir. Onay kutusunun işareti kaldırılırsa, yalnızca gizli giriş değeri false gönderilir. ASP.NET Core model bağlama işlemi, bir bool değere bağlanırken yalnızca ilk değeri okur ve bu da işaretli onay kutularına ve false işaretsiz onay kutularına neden true olur.

Önceki uç noktaya gönderilen form verilerine bir örnek aşağıdaki gibi görünür:

__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false

Üst bilgilerden ve sorgu dizelerinden dizileri ve dize değerlerini bağlama

Aşağıdaki kod, sorgu dizelerini ilkel türler, dize dizileri ve StringValues dizilerine bağlamayı gösterir:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Sorgu dizelerini veya üst bilgi değerlerini karmaşık türlerden oluşan bir diziye bağlama, tür TryParse uygulandığında desteklenir. Aşağıdaki kod bir dize dizisine bağlanır ve belirtilen etiketlere sahip tüm öğeleri döndürür:

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

Aşağıdaki kod modeli ve gerekli TryParse uygulamayı gösterir:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

Aşağıdaki kod bir int diziye bağlanır:

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Yukarıdaki kodu test etmek için aşağıdaki uç noktayı ekleyerek veritabanını öğelerle Todo doldurun:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Aşağıdaki verileri önceki uç noktaya geçirmek için gibi HttpRepl bir araç kullanın:

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

Aşağıdaki kod üst bilgi anahtarına X-Todo-Id bağlanır ve eşleşen Id değerlere Todo sahip öğeleri döndürür:

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Not

Sorgu dizesinden bir string[] bağlama yapılırken eşleşen sorgu dizesi değerinin olmaması, null değer yerine boş bir diziyle sonuçlanır.

[AsParameters] ile bağımsız değişken listeleri için parametre bağlama

AsParametersAttribute karmaşık veya özyinelemeli model bağlamasına değil, türlere basit parametre bağlamasını etkinleştirir.

Aşağıdaki kodu inceleyin:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

Aşağıdaki GET uç noktayı göz önünde bulundurun:

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

struct Aşağıdakiler, önceki vurgulanan parametreleri değiştirmek için kullanılabilir:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Yeniden düzenlenmiş GET uç nokta, AsParameters özniteliğiyle öncekini struct kullanır:

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Aşağıdaki kod, uygulamadaki ek uç noktaları gösterir:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Parametre listelerini yeniden düzenlemede aşağıdaki sınıflar kullanılır:

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

Aşağıdaki kod, ve önceki struct ve sınıflarını kullanarak AsParameters yeniden düzenlenmiş uç noktaları gösterir:

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Yukarıdaki parametreleri değiştirmek için aşağıdaki record türler kullanılabilir:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

struct ile AsParameters kullanmak, tür kullanmaktan record daha yüksek performansa sahip olabilir.

AspNetCore.Docs.Samples deposundaki tam örnek kod.

Özel Bağlama

Parametre bağlamasını özelleştirmenin iki yolu vardır:

  1. Yol, sorgu ve üst bilgi bağlama kaynakları için, türü için statik TryParse bir yöntem ekleyerek özel türleri bağlayın.
  2. Bir türe bir BindAsync yöntem uygulayarak bağlama işlemini denetleyin.

TryParse

TryParse iki API'ye sahiptir:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Aşağıdaki kod URI /map?Point=12.3,10.1ile birlikte görüntülenirPoint: 12.3, 10.1:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync aşağıdaki API'lere sahiptir:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Aşağıdaki kod URI /products?SortBy=xyz&SortDir=Desc&Page=99ile birlikte görüntülenirSortBy:xyz, SortDirection:Desc, CurrentPage:99:

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Bağlama hataları

Bağlama başarısız olduğunda, çerçeve hata ayıklama iletisini günlüğe kaydeder ve hata moduna bağlı olarak istemciye çeşitli durum kodları döndürür.

Hata modu Null Atanabilir Parametre Türü Bağlama Kaynağı Durum kodu
{ParameterType}.TryParse Döndürür false evet route/query/header 400
{ParameterType}.BindAsync Döndürür null evet özel 400
{ParameterType}.BindAsync Atar fark etmez özel 500
JSON gövdesinin seri durumdan çıkarılamaması fark etmez gövde 400
Yanlış içerik türü (değil application/json) fark etmez gövde Kategori 415

Bağlama Önceliği

Bir parametreden bağlama kaynağını belirleme kuralları:

  1. Parametrede (From* öznitelikleri) aşağıdaki sırayla tanımlanan açık öznitelik:
    1. Yol değerleri: [FromRoute]
    2. Sorgu dizesi: [FromQuery]
    3. Üstbilgi: [FromHeader]
    4. Beden: [FromBody]
    5. Form: [FromForm]
    6. Hizmet: [FromServices]
    7. Parametre değerleri: [AsParameters]
  2. Özel türler
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormCollection (HttpContext.Request.Form)
    7. IFormFileCollection (HttpContext.Request.Form.Files)
    8. IFormFile (HttpContext.Request.Form.Files[paramName])
    9. Stream (HttpContext.Request.Body)
    10. PipeReader (HttpContext.Request.BodyReader)
  3. Parametre türü geçerli bir statik BindAsync yönteme sahiptir.
  4. Parametre türü bir dizedir veya geçerli bir statik TryParse yöntemi vardır.
    1. Parametre adı örneğin, yol şablonunda varsa, app.Map("/todo/{id}", (int id) => {});rotadan bağlanır.
    2. Sorgu dizesinden bağlanır.
  5. Parametre türü bağımlılık ekleme tarafından sağlanan bir hizmetse, kaynak olarak bu hizmeti kullanır.
  6. parametresi gövdedendir.

Gövde bağlaması için JSON seri durumdan çıkarma seçeneklerini yapılandırma

Gövde bağlama kaynağı seri durumdan çıkarma için kullanır System.Text.Json . Bu varsayılanı değiştirmek mümkün değildir, ancak JSON serileştirme ve seri durumdan çıkarma seçenekleri yapılandırılabilir.

JSON seri durumdan çıkarma seçeneklerini genel olarak yapılandırma

Bir uygulama için genel olarak geçerli olan seçenekler çağrılarak ConfigureHttpJsonOptionsyapı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
// }

Örnek kod hem serileştirmeyi hem de seri durumdan çıkarmayı yapılandırdığından çıktı JSON'unu okuyabilir NameField ve içerebilir NameField .

Uç nokta için JSON seri durumdan çıkarma seçeneklerini yapılandırma

ReadFromJsonAsync bir nesneyi kabul JsonSerializerOptions eden aşırı yüklemelere sahiptir. Aşağıdaki örnek, ortak alanları ve JSON çıkışını biçimlendirmektedir.

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

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",
//    "isComplete":false
// }

Yukarıdaki kod özelleştirilmiş seçenekleri yalnızca seri durumdan çıkarma işlemine uyguladığından çıktı JSON dışlar NameField.

İstek gövdesini okuma

bir veya HttpRequest parametresini kullanarak HttpContext istek gövdesini doğrudan okuyun:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Yukarıdaki kod:

Parametre bağlama, istek verilerini yol işleyicileri tarafından ifade edilen kesin olarak belirlenmiş parametrelere dönüştürme işlemidir. Bağlama kaynağı, parametrelerin nereye bağlandığını belirler. Bağlama kaynakları HTTP yöntemine ve parametre türüne göre açık veya çıkarılabilir.

Desteklenen bağlama kaynakları:

  • Yol değerleri
  • Sorgu dizesi
  • Üst bilgi
  • Gövde (JSON olarak)
  • Bağımlılık ekleme tarafından sağlanan hizmetler
  • Özel

.NET 6 ve 7'de form değerlerinden bağlama yerel olarak desteklenmez.

Aşağıdaki GET yol işleyicisi bu parametre bağlama kaynaklarından bazılarını kullanır:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Aşağıdaki tabloda, önceki örnekte kullanılan parametrelerle ilişkili bağlama kaynakları arasındaki ilişki gösterilmektedir.

Parametre Bağlama Kaynağı
id yol değeri
page sorgu dizesi
customHeader üst bilgi
service Bağımlılık ekleme tarafından sağlanır

, , HEADOPTIONSve DELETE HTTP yöntemleri GETgövdeden örtük olarak bağlanmaz. Bu HTTP yöntemleri için gövdeden (JSON olarak) bağlanmak için ile [FromBody] açıkça bağlayın veya dosyasından HttpRequestokuyun.

Aşağıdaki örnek POST yol işleyicisi, parametresi için person bir bağlama gövdesi kaynağı (JSON olarak) kullanır:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Yukarıdaki örneklerde yer alan parametrelerin tümü istek verilerinden otomatik olarak bağlıdır. Parametre bağlamanın sağladığı kolaylığı göstermek için aşağıdaki yol işleyicileri istek verilerini doğrudan istekten nasıl okuyacaklarını gösterir:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Açık Parametre Bağlama

Öznitelikler, parametrelerin nereye bağlı olduğunu açıkça bildirmek için kullanılabilir.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parametre Bağlama Kaynağı
id adıyla yol değeri id
page adlı sorgu dizesi "p"
service Bağımlılık ekleme tarafından sağlanır
contentType adıyla üst bilgi "Content-Type"

Not

.NET 6 ve 7'de form değerlerinden bağlama yerel olarak desteklenmez.

Bağımlılık ekleme ile parametre bağlama

En düşük API'ler için parametre bağlama, tür hizmet olarak yapılandırıldığında bağımlılık ekleme yoluyla parametreleri bağlar. Özniteliğin bir parametreye açıkça uygulanması [FromServices] gerekmez. Aşağıdaki kodda, her iki eylem de saati döndürür:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

İsteğe bağlı parametreler

Yol işleyicilerinde bildirilen parametreler gerekli olarak değerlendirilir:

  • Bir istek yolla eşleşiyorsa, yol işleyicisi yalnızca istekte tüm gerekli parametreler sağlandığında çalışır.
  • Tüm gerekli parametrelerin sağlanamaması hatayla sonuçlanır.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI result
/products?pageNumber=3 3 döndürüldü
/products BadHttpRequestException: Sorgu dizesinden gerekli "int pageNumber" parametresi sağlanmadı.
/products/1 HTTP 404 hatası, eşleşen yol yok

İsteğe bağlı yapmak pageNumber için türü isteğe bağlı olarak tanımlayın veya varsayılan bir değer sağlayın:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI result
/products?pageNumber=3 3 döndürüldü
/products 1 döndürüldü
/products2 1 döndürüldü

Yukarıdaki null atanabilir ve varsayılan değer tüm kaynaklar için geçerlidir:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

Yukarıdaki kod, istek gövdesi gönderilmezse null bir ürünle yöntemini çağırır.

NOT: Geçersiz veri sağlanırsa ve parametre null atanabilirse, yol işleyicisi çalıştırılmaz .

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI result
/products?pageNumber=3 3 Döndürülen
/products 1 Döndürülen
/products?pageNumber=two BadHttpRequestException: "two" parametresi "Nullable<int> pageNumber" bağlanamadı.
/products/two HTTP 404 hatası, eşleşen yol yok

Daha fazla bilgi için Bağlama Hataları bölümüne bakın.

Özel türler

Aşağıdaki türler açık öznitelikler olmadan bağlanır:

  • HttpContext: Geçerli HTTP isteği veya yanıtı hakkındaki tüm bilgileri barındıran bağlam:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest ve HttpResponse: HTTP isteği ve HTTP yanıtı:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Geçerli HTTP isteğiyle ilişkili iptal belirteci:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: İstekle ilişkili kullanıcı, ile HttpContext.Userilişkili:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

İstek gövdesini veya Stream olarak bağlama PipeReader

İstek gövdesi, kullanıcının verileri işlemesi gereken senaryoları verimli bir Stream şekilde desteklemek için veya PipeReader olarak bağlanabilir ve:

  • Verileri blob depolamaya depolayın veya bir kuyruk sağlayıcısına sıralayın.
  • Depolanan verileri bir çalışan işlemi veya bulut işleviyle işleme.

Örneğin, veriler Azure Kuyruk depolama alanına sıralanabilir veya Azure Blob depolamada depolanabilir.

Aşağıdaki kod bir arka plan kuyruğu uygular:

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

Aşağıdaki kod istek gövdesini bir Streamile bağlar:

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

Aşağıdaki kod dosyanın tamamını Program.cs gösterir:

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • Verileri okurken, Stream ile aynı nesnedir HttpRequest.Body.
  • İstek gövdesi varsayılan olarak arabelleğe alınmıyor. Ceset okunduktan sonra geri alınamaz. Akış birden çok kez okunamaz.
  • Stream PipeReader ve, temel alınan arabellekler atılacağı veya yeniden kullanılacağı için en düşük eylem işleyicisi dışında kullanılamaz.

IFormFile ve IFormFileCollection kullanılarak dosya yüklemeleri

Aşağıdaki kod dosyayı karşıya yüklemek için ve IFormFileCollection kullanırIFormFile:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Kimliği doğrulanmış dosya yükleme istekleri Yetkilendirme üst bilgisi, istemci sertifikası veya cookie üst bilgi kullanılarak desteklenir.

ASP.NET Core 7.0'da antiforgery için yerleşik destek yoktur. Antiforgery, ASP.NET Core 8.0 ve sonraki sürümlerde kullanılabilir. Ancak, hizmeti kullanılarak IAntiforgery uygulanabilir.

Üst bilgilerden ve sorgu dizelerinden dizileri ve dize değerlerini bağlama

Aşağıdaki kod, sorgu dizelerini ilkel türler, dize dizileri ve StringValues dizilerine bağlamayı gösterir:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Sorgu dizelerini veya üst bilgi değerlerini karmaşık türlerden oluşan bir diziye bağlama, tür TryParse uygulandığında desteklenir. Aşağıdaki kod bir dize dizisine bağlanır ve belirtilen etiketlere sahip tüm öğeleri döndürür:

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

Aşağıdaki kod modeli ve gerekli TryParse uygulamayı gösterir:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

Aşağıdaki kod bir int diziye bağlanır:

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Yukarıdaki kodu test etmek için aşağıdaki uç noktayı ekleyerek veritabanını öğelerle Todo doldurun:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Aşağıdaki verileri önceki uç noktaya geçirmek gibi HttpRepl bir API test aracı kullanın:

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

Aşağıdaki kod üst bilgi anahtarına X-Todo-Id bağlanır ve eşleşen Id değerlere Todo sahip öğeleri döndürür:

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Not

Sorgu dizesinden bir string[] bağlama yapılırken eşleşen sorgu dizesi değerinin olmaması, null değer yerine boş bir diziyle sonuçlanır.

[AsParameters] ile bağımsız değişken listeleri için parametre bağlama

AsParametersAttribute karmaşık veya özyinelemeli model bağlamasına değil, türlere basit parametre bağlamasını etkinleştirir.

Aşağıdaki kodu inceleyin:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

Aşağıdaki GET uç noktayı göz önünde bulundurun:

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

struct Aşağıdakiler, önceki vurgulanan parametreleri değiştirmek için kullanılabilir:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Yeniden düzenlenmiş GET uç nokta, AsParameters özniteliğiyle öncekini struct kullanır:

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Aşağıdaki kod, uygulamadaki ek uç noktaları gösterir:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Parametre listelerini yeniden düzenlemede aşağıdaki sınıflar kullanılır:

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

Aşağıdaki kod, ve önceki struct ve sınıflarını kullanarak AsParameters yeniden düzenlenmiş uç noktaları gösterir:

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Yukarıdaki parametreleri değiştirmek için aşağıdaki record türler kullanılabilir:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

struct ile AsParameters kullanmak, tür kullanmaktan record daha yüksek performansa sahip olabilir.

AspNetCore.Docs.Samples deposundaki tam örnek kod.

Özel Bağlama

Parametre bağlamasını özelleştirmenin iki yolu vardır:

  1. Yol, sorgu ve üst bilgi bağlama kaynakları için, türü için statik TryParse bir yöntem ekleyerek özel türleri bağlayın.
  2. Bir türe bir BindAsync yöntem uygulayarak bağlama işlemini denetleyin.

TryParse

TryParse iki API'ye sahiptir:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Aşağıdaki kod URI /map?Point=12.3,10.1ile birlikte görüntülenirPoint: 12.3, 10.1:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync aşağıdaki API'lere sahiptir:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Aşağıdaki kod URI /products?SortBy=xyz&SortDir=Desc&Page=99ile birlikte görüntülenirSortBy:xyz, SortDirection:Desc, CurrentPage:99:

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Bağlama hataları

Bağlama başarısız olduğunda, çerçeve hata ayıklama iletisini günlüğe kaydeder ve hata moduna bağlı olarak istemciye çeşitli durum kodları döndürür.

Hata modu Null Atanabilir Parametre Türü Bağlama Kaynağı Durum kodu
{ParameterType}.TryParse Döndürür false evet route/query/header 400
{ParameterType}.BindAsync Döndürür null evet özel 400
{ParameterType}.BindAsync Atar önemli değil özel 500
JSON gövdesinin seri durumdan çıkarılamaması önemli değil gövde 400
Yanlış içerik türü (değil application/json) önemli değil gövde Kategori 415

Bağlama Önceliği

Bir parametreden bağlama kaynağını belirleme kuralları:

  1. Parametrede (From* öznitelikleri) aşağıdaki sırayla tanımlanan açık öznitelik:
    1. Yol değerleri: [FromRoute]
    2. Sorgu dizesi: [FromQuery]
    3. Üstbilgi: [FromHeader]
    4. Beden: [FromBody]
    5. Hizmet: [FromServices]
    6. Parametre değerleri: [AsParameters]
  2. Özel türler
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. PipeReader (HttpContext.Request.BodyReader)
  3. Parametre türü geçerli bir statik BindAsync yönteme sahiptir.
  4. Parametre türü bir dizedir veya geçerli bir statik TryParse yöntemi vardır.
    1. Rota şablonunda parametre adı varsa. id içindeapp.Map("/todo/{id}", (int id) => {});, yolundan bağlıdır.
    2. Sorgu dizesinden bağlanır.
  5. Parametre türü bağımlılık ekleme tarafından sağlanan bir hizmetse, kaynak olarak bu hizmeti kullanır.
  6. parametresi gövdedendir.

Gövde bağlaması için JSON seri durumdan çıkarma seçeneklerini yapılandırma

Gövde bağlama kaynağı seri durumdan çıkarma için kullanır System.Text.Json . Bu varsayılanı değiştirmek mümkün değildir, ancak JSON serileştirme ve seri durumdan çıkarma seçenekleri yapılandırılabilir.

JSON seri durumdan çıkarma seçeneklerini genel olarak yapılandırma

Bir uygulama için genel olarak geçerli olan seçenekler çağrılarak ConfigureHttpJsonOptionsyapı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
// }

Örnek kod hem serileştirmeyi hem de seri durumdan çıkarmayı yapılandırdığından çıktı JSON'unu okuyabilir NameField ve içerebilir NameField .

Uç nokta için JSON seri durumdan çıkarma seçeneklerini yapılandırma

ReadFromJsonAsync bir nesneyi kabul JsonSerializerOptions eden aşırı yüklemelere sahiptir. Aşağıdaki örnek, ortak alanları ve JSON çıkışını biçimlendirmektedir.

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

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",
//    "isComplete":false
// }

Yukarıdaki kod özelleştirilmiş seçenekleri yalnızca seri durumdan çıkarma işlemine uyguladığından çıktı JSON dışlar NameField.

İstek gövdesini okuma

bir veya HttpRequest parametresini kullanarak HttpContext istek gövdesini doğrudan okuyun:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Yukarıdaki kod: