Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Megjegyzés:
Ez nem a cikk legújabb verziója. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.
Figyelmeztetés
A ASP.NET Core ezen verziója már nem támogatott. További információt a .NET és a .NET Core támogatási szabályzatában talál. A jelen cikk .NET 9-es verzióját lásd az aktuális kiadásért .
A paraméterkötés a kérelemadatok erősen gépelt paraméterekké alakításának folyamata, amelyeket az útvonalkezelők fejeznek ki. A kötési forrás határozza meg, hogy a paraméterek honnan vannak kötve. A kötési források lehetnek explicitek vagy következtethetők a HTTP-módszer és a paramétertípus alapján.
Támogatott kötési források:
- Útvonalértékek
- Lekérdezési karakterlánc
- Fejléc
- Törzs (mint JSON)
- Űrlapértékek
- Függőséginjektálás által biztosított szolgáltatások
- Személyre szabott
Az alábbi GET útvonalkezelő az alábbi paraméterkötési források némelyikét használja:
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 { }
Az alábbi táblázat az előző példában használt paraméterek és a kapcsolódó kötési források közötti kapcsolatot mutatja be.
| Paraméter | Kötés forrása |
|---|---|
id |
útvonal értéke |
page |
lekérdezési karakterlánc |
customHeader |
fejléc |
service |
Függőséginjektálás által biztosított |
A HTTP-metódusok GET, HEAD, OPTIONSés DELETE nem kötődnek implicit módon a törzsből. A HTTP-metódusok törzséből (JSON-ként) való kötéshez kifejezetten[FromBody] vagy a HttpRequestolvasásához.
Az alábbi példában a POST útvonalkezelő egy kötési törzsforrást (JSON-ként) használ a person paraméterhez:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Az előző példákban szereplő paraméterek automatikusan kötődnek a kérelemadatokhoz. A paraméterkötés által biztosított kényelem bemutatásához az alábbi útvonalkezelők bemutatják, hogyan olvashatók be közvetlenül a kérelem adatai a kérelemből:
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>();
// ...
});
Explicit paraméterkötés
Az attribútumokkal explicit módon deklarálható, hogy a paraméterek honnan vannak kötve.
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);
| Paraméter | Kötés forrása |
|---|---|
id |
útvonalérték id |
page |
lekérdezési karakterlánc "p" nevű |
service |
Függőséginjektálás által biztosított |
contentType |
fejléc a "Content-Type" névvel |
Explicit kötés űrlapértékekből
A [FromForm] attribútum az űrlapértékeket köti össze:
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.
Másik lehetőségként használhatja a [AsParameters] attribútumot olyan egyéni típussal, amely [FromForm]felirattal ellátott tulajdonságokkal rendelkezik. Az alábbi kód például az űrlapértékek és a NewTodoRequest rekordstruktúra tulajdonságaihoz kötődik:
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);
További információért lásd a AsParameters részt a cikk későbbi részében.
A teljes mintakód az AspNetCore.Docs.Samples adattárban található.
Biztonságos kötés az IFormFile-ból és az IFormFileCollectionből
Az összetett űrlapkötés támogatott a IFormFile, a IFormFileCollection és a [FromForm]használatával.
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();
A [FromForm] kéréshez kötött paraméterek közé tartozik egy álhamisítás elleni token. A kérés feldolgozásakor a rendszer ellenőrzi az antiforgery tokent. További információért lásd: Hamisítás elleni védelem minimális API-kkal.
További információért tekintse meg: Űrlapkötés minimális API-kban.
A teljes mintakód az AspNetCore.Docs.Samples adattárban található.
Paraméterkötés függőséginjektálással
A minimális API-k paraméterkötése függőséginjektálási köti a paramétereket, ha a típus szolgáltatásként van konfigurálva. Nem szükséges explicit módon alkalmazni a [FromServices] attribútumot egy paraméterre. A következő kódban mindkét művelet az időt adja vissza:
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();
Választható paraméterek
Az útvonalkezelőkben deklarált paramétereket a rendszer szükség szerint kezeli:
- Ha egy kérelem megfelel az útvonalnak, az útvonalkezelő csak akkor fut, ha a kérelemben minden szükséges paraméter meg van adva.
- Ha nem adja meg az összes szükséges paramétert, az hibát eredményez.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaküldött |
/products |
BadHttpRequestException: A szükséges "int pageNumber" paraméter nem lett megadva a lekérdezési sztringből. |
/products/1 |
HTTP 404-es hiba, nincs egyező útvonal |
Ha a pageNumber nem kötelezővé szeretné tenni, adja meg a típust opcionálisként, vagy adjon meg egy alapértelmezett értéket:
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 | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaküldött |
/products |
1 visszaadott |
/products2 |
1 visszaadott |
Az előző null értékű és alapértelmezett érték az összes forrásra vonatkozik:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Az előző kód null értékű termékkel hívja meg a metódust, ha nem küldenek kéréstörzset.
MEGJEGYZÉS: Ha érvénytelen adatokat ad meg, és a paraméter nullázható, az útvonalkezelő nem kerül végrehajtásra.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaérkezett |
/products |
1 visszaérkezett |
/products?pageNumber=two |
BadHttpRequestException: Nem sikerült megkötni a "Nullable<int> pageNumber" paramétert a "kettő" értékből. |
/products/two |
HTTP 404-es hiba, nincs egyező útvonal |
További információt a kötési hibák szakaszban talál.
Speciális típusok
A következő típusok explicit attribútumok nélkül vannak megkötve:
HttpContext: Az aktuális HTTP-kéréssel vagy -válaszsal kapcsolatos összes információt tartalmazó környezet:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest és HttpResponse: A HTTP-kérés és a HTTP-válasz:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Az aktuális HTTP-kéréshez társított törlési token:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: A kérelemhez társított felhasználó, HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
A kérelem törzsét kössük össze Stream vagy PipeReader-ként.
A kérelem törzse Stream vagy PipeReader-ként összekapcsolható, hogy hatékonyan támogassa azokat a forgatókönyveket, amelyekben a felhasználónak adatokat kell feldolgoznia, és:
- Tárolja az adatokat blobtárolóba, vagy az adatokat egy üzenetsor-szolgáltatóhoz csatolja.
- Feldolgozhatja a tárolt adatokat egy feldolgozói folyamattal vagy egy felhőfüggvénnyel.
Előfordulhat például, hogy az adatok Azure Queue Storage- vagy Azure Blob Storage-tárolóba kerülnek.
A következő kód egy háttér-üzenetsort implementál:
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 következő kód hozzárendeli a kérelem törzsét egy Stream-hoz:
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);
});
Az alábbi kód a teljes Program.cs fájlt jeleníti meg:
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();
- Az adatok olvasásakor a
Streamugyanaz az objektum, mintHttpRequest.Body. - A kérelem törzse alapértelmezés szerint nincs pufferelve. A test elolvasása után nem lehet újraindítani. A stream nem olvasható többször.
- A
Streamés aPipeReadernem használhatók a minimális műveletkezelőn kívül, mivel a mögöttes puffereket a rendszer megsemmisíti vagy újra felhasználja.
Fájlfeltöltések az IFormFile és az IFormFileCollection használatával
A minimális API-k használatával IFormFile és IFormFileCollection használatával végzett fájlfeltöltések kódolást igényelnek multipart/form-data . Az útvonalkezelő paraméternevének meg kell egyeznie a kérelem űrlapmezőjének nevével. A minimális API-k nem támogatják a teljes kérelemtörzs közvetlen kötését egy IFormFile paraméterhez űrlapkódolás nélkül.
Ha a teljes kérelemtörzset meg kell kötnie, például JSON-, bináris adatok vagy más tartalomtípusok használatakor, tekintse meg a következőt:
A következő kód IFormFile és IFormFileCollection használ a fájl feltöltéséhez:
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();
A hitelesített fájlfeltöltési kérelmek egy Engedélyezési fejléc, ügyféltanúsítványvagy cookie fejléc használatával támogatottak.
Az űrlapokhoz való kötődés az IFormCollection, IFormFile és IFormFileCollection használatával
A IFormCollection, IFormFileés IFormFileCollection használatával végzett űrlapalapú paraméterek kötése támogatott. OpenAPI metaadata a űrlapparaméterekre következtetve támogatja a Swagger UIintegrációt.
Az alábbi kód a IFormFile típusból származó következtetéses kötéssel tölti fel a fájlokat:
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();
Figyelmeztetés: Űrlapok implementálásakor az alkalmazás meg kell akadályozniahelyek közötti kérelemhamisítási (XSRF/CSRF) támadásokat. Az előző kódban a IAntiforgery szolgáltatás az XSRF-támadások megelőzésére szolgál egy antiforgery token létrehozásával és érvényesítésével:
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();
További információ az XSRF-támadásokról: Antiforgery hamisítás elleni védelem minimális API-kkal
További információkért lásd: Űrlapkötés minimális API-kban.
Gyűjtemények és összetett típusok űrlapokból való összekötése
A kötés támogatott az alábbiaknál:
Az alábbi kód a következőket jeleníti meg:
- Egy minimális végpont, amely egy többrészes űrlap bemenetét egy összetett objektumhoz köti.
- Az hamisítás elleni szolgáltatások használata a hamisítás elleni tokenek létrehozásának és érvényesítésének támogatására.
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));
}
Az előző kódban:
- A célparamétert a JSON törzsből beolvasandó paraméterek egyértelműsítéséhez
[FromForm]attribútummal kell ellátni. - Az összetett vagy gyűjteménytípusok kötése nem támogatott minimális API-k esetében, amelyek a kérelemdelegálási generátorral vannak lefordítva.
- A korrektúra egy további rejtett bemenetet jelenít meg
isCompletednevével ésfalseértékkel. Ha aisCompletedjelölőnégyzet be van jelölve az űrlap elküldésekor, akkor atrueés afalseis értékként lesz elküldve. Ha a jelölőnégyzet nincs bejelölve, csak a rejtett bemeneti értékfalselesz elküldve. Az ASP.NET Core modellkötési folyamata csak az első értéket olvassa be, amikor egyboolértékhez van kötés, amitrueeredményez a bejelölt jelölőnégyzetek esetén ésfalsea nem bejelölt jelölőnégyzeteknél.
Az előző végpontnak küldött űrlapadatok egy példája a következőképpen néz ki:
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
Tömbök és karakterláncértékek összekapcsolása fejlécekből és kérdésláncokból
Az alábbi kód bemutatja, hogyan lehet a lekérdezési karakterláncokat primitív típusokba, karakterlánctömbökbe és StringValuestömbbe kötni.
// 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]}");
A lekérdezési sztringek vagy fejlécértékek összetett típusú tömbhöz való kötése akkor támogatott, ha a típus TryParse implementálva van. A következő kód egy sztringtömbhöz kötődik, és a megadott címkékkel rendelkező összes elemet visszaadja:
// 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();
});
Az alábbi kód a modellt és a szükséges TryParse implementációt mutatja be:
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 következő kód egy int tömbhöz kapcsolódik:
// 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();
});
Az előző kód teszteléséhez adja hozzá a következő végpontot az adatbázis Todo elemekkel való feltöltéséhez:
// 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 következő adatok az előző végpontra való továbbításához használjon olyan eszközt, mint a HttpRepl:
[
{
"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 következő kód a fejléckulcshoz X-Todo-Id kapcsolódik, és visszaadja a Todo elemeket az egyező Id értékekkel:
// 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();
});
Megjegyzés:
Ha egy string[] egy lekérdezési sztringből illeszt, az egyező lekérdezési sztringérték hiánya null érték helyett üres tömböt eredményez.
Paraméterkötés az [AsParameters] argumentumlistákhoz
AsParametersAttribute egyszerű paraméterkötést tesz lehetővé a típusokhoz, nem pedig összetett vagy rekurzív modellkötéshez.
Vegye figyelembe a következő kódot:
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.
Fontolja meg a következő GET végpontot:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Az alábbi struct helyettesítheti az előző kiemelt paramétereket:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Az átszervezett GET végpont az előző struct-et használja az AsParameters attribútummal.
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());
Az alábbi kód további végpontokat jelenít meg az alkalmazásban:
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();
});
A paraméterlisták újrabontásához a következő osztályok használhatók:
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!;
}
Az alábbi kód a AsParameters és az előtte lévő struct valamint osztályok használatával átszervezett végpontokat mutatja be.
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();
});
Az alábbi record típusokkal helyettesítheti az előző paramétereket:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
A structAsParameters-gyel való használata hatékonyabb lehet, mint egy record típus használata.
A teljes mintakód az AspNetCore.Docs.Samples adattárban.
Egyéni kötés
A paraméterkötés testreszabásának három módja van:
- Az útvonal-, lekérdezés- és fejléckötési forrásokhoz az egyéni típusok kötéséhez adjon hozzá egy statikus
TryParsemetódust a típushoz. - A kötési folyamat szabályozása egy
BindAsyncmetódus típuson való implementálásával. - Speciális forgatókönyvek esetén implementálja a(z) IBindableFromHttpContext<TSelf> interfészt, hogy közvetlenül
HttpContextegyéni kötési logikát biztosítson.
TryParse
TryParse két API-val rendelkezik:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Az alábbi kód a Point: 12.3, 10.1 kódot jeleníti meg az URI /map?Point=12.3,10.1-el.
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 következő API-kkal rendelkezik:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Az alábbi kód a SortBy:xyz, SortDirection:Desc, CurrentPage:99 kódot jeleníti meg az URI /products?SortBy=xyz&SortDir=Desc&Page=99-el.
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
}
Egyéni paraméterkötés a IBindableFromHttpContext segítségével
ASP.NET Core támogatja az egyéni paraméterkötést minimális API-kban az IBindableFromHttpContext<TSelf> interfész használatával. Ez a C# 11 statikus absztrakt tagjaival bevezetett felület lehetővé teszi, hogy olyan típusokat hozzon létre, amelyek közvetlenül az útvonalkezelő paraméterekben köthetők http-környezetből.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Az IBindableFromHttpContext<TSelf> implementálásával egyéni típusokat hozhat létre, amelyek a saját kötési logikájukat kezelik a HttpContext-ból. Ha egy útvonalkezelő ilyen típusú paramétert tartalmaz, a keretrendszer automatikusan meghívja a statikus BindAsync metódust a példány létrehozásához:
using CustomBindingExample;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/", () => "Hello, IBindableFromHttpContext example!");
app.MapGet("/custom-binding", (CustomBoundParameter param) =>
{
return $"Value from custom binding: {param.Value}";
});
app.MapGet("/combined/{id}", (int id, CustomBoundParameter param) =>
{
return $"ID: {id}, Custom Value: {param.Value}";
});
Az alábbi példa egy HTTP-fejlécből kötődő egyéni paraméter implementációját mutatja be:
using System.Reflection;
namespace CustomBindingExample;
public class CustomBoundParameter : IBindableFromHttpContext<CustomBoundParameter>
{
public string Value { get; init; } = default!;
public static ValueTask<CustomBoundParameter?> BindAsync(HttpContext context, ParameterInfo parameter)
{
// Custom binding logic here
// This example reads from a custom header
var value = context.Request.Headers["X-Custom-Header"].ToString();
// If no header was provided, you could fall back to a query parameter
if (string.IsNullOrEmpty(value))
{
value = context.Request.Query["customValue"].ToString();
}
return ValueTask.FromResult<CustomBoundParameter?>(new CustomBoundParameter
{
Value = value
});
}
}
Az érvényesítést az egyéni kötési logikán belül is megvalósíthatja:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
A mintakód megtekintése vagy letöltése (letöltés)
Kötési hibák
Ha a kötés sikertelen, a keretrendszer hibakeresési üzenetet naplóz, és a hibamódtól függően különböző állapotkódokat ad vissza az ügyfélnek.
| Hibamód | Null értékű paraméter típusa | Kötés forrása | Állapotkód |
|---|---|---|---|
{ParameterType}.TryParse visszaad false |
igen | útvonal/lekérdezés/fejléc | 400 |
{ParameterType}.BindAsync visszaad null |
igen | szokás | 400 |
{ParameterType}.BindAsync dobások |
nem számít | szokás | ötszáz |
| A JSON-törzs deszerializálásának sikertelensége | nem számít | test | 400 |
Helytelen tartalomtípus (nem application/json) |
nem számít | test | 415 |
Kötési elsőbbség
A kötési forrás paraméterből való meghatározásának szabályai:
- A paraméteren (From* attribútumokon) definiált explicit attribútum a következő sorrendben:
- Útvonalértékek:
[FromRoute] - Lekérdezési karakterlánc:
[FromQuery] - Fejléc:
[FromHeader] - Törzs:
[FromBody] - Űrlap:
[FromForm] - Szolgáltatás:
[FromServices] - Paraméterértékek:
[AsParameters]
- Útvonalértékek:
- Speciális típusok
HttpContext-
HttpRequest(HttpContext.Request) -
HttpResponse(HttpContext.Response) -
ClaimsPrincipal(HttpContext.User) -
CancellationToken(HttpContext.RequestAborted) -
IFormCollection(HttpContext.Request.Form) -
IFormFileCollection(HttpContext.Request.Form.Files) -
IFormFile(HttpContext.Request.Form.Files[paramName]) -
Stream(HttpContext.Request.Body) -
PipeReader(HttpContext.Request.BodyReader)
- A paramétertípus érvényes statikus
BindAsyncmetódussal rendelkezik. - A paraméter típusa sztring, vagy érvényes statikus
TryParsemetódussal rendelkezik.- Ha a paraméter neve létezik például az útvonalsablonban,
app.Map("/todo/{id}", (int id) => {});, akkor az útvonalhoz van kötve. - Lekérdezési karakterláncból kötve.
- Ha a paraméter neve létezik például az útvonalsablonban,
- Ha a paramétertípus egy függőséginjektálás által biztosított szolgáltatás, akkor ezt a szolgáltatást használja forrásként.
- A paraméter a törzsből származik.
JSON deszerializálási beállítások konfigurálása a törzskötéshez
A testkötési forrás System.Text.Json-t használ a deszerializáláshoz. Ezt az alapértelmezett beállítást nem lehet módosítani, de a JSON szerializálási és deszerializálási beállításai konfigurálhatók.
JSON deszerializálási beállítások konfigurálása globálisan
Az alkalmazásokra globálisan érvényes beállítások ConfigureHttpJsonOptionsmeghívásával konfigurálhatók. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Mivel a mintakód szerializálást és deszerializálást is konfigurál, képes olvasni NameField, és NameField belefoglalni a kimeneti JSON-fájlba.
JSON-deszerializálási beállítások konfigurálása végponthoz
ReadFromJsonAsync-nak vannak túlterhelései, amelyek JsonSerializerOptions objektumot fogadnak el. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
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
// }
Mivel az előző kód csak a deszerializálásra alkalmazza a testre szabott beállításokat, a kimeneti JSON kizárja NameField.
Olvassa el a kérés törzsét
Olvassa el a kérelem törzsét közvetlenül egy HttpContext vagy HttpRequest paraméter használatával:
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();
Az előző kód:
- A kérelmi törzshöz a HttpRequest.BodyReaderhasználatával fér hozzá.
- Másolja a kérelem törzsét egy helyi fájlba.
A paraméterkötés a kérelemadatok erősen gépelt paraméterekké alakításának folyamata, amelyeket az útvonalkezelők fejeznek ki. A kötési forrás határozza meg, hogy a paraméterek honnan vannak kötve. A kötési források lehetnek explicitek vagy következtethetők a HTTP-módszer és a paramétertípus alapján.
Támogatott kötési források:
- Útvonalértékek
- Lekérdezési karakterlánc
- Fejléc
- Törzs (mint JSON)
- Függőséginjektálás által biztosított szolgáltatások
- Személyre szabott
Az űrlapértékek kötése nem natívan támogatott .NET 6 és 7 rendszerben.
Az alábbi GET útvonalkezelő az alábbi paraméterkötési források némelyikét használja:
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 { }
Az alábbi táblázat az előző példában használt paraméterek és a kapcsolódó kötési források közötti kapcsolatot mutatja be.
| Paraméter | Kötés forrása |
|---|---|
id |
útvonal értéke |
page |
lekérdezési karakterlánc |
customHeader |
fejléc |
service |
Függőséginjektálás által biztosított |
A HTTP-metódusok GET, HEAD, OPTIONSés DELETE nem kötődnek implicit módon a törzsből. A HTTP-metódusok törzséből (JSON-ként) való kötéshez kifejezetten[FromBody] vagy a HttpRequestolvasásához.
Az alábbi példában a POST útvonalkezelő egy kötési törzsforrást (JSON-ként) használ a person paraméterhez:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Az előző példákban szereplő paraméterek automatikusan kötődnek a kérelemadatokhoz. A paraméterkötés által biztosított kényelem bemutatásához az alábbi útvonalkezelők bemutatják, hogyan olvashatók be közvetlenül a kérelem adatai a kérelemből:
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>();
// ...
});
Explicit paraméterkötés
Az attribútumokkal explicit módon deklarálható, hogy a paraméterek honnan vannak kötve.
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);
| Paraméter | Kötés forrása |
|---|---|
id |
útvonalérték id |
page |
lekérdezési karakterlánc "p" nevű |
service |
Függőséginjektálás által biztosított |
contentType |
fejléc a "Content-Type" névvel |
Megjegyzés:
Az űrlapértékek kötése nem natívan támogatott .NET 6 és 7 rendszerben.
Paraméterkötés függőséginjektálással
A minimális API-k paraméterkötése függőséginjektálási köti a paramétereket, ha a típus szolgáltatásként van konfigurálva. Nem szükséges explicit módon alkalmazni a [FromServices] attribútumot egy paraméterre. A következő kódban mindkét művelet az időt adja vissza:
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();
Választható paraméterek
Az útvonalkezelőkben deklarált paramétereket a rendszer szükség szerint kezeli:
- Ha egy kérelem megfelel az útvonalnak, az útvonalkezelő csak akkor fut, ha a kérelemben minden szükséges paraméter meg van adva.
- Ha nem adja meg az összes szükséges paramétert, az hibát eredményez.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaküldött |
/products |
BadHttpRequestException: A szükséges "int pageNumber" paraméter nem lett megadva a lekérdezési sztringből. |
/products/1 |
HTTP 404-es hiba, nincs egyező útvonal |
Ha a pageNumber nem kötelezővé szeretné tenni, adja meg a típust opcionálisként, vagy adjon meg egy alapértelmezett értéket:
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 | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaküldött |
/products |
1 visszaadott |
/products2 |
1 visszaadott |
Az előző null értékű és alapértelmezett érték az összes forrásra vonatkozik:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Az előző kód null értékű termékkel hívja meg a metódust, ha nem küldenek kéréstörzset.
MEGJEGYZÉS: Ha érvénytelen adatokat ad meg, és a paraméter nullázható, az útvonalkezelő nem kerül végrehajtásra.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI | eredmény |
|---|---|
/products?pageNumber=3 |
3 visszaérkezett |
/products |
1 visszaérkezett |
/products?pageNumber=two |
BadHttpRequestException: Nem sikerült megkötni a "Nullable<int> pageNumber" paramétert a "kettő" értékből. |
/products/two |
HTTP 404-es hiba, nincs egyező útvonal |
További információt a kötési hibák szakaszban talál.
Speciális típusok
A következő típusok explicit attribútumok nélkül vannak megkötve:
HttpContext: Az aktuális HTTP-kéréssel vagy -válaszsal kapcsolatos összes információt tartalmazó környezet:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest és HttpResponse: A HTTP-kérés és a HTTP-válasz:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Az aktuális HTTP-kéréshez társított törlési token:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: A kérelemhez társított felhasználó, HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
A kérelem törzsét kössük össze Stream vagy PipeReader-ként.
A kérelem törzse Stream vagy PipeReader-ként összekapcsolható, hogy hatékonyan támogassa azokat a forgatókönyveket, amelyekben a felhasználónak adatokat kell feldolgoznia, és:
- Tárolja az adatokat blobtárolóba, vagy az adatokat egy üzenetsor-szolgáltatóhoz csatolja.
- Feldolgozhatja a tárolt adatokat egy feldolgozói folyamattal vagy egy felhőfüggvénnyel.
Előfordulhat például, hogy az adatok Azure Queue Storage- vagy Azure Blob Storage-tárolóba kerülnek.
A következő kód egy háttér-üzenetsort implementál:
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 következő kód hozzárendeli a kérelem törzsét egy Stream-hoz:
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);
});
Az alábbi kód a teljes Program.cs fájlt jeleníti meg:
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();
- Az adatok olvasásakor a
Streamugyanaz az objektum, mintHttpRequest.Body. - A kérelem törzse alapértelmezés szerint nincs pufferelve. A test elolvasása után nem lehet újraindítani. A stream nem olvasható többször.
- A
Streamés aPipeReadernem használhatók a minimális műveletkezelőn kívül, mivel a mögöttes puffereket a rendszer megsemmisíti vagy újra felhasználja.
Fájlfeltöltések az IFormFile és az IFormFileCollection használatával
A következő kód IFormFile és IFormFileCollection használ a fájl feltöltéséhez:
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();
A hitelesített fájlfeltöltési kérelmek egy Engedélyezési fejléc, ügyféltanúsítványvagy cookie fejléc használatával támogatottak.
A .NET 7-ben a ASP.NET Core nem támogatja az antiforgery beépített támogatását.
Az Antiforgery a ASP.NET Core-ban érhető el a .NET 8-ban vagy újabb verzióiban . Azonban a IAntiforgery szolgáltatáshasználatával implementálható.
Tömbök és karakterláncértékek összekapcsolása fejlécekből és kérdésláncokból
Az alábbi kód bemutatja, hogyan lehet a lekérdezési karakterláncokat primitív típusokba, karakterlánctömbökbe és StringValuestömbbe kötni.
// 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]}");
A lekérdezési sztringek vagy fejlécértékek összetett típusú tömbhöz való kötése akkor támogatott, ha a típus TryParse implementálva van. A következő kód egy sztringtömbhöz kötődik, és a megadott címkékkel rendelkező összes elemet visszaadja:
// 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();
});
Az alábbi kód a modellt és a szükséges TryParse implementációt mutatja be:
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 következő kód egy int tömbhöz kapcsolódik:
// 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();
});
Az előző kód teszteléséhez adja hozzá a következő végpontot az adatbázis Todo elemekkel való feltöltéséhez:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Használjon egy API-tesztelési eszközt, például HttpRepl a következő adatok átadásához az előző végpontnak:
[
{
"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 következő kód a fejléckulcshoz X-Todo-Id kapcsolódik, és visszaadja a Todo elemeket az egyező Id értékekkel:
// 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();
});
Megjegyzés:
Ha egy string[] egy lekérdezési sztringből illeszt, az egyező lekérdezési sztringérték hiánya null érték helyett üres tömböt eredményez.
Paraméterkötés az [AsParameters] argumentumlistákhoz
AsParametersAttribute egyszerű paraméterkötést tesz lehetővé a típusokhoz, nem pedig összetett vagy rekurzív modellkötéshez.
Vegye figyelembe a következő kódot:
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.
Fontolja meg a következő GET végpontot:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Az alábbi struct helyettesítheti az előző kiemelt paramétereket:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Az átszervezett GET végpont az előző struct-et használja az AsParameters attribútummal.
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());
Az alábbi kód további végpontokat jelenít meg az alkalmazásban:
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();
});
A paraméterlisták újrabontásához a következő osztályok használhatók:
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!;
}
Az alábbi kód a AsParameters és az előtte lévő struct valamint osztályok használatával átszervezett végpontokat mutatja be.
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();
});
Az alábbi record típusokkal helyettesítheti az előző paramétereket:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
A structAsParameters-gyel való használata hatékonyabb lehet, mint egy record típus használata.
A teljes mintakód az AspNetCore.Docs.Samples adattárban.
Egyéni kötés
A paraméterkötés testreszabásának három módja van:
- Az útvonal-, lekérdezés- és fejléckötési forrásokhoz az egyéni típusok kötéséhez adjon hozzá egy statikus
TryParsemetódust a típushoz. - A kötési folyamat szabályozása egy
BindAsyncmetódus típuson való implementálásával. - Speciális forgatókönyvek esetén implementálja a(z) IBindableFromHttpContext<TSelf> interfészt, hogy közvetlenül
HttpContextegyéni kötési logikát biztosítson.
TryParse
TryParse két API-val rendelkezik:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Az alábbi kód a Point: 12.3, 10.1 kódot jeleníti meg az URI /map?Point=12.3,10.1-el.
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 következő API-kkal rendelkezik:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Az alábbi kód a SortBy:xyz, SortDirection:Desc, CurrentPage:99 kódot jeleníti meg az URI /products?SortBy=xyz&SortDir=Desc&Page=99-el.
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
}
Egyéni paraméterkötés a IBindableFromHttpContext segítségével
ASP.NET Core támogatja az egyéni paraméterkötést minimális API-kban az IBindableFromHttpContext<TSelf> interfész használatával. Ez a C# 11 statikus absztrakt tagjaival bevezetett felület lehetővé teszi, hogy olyan típusokat hozzon létre, amelyek közvetlenül az útvonalkezelő paraméterekben köthetők http-környezetből.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Az IBindableFromHttpContext<TSelf> interfész implementálásával egyéni típusokat hozhat létre, amelyek a saját kötési logikájukat a HttpContext-ből kezelik. Ha egy útvonalkezelő tartalmaz egy ilyen típusú paramétert, a keretrendszer automatikusan meghívja a statikus BindAsync metódust a példány létrehozásához:
using CustomBindingExample;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/", () => "Hello, IBindableFromHttpContext example!");
app.MapGet("/custom-binding", (CustomBoundParameter param) =>
{
return $"Value from custom binding: {param.Value}";
});
app.MapGet("/combined/{id}", (int id, CustomBoundParameter param) =>
{
return $"ID: {id}, Custom Value: {param.Value}";
});
Az alábbi példa egy HTTP-fejlécből kötődő egyéni paraméter implementációját mutatja be:
using System.Reflection;
namespace CustomBindingExample;
public class CustomBoundParameter : IBindableFromHttpContext<CustomBoundParameter>
{
public string Value { get; init; } = default!;
public static ValueTask<CustomBoundParameter?> BindAsync(HttpContext context, ParameterInfo parameter)
{
// Custom binding logic here
// This example reads from a custom header
var value = context.Request.Headers["X-Custom-Header"].ToString();
// If no header was provided, you could fall back to a query parameter
if (string.IsNullOrEmpty(value))
{
value = context.Request.Query["customValue"].ToString();
}
return ValueTask.FromResult<CustomBoundParameter?>(new CustomBoundParameter
{
Value = value
});
}
}
Az érvényesítést az egyéni kötési logikán belül is megvalósíthatja:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
A mintakód megtekintése vagy letöltése (letöltés)
Kötési hibák
Ha a kötés sikertelen, a keretrendszer hibakeresési üzenetet naplóz, és a hibamódtól függően különböző állapotkódokat ad vissza az ügyfélnek.
| Hibamód | Null értékű paraméter típusa | Kötés forrása | Állapotkód |
|---|---|---|---|
{ParameterType}.TryParse visszaad false |
igen | útvonal/lekérdezés/fejléc | 400 |
{ParameterType}.BindAsync visszaad null |
igen | szokás | 400 |
{ParameterType}.BindAsync dobások |
nem számít | szokás | ötszáz |
| A JSON-törzs deszerializálásának sikertelensége | nem számít | test | 400 |
Helytelen tartalomtípus (nem application/json) |
nem számít | test | 415 |
Kötési elsőbbség
A kötési forrás paraméterből való meghatározásának szabályai:
- A paraméteren (From* attribútumokon) definiált explicit attribútum a következő sorrendben:
- Útvonalértékek:
[FromRoute] - Lekérdezési karakterlánc:
[FromQuery] - Fejléc:
[FromHeader] - Törzs:
[FromBody] - Szolgáltatás:
[FromServices] - Paraméterértékek:
[AsParameters]
- Útvonalértékek:
- Speciális típusok
HttpContext-
HttpRequest(HttpContext.Request) -
HttpResponse(HttpContext.Response) -
ClaimsPrincipal(HttpContext.User) -
CancellationToken(HttpContext.RequestAborted) -
IFormFileCollection(HttpContext.Request.Form.Files) -
IFormFile(HttpContext.Request.Form.Files[paramName]) -
Stream(HttpContext.Request.Body) -
PipeReader(HttpContext.Request.BodyReader)
- A paramétertípus érvényes statikus
BindAsyncmetódussal rendelkezik. - A paraméter típusa sztring, vagy érvényes statikus
TryParsemetódussal rendelkezik.- Ha a paraméter neve megtalálható az útvonalsablonban. A
app.Map("/todo/{id}", (int id) => {});idaz útvonalhoz van kötve. - Lekérdezési karakterláncból kötve.
- Ha a paraméter neve megtalálható az útvonalsablonban. A
- Ha a paramétertípus egy függőséginjektálás által biztosított szolgáltatás, akkor ezt a szolgáltatást használja forrásként.
- A paraméter a törzsből származik.
JSON deszerializálási beállítások konfigurálása a törzskötéshez
A testkötési forrás System.Text.Json-t használ a deszerializáláshoz. Ezt az alapértelmezett beállítást nem lehet módosítani, de a JSON szerializálási és deszerializálási beállításai konfigurálhatók.
JSON deszerializálási beállítások konfigurálása globálisan
Az alkalmazásokra globálisan érvényes beállítások ConfigureHttpJsonOptionsmeghívásával konfigurálhatók. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Mivel a mintakód szerializálást és deszerializálást is konfigurál, képes olvasni NameField, és NameField belefoglalni a kimeneti JSON-fájlba.
JSON-deszerializálási beállítások konfigurálása végponthoz
ReadFromJsonAsync-nak vannak túlterhelései, amelyek JsonSerializerOptions objektumot fogadnak el. Az alábbi példa a nyilvános mezőket és a JSON-kimenet formátumát tartalmazza.
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
// }
Mivel az előző kód csak a deszerializálásra alkalmazza a testre szabott beállításokat, a kimeneti JSON kizárja NameField.
Olvassa el a kérés törzsét
Olvassa el a kérelem törzsét közvetlenül egy HttpContext vagy HttpRequest paraméter használatával:
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();
Az előző kód:
- A kérelmi törzshöz a HttpRequest.BodyReaderhasználatával fér hozzá.
- Másolja a kérelem törzsét egy helyi fájlba.