Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Opmerking
Dit is niet de nieuwste versie van dit artikel. Zie de .NET 10-versie van dit artikel voor de huidige release.
Waarschuwing
Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.
Parameterbinding is het proces van het converteren van aanvraaggegevens naar sterk getypte parameters die worden uitgedrukt door route-handlers. Een bindingsbron bepaalt waar parameters van afhankelijk zijn. Bindingsbronnen kunnen expliciet of afgeleid zijn op basis van de HTTP-methode en het parametertype.
Ondersteunde bindingsbronnen:
- Routewaarden
- Querystring
- Koptekst
- Hoofdtekst (als JSON)
- Formulierwaarden
- Services geleverd door afhankelijkheidsinjectie
- Op maat gemaakt
De volgende GET route-handler maakt gebruik van enkele van deze parameterbindingsbronnen:
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 { }
In de volgende tabel ziet u de relatie tussen de parameters die in het voorgaande voorbeeld worden gebruikt en de bijbehorende bindingsbronnen.
| Kenmerk | Bindingsbron |
|---|---|
id |
routewaarde |
page |
querystring |
customHeader |
koptekst |
service |
Geleverd door afhankelijkheidsinjectie |
De HTTP-methodenGET, HEADen OPTIONSDELETE binden niet impliciet vanuit de hoofdtekst. Als u verbinding wilt maken vanaf hoofdtekst (als JSON) voor deze HTTP-methoden, bindt u expliciet met [FromBody] of leest u van de HttpRequest.
In het volgende voorbeeld wordt voor de person parameter een bindingsbron van body, verpakt als JSON, gebruikt.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
De parameters in de voorgaande voorbeelden zijn allemaal automatisch afhankelijk van aanvraaggegevens. Ter illustratie van het gemak dat parameterbinding biedt, laten de volgende routehandlers zien hoe u aanvraaggegevens rechtstreeks vanuit de aanvraag kunt lezen:
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>();
// ...
});
Expliciete parameterbinding
Kenmerken kunnen worden gebruikt om expliciet te declareren waar parameters van afhankelijk zijn.
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);
| Kenmerk | Bindingsbron |
|---|---|
id |
routewaarde met de naam id |
page |
querystring met de naam "p" |
service |
Geleverd door afhankelijkheidsinjectie |
contentType |
header met de naam "Content-Type" |
Expliciete binding van formulierwaarden
Met het [FromForm] kenmerk worden formulierwaarden gekoppeld:
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.
Een alternatief is om het [AsParameters] kenmerk te gebruiken met een aangepast type waarvan de eigenschappen met [FromForm] zijn geannoteerd. De volgende code bindt bijvoorbeeld van formulierwaarden aan eigenschappen van de NewTodoRequest recordstruct:
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);
Zie de sectie over AsParameters verderop in dit artikel voor meer informatie.
De volledige voorbeeldcode bevindt zich in de opslagplaats AspNetCore.Docs.Samples .
Binding beveiligen vanuit IFormFile en IFormFileCollection
Complexe formulierbinding wordt ondersteund met behulp van IFormFile en IFormFileCollection gebruikmakend van [FromForm].
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();
Parameters die aan het verzoek zijn gebonden met [FromForm] bevatten een antivervalsingstoken. Het antivervalsingstoken wordt gevalideerd wanneer de aanvraag wordt verwerkt. Zie Antiforgery met Minimale API'svoor meer informatie.
Zie Formulierbinding in minimale API's voor meer informatie.
De volledige voorbeeldcode bevindt zich in de opslagplaats AspNetCore.Docs.Samples .
Parameterbinding met afhankelijkheidsinjectie
Parameterbinding voor minimale API's verbindt parameters via afhankelijkheidsinjectie wanneer het type is geconfigureerd als een service. Het is niet nodig om het [FromServices] kenmerk expliciet toe te passen op een parameter. In de volgende code retourneren beide acties de tijd:
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();
Optionele parameters
In route-handlers gedeclareerde parameters worden als verplicht behandeld.
- Als een aanvraag overeenkomt met de route, wordt de route-handler alleen uitgevoerd als alle vereiste parameters zijn opgegeven in de aanvraag.
- Fout bij het opgeven van alle vereiste parameters resulteert in een fout.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 geretourneerd |
/products |
BadHttpRequestException: De vereiste parameter 'int pageNumber' is niet opgegeven uit de querytekenreeks. |
/products/1 |
HTTP 404-fout, geen overeenkomende route |
Als u optioneel wilt maken pageNumber , definieert u het type als optioneel of geeft u een standaardwaarde op:
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 (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 geretourneerd |
/products |
1 geretourneerd |
/products2 |
1 geretourneerd |
De voorgaande null-waarde en standaardwaarde zijn van toepassing op alle bronnen:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
De voorgaande code roept de methode aan met een null-product als er geen verzoekbody wordt verzonden.
OPMERKING: Als er ongeldige gegevens worden opgegeven en de parameter nullable is, wordt de route-handler niet uitgevoerd.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 keerde terug |
/products |
1 keerde terug |
/products?pageNumber=two |
BadHttpRequestException: Kan de parameter "Nullable<int> pageNumber" van 'twee' niet binden. |
/products/two |
HTTP 404-fout, geen overeenkomende route |
Zie de sectie Bindingsfouten voor meer informatie.
Speciale typen
De volgende typen zijn gebonden zonder expliciete kenmerken:
HttpContext: De context die alle informatie over de huidige HTTP-aanvraag of -reactie bevat:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest en HttpResponse: de HTTP-aanvraag en het HTTP-antwoord:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Het annuleringstoken dat is gekoppeld aan de huidige HTTP-aanvraag:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: De gebruiker die is gekoppeld aan de aanvraag, gebonden aan HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Verbind de verzoekinhoud als een Stream of PipeReader
De aanvraagtekst kan worden gebonden als een Stream of PipeReader om efficiënt ondersteuning te bieden voor scenario's waarin de gebruiker gegevens moet verwerken en:
- Sla de gegevens op in Blob Storage of zet de gegevens in een wachtrij bij een wachtrijprovider.
- De opgeslagen gegevens verwerken met een werkproces of een cloudfunctie.
De gegevens kunnen bijvoorbeeld worden verzonden naar Azure Queue Storage- of worden opgeslagen in Azure Blob Storage-.
Met de volgende code wordt een achtergrondwachtrij geïmplementeerd:
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;
}
Met de volgende code wordt de hoofdtekst van de aanvraag gekoppeld aan een Stream:
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);
});
De volgende code toont het volledige Program.cs bestand:
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();
- Bij het lezen van gegevens is het
Streamhetzelfde object alsHttpRequest.Body. - De aanvraagbody wordt niet standaard gebufferd. Nadat de hoofdtekst is gelezen, kan het niet worden terugspoelen. De stream kan niet meerdere keren worden gelezen.
- De
StreamenPipeReaderzijn niet bruikbaar buiten de minimale actie-handler omdat de onderliggende buffers worden verwijderd of hergebruikt.
Bestandsuploads met behulp van IFormFile en IFormFileCollection
Voor het uploaden van bestanden met IFormFile en IFormFileCollection in minimale API's is multipart/form-data codering vereist. De parameternaam in de route-handler moet overeenkomen met de naam van het formulierveld in de aanvraag. Minimale API's bieden geen ondersteuning voor het rechtstreeks binden van de hele aanvraagbody aan een IFormFile parameter zonder formuliercodering.
Als u de volledige aanvraagbody wilt binden, bijvoorbeeld wanneer u werkt met JSON, binaire gegevens of andere inhoudstypen, raadpleegt u:
De volgende code maakt gebruik van IFormFile en IFormFileCollection om het bestand te uploaden:
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();
Geverifieerde aanvragen voor het uploaden van bestanden worden ondersteund met behulp van een Autorisatieheader, een clientcertificaatof een cookie-header.
Binding met formulieren met IFormCollection, IFormFile en IFormFileCollection
Binding vanuit op formulieren gebaseerde parameters met behulp van IFormCollection, IFormFileen IFormFileCollection wordt ondersteund. Metagegevens van OpenAPI- worden afgeleid voor formulierparameters ter ondersteuning van integratie met Swagger UI-.
Met de volgende code worden bestanden geüpload met behulp van een afgeleide binding van het IFormFile type.
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();
Waarschuwing: Bij het implementeren van formulieren moet de app Cross-Site Request Forgery (XSRF/CSRF) aanvallen voorkomen. In de voorgaande code wordt de IAntiforgery service gebruikt om XSRF-aanvallen te voorkomen door een antiforgery-token te genereren en te valideren:
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();
Zie Antiforgery met Minimale API's voor meer informatie over XSRF-aanvallen
Zie Formulierbinding in minimale API's voor meer informatie;
Binden aan verzamelingen en complexe typen vanuit formulieren
Binding wordt ondersteund voor:
- Verzamelingen, bijvoorbeeld Lijst en Woordenlijst
- Complexe typen, bijvoorbeeld
TodoofProject
De volgende code toont:
- Een minimaal eindpunt waarmee een formulierinvoer met meerdere onderdelen wordt gekoppeld aan een complex object.
- Hoe de diensten voor vervalsingsbescherming te gebruiken ter ondersteuning van het genereren en valideren van vervalsingsbeschermingstokens.
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));
}
In de voorgaande code:
- De doelparameter moet worden geannoteerd met het
[FromForm]kenmerk om ondubbelzinnig te zijn van parameters die moeten worden gelezen uit de JSON-hoofdtekst. - Binding van complexe typen of verzamelingen wordt niet ondersteund voor minimale API's die zijn gecompileerd met de aanvraagdelegatiegenerator.
- De markering toont een extra verborgen invoer met een naam van
isCompleteden een waarde vanfalse. Als hetisCompletedselectievakje is ingeschakeld wanneer het formulier wordt verzonden, worden beide waardentrueenfalseals waarden verzonden. Als het selectievakje is uitgeschakeld, wordt alleen de verborgen invoerwaardefalseverzonden. Het ASP.NET Core modelbindingsproces leest alleen de eerste waarde bij het binden aan eenboolwaarde, wat resulteert intrueingeschakelde selectievakjes enfalsevoor niet-ingeschakelde selectievakjes.
Een voorbeeld van de formuliergegevens die naar het voorgaande eindpunt zijn verzonden, ziet er als volgt uit:
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
Matrices en tekenreekswaarden binden vanuit headers en queryreeksen
De volgende code demonstreert het binden van queryreeksen aan een matrix van primitieve typen, tekenreeksmatrices en StringValues:
// 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]}");
Het binden van querystrings of headerwaarden aan een array van complexe typen wordt ondersteund wanneer het type TryParse is geïmplementeerd. De volgende code wordt gekoppeld aan een tekenreeksmatrix en retourneert alle items met de opgegeven tags:
// 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();
});
De volgende code toont het model en de vereiste TryParse implementatie:
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;
}
}
De volgende code wordt gekoppeld aan een int matrix:
// 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();
});
Als u de voorgaande code wilt testen, voegt u het volgende eindpunt toe om de database te vullen met Todo items:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Gebruik een hulpprogramma zoals HttpRepl om de volgende gegevens door te geven aan het vorige eindpunt:
[
{
"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"
}
}
]
De volgende code wordt gekoppeld aan de headersleutel X-Todo-Id en retourneert de Todo items met overeenkomende Id waarden:
// 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();
});
Opmerking
Wanneer een querystring wordt string[] gekoppeld, zal bij afwezigheid van een overeenkomende querystringwaarde een lege array ontstaan in plaats van een null-waarde.
Parameterbinding voor argumentlijsten met [AsParameters]
AsParametersAttribute maakt eenvoudige parameterbinding mogelijk voor typen en geen complexe of recursieve modelbinding.
Houd rekening met de volgende code:
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.
Houd rekening met het volgende GET eindpunt:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Het volgende struct kan worden gebruikt om de voorgaande gemarkeerde parameters te vervangen:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Het geherstructureerde GET eindpunt maakt gebruik van het voorgaande struct met het kenmerk AsParameters :
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());
De volgende code toont extra eindpunten in de app:
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();
});
De volgende klassen worden gebruikt om de parameterlijsten te herstructureren:
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!;
}
De volgende code toont de geherstructureerde eindpunten die gebruikmaken van AsParameters en de voorgaande struct en klassen:
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();
});
De volgende record typen kunnen worden gebruikt om de voorgaande parameters te vervangen:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Het gebruik van een struct met AsParameters kan krachtiger zijn dan het gebruik van een record type.
De volledige voorbeeldcode in de opslagplaats AspNetCore.Docs.Samples .
Aangepaste binding
Er zijn drie manieren om parameterbinding aan te passen:
- Voor route-, query- en headerbindingsbronnen bindt u aangepaste typen door een statische
TryParsemethode voor het type toe te voegen. - Beheer het bindingsproces door een
BindAsyncmethode voor een type te implementeren. - Implementeer de IBindableFromHttpContext<TSelf>-interface voor geavanceerde scenario's om aangepaste bindingslogica direct vanuit de
HttpContextte bieden.
TryParse
TryParse heeft twee API's:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
De volgende code toont Point: 12.3, 10.1 met de URI /map?Point=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 heeft de volgende API's:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
De volgende code toont SortBy:xyz, SortDirection:Desc, CurrentPage:99 met de URI /products?SortBy=xyz&SortDir=Desc&Page=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
}
Aangepaste parameterbinding met IBindableFromHttpContext
ASP.NET Core biedt ondersteuning voor aangepaste parameterbinding in minimale API's met behulp van de IBindableFromHttpContext<TSelf> interface. Met deze interface, geïntroduceerd met de statische abstracte leden van C# 11, kunt u typen maken die rechtstreeks vanuit een HTTP-context kunnen worden gebonden in route-handlerparameters.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Door de IBindableFromHttpContext<TSelf>te implementeren, kunt u aangepaste typen maken die hun eigen bindingslogica verwerken op basis van de HttpContext. Wanneer een route-handler een parameter van dit type bevat, roept het framework automatisch de statische BindAsync methode aan om het exemplaar te maken:
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}";
});
Hier volgt een voorbeeld van een implementatie van een aangepaste parameter die wordt gekoppeld vanuit een HTTP-header:
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
});
}
}
U kunt ook validatie implementeren binnen uw aangepaste bindingslogica:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
De voorbeeldcode weergeven of downloaden (downloaden)
Bindingsfouten
Wanneer de binding mislukt, registreert het framework een foutopsporingsbericht en retourneert het verschillende statuscodes naar de client, afhankelijk van de foutmodus.
| Foutmodus | Type null-parameter | Bindingsbron | Statuscode |
|---|---|---|---|
{ParameterType}.TryParse Retourneert false |
ja | route/query/header | 400 |
{ParameterType}.BindAsync Retourneert null |
ja | gewoonte | 400 |
{ParameterType}.BindAsync Gooit |
maakt niet uit | gewoonte | 500 |
| Kan JSON-hoofdtekst niet deserialiseren | maakt niet uit | lichaam | 400 |
Onjuist inhoudstype (niet application/json) |
maakt niet uit | lichaam | 415 |
Bindingsvoorrang
De regels voor het bepalen van een bindingsbron van een parameter:
- Expliciet kenmerk dat is gedefinieerd voor de parameter (From*-kenmerken) in de volgende volgorde:
- Routewaarden:
[FromRoute] - Queryreeks:
[FromQuery] - Koptekst:
[FromHeader] - Lichaam:
[FromBody] - Formulier:
[FromForm] - Dienst:
[FromServices] - Parameterwaarden:
[AsParameters]
- Routewaarden:
- Speciale typen
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)
- Het parametertype heeft een geldige statische
BindAsyncmethode. - Parametertype is een tekenreeks of heeft een geldige statische
TryParsemethode.- Als de parameternaam bijvoorbeeld bestaat in de routesjabloon,
app.Map("/todo/{id}", (int id) => {});is deze afhankelijk van de route. - Afhankelijk van de querytekenreeks.
- Als de parameternaam bijvoorbeeld bestaat in de routesjabloon,
- Als het parametertype een service is die wordt geleverd door afhankelijkheidsinjectie, wordt die service als bron gebruikt.
- De parameter is afkomstig uit het lichaam.
Opties voor JSON-deserialisatie configureren voor hoofdtekstbinding
De hoofdtekstbindingsbron gebruikt System.Text.Json voor deserialisatie. Het is niet mogelijk om deze standaardinstelling te wijzigen, maar JSON-serialisatie- en deserialisatieopties kunnen worden geconfigureerd.
JSON-deserialisatieopties globaal configureren
Opties die globaal voor een app gelden, kunnen worden geconfigureerd door het aanroepen ConfigureHttpJsonOptions. Het volgende voorbeeld bevat openbare velden en indelingen voor JSON-uitvoer.
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
// }
Omdat de voorbeeldcode zowel serialisatie als deserialisatie configureert, kan deze de uitvoer-JSON lezen NameField en opnemen NameField .
JSON-deserialisatieopties configureren voor een eindpunt
ReadFromJsonAsync heeft overbelastingen die een JsonSerializerOptions object accepteren. Het volgende voorbeeld bevat openbare velden en indelingen voor JSON-uitvoer.
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
// }
Aangezien de voorgaande code de aangepaste opties alleen toepast op deserialisatie, is NameField niet opgenomen in de uitvoer-JSON.
De hoofdtekst van de aanvraag lezen
Lees de hoofdtekst van de aanvraag rechtstreeks met behulp van een HttpContext of HttpRequest parameter:
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();
De voorgaande code:
- Hiermee krijgt u toegang tot de verzoekbody met behulp van HttpRequest.BodyReader.
- Kopieert de hoofdtekst van de aanvraag naar een lokaal bestand.
Parameterbinding is het proces van het converteren van aanvraaggegevens naar sterk getypte parameters die worden uitgedrukt door route-handlers. Een bindingsbron bepaalt waar parameters van afhankelijk zijn. Bindingsbronnen kunnen expliciet of afgeleid zijn op basis van de HTTP-methode en het parametertype.
Ondersteunde bindingsbronnen:
- Routewaarden
- Querystring
- Koptekst
- Hoofdtekst (als JSON)
- Services geleverd door afhankelijkheidsinjectie
- Op maat gemaakt
Binding vanuit formulierwaarden wordt niet systeemeigen ondersteund in .NET 6 en 7.
De volgende GET route-handler maakt gebruik van enkele van deze parameterbindingsbronnen:
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 { }
In de volgende tabel ziet u de relatie tussen de parameters die in het voorgaande voorbeeld worden gebruikt en de bijbehorende bindingsbronnen.
| Kenmerk | Bindingsbron |
|---|---|
id |
routewaarde |
page |
querystring |
customHeader |
koptekst |
service |
Geleverd door afhankelijkheidsinjectie |
De HTTP-methodenGET, HEADen OPTIONSDELETE binden niet impliciet vanuit de hoofdtekst. Als u verbinding wilt maken vanaf hoofdtekst (als JSON) voor deze HTTP-methoden, bindt u expliciet met [FromBody] of leest u van de HttpRequest.
In het volgende voorbeeld wordt voor de person parameter een bindingsbron van body, verpakt als JSON, gebruikt.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
De parameters in de voorgaande voorbeelden zijn allemaal automatisch afhankelijk van aanvraaggegevens. Ter illustratie van het gemak dat parameterbinding biedt, laten de volgende routehandlers zien hoe u aanvraaggegevens rechtstreeks vanuit de aanvraag kunt lezen:
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>();
// ...
});
Expliciete parameterbinding
Kenmerken kunnen worden gebruikt om expliciet te declareren waar parameters van afhankelijk zijn.
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);
| Kenmerk | Bindingsbron |
|---|---|
id |
routewaarde met de naam id |
page |
querystring met de naam "p" |
service |
Geleverd door afhankelijkheidsinjectie |
contentType |
header met de naam "Content-Type" |
Opmerking
Binding vanuit formulierwaarden wordt niet systeemeigen ondersteund in .NET 6 en 7.
Parameterbinding met afhankelijkheidsinjectie
Parameterbinding voor minimale API's verbindt parameters via afhankelijkheidsinjectie wanneer het type is geconfigureerd als een service. Het is niet nodig om het [FromServices] kenmerk expliciet toe te passen op een parameter. In de volgende code retourneren beide acties de tijd:
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();
Optionele parameters
In route-handlers gedeclareerde parameters worden als verplicht behandeld.
- Als een aanvraag overeenkomt met de route, wordt de route-handler alleen uitgevoerd als alle vereiste parameters zijn opgegeven in de aanvraag.
- Fout bij het opgeven van alle vereiste parameters resulteert in een fout.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 geretourneerd |
/products |
BadHttpRequestException: De vereiste parameter 'int pageNumber' is niet opgegeven uit de querytekenreeks. |
/products/1 |
HTTP 404-fout, geen overeenkomende route |
Als u optioneel wilt maken pageNumber , definieert u het type als optioneel of geeft u een standaardwaarde op:
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 (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 geretourneerd |
/products |
1 geretourneerd |
/products2 |
1 geretourneerd |
De voorgaande null-waarde en standaardwaarde zijn van toepassing op alle bronnen:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
De voorgaande code roept de methode aan met een null-product als er geen verzoekbody wordt verzonden.
OPMERKING: Als er ongeldige gegevens worden opgegeven en de parameter nullable is, wordt de route-handler niet uitgevoerd.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI (Uniform Resource Identifier) | resultaat |
|---|---|
/products?pageNumber=3 |
3 keerde terug |
/products |
1 keerde terug |
/products?pageNumber=two |
BadHttpRequestException: Kan de parameter "Nullable<int> pageNumber" van 'twee' niet binden. |
/products/two |
HTTP 404-fout, geen overeenkomende route |
Zie de sectie Bindingsfouten voor meer informatie.
Speciale typen
De volgende typen zijn gebonden zonder expliciete kenmerken:
HttpContext: De context die alle informatie over de huidige HTTP-aanvraag of -reactie bevat:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest en HttpResponse: de HTTP-aanvraag en het HTTP-antwoord:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Het annuleringstoken dat is gekoppeld aan de huidige HTTP-aanvraag:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: De gebruiker die is gekoppeld aan de aanvraag, gebonden aan HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Verbind de verzoekinhoud als een Stream of PipeReader
De aanvraagtekst kan worden gebonden als een Stream of PipeReader om efficiënt ondersteuning te bieden voor scenario's waarin de gebruiker gegevens moet verwerken en:
- Sla de gegevens op in Blob Storage of zet de gegevens in een wachtrij bij een wachtrijprovider.
- De opgeslagen gegevens verwerken met een werkproces of een cloudfunctie.
De gegevens kunnen bijvoorbeeld worden verzonden naar Azure Queue Storage- of worden opgeslagen in Azure Blob Storage-.
Met de volgende code wordt een achtergrondwachtrij geïmplementeerd:
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;
}
Met de volgende code wordt de hoofdtekst van de aanvraag gekoppeld aan een Stream:
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);
});
De volgende code toont het volledige Program.cs bestand:
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();
- Bij het lezen van gegevens is het
Streamhetzelfde object alsHttpRequest.Body. - De aanvraagbody wordt niet standaard gebufferd. Nadat de hoofdtekst is gelezen, kan het niet worden terugspoelen. De stream kan niet meerdere keren worden gelezen.
- De
StreamenPipeReaderzijn niet bruikbaar buiten de minimale actie-handler omdat de onderliggende buffers worden verwijderd of hergebruikt.
Bestandsuploads met behulp van IFormFile en IFormFileCollection
De volgende code maakt gebruik van IFormFile en IFormFileCollection om het bestand te uploaden:
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();
Geverifieerde aanvragen voor het uploaden van bestanden worden ondersteund met behulp van een Autorisatieheader, een clientcertificaatof een cookie-header.
Er is geen ingebouwde ondersteuning voor antivervalsing in ASP.NET Core in .NET 7.
Antivervalsingsfunctie is beschikbaar in ASP.NET Core in .NET 8 of hoger. Het kan echter worden geïmplementeerd met behulp van de IAntiforgery-service.
Matrices en tekenreekswaarden binden vanuit headers en queryreeksen
De volgende code demonstreert het binden van queryreeksen aan een matrix van primitieve typen, tekenreeksmatrices en StringValues:
// 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]}");
Het binden van querystrings of headerwaarden aan een array van complexe typen wordt ondersteund wanneer het type TryParse is geïmplementeerd. De volgende code wordt gekoppeld aan een tekenreeksmatrix en retourneert alle items met de opgegeven tags:
// 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();
});
De volgende code toont het model en de vereiste TryParse implementatie:
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;
}
}
De volgende code wordt gekoppeld aan een int matrix:
// 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();
});
Als u de voorgaande code wilt testen, voegt u het volgende eindpunt toe om de database te vullen met Todo items:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Gebruik een hulpprogramma voor het testen van API's zoals HttpRepl om de volgende gegevens naar het vorige eindpunt te verzenden:
[
{
"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"
}
}
]
De volgende code wordt gekoppeld aan de headersleutel X-Todo-Id en retourneert de Todo items met overeenkomende Id waarden:
// 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();
});
Opmerking
Wanneer een querystring wordt string[] gekoppeld, zal bij afwezigheid van een overeenkomende querystringwaarde een lege array ontstaan in plaats van een null-waarde.
Parameterbinding voor argumentlijsten met [AsParameters]
AsParametersAttribute maakt eenvoudige parameterbinding mogelijk voor typen en geen complexe of recursieve modelbinding.
Houd rekening met de volgende code:
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.
Houd rekening met het volgende GET eindpunt:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Het volgende struct kan worden gebruikt om de voorgaande gemarkeerde parameters te vervangen:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Het geherstructureerde GET eindpunt maakt gebruik van het voorgaande struct met het kenmerk AsParameters :
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());
De volgende code toont extra eindpunten in de app:
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();
});
De volgende klassen worden gebruikt om de parameterlijsten te herstructureren:
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!;
}
De volgende code toont de geherstructureerde eindpunten die gebruikmaken van AsParameters en de voorgaande struct en klassen:
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();
});
De volgende record typen kunnen worden gebruikt om de voorgaande parameters te vervangen:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Het gebruik van een struct met AsParameters kan krachtiger zijn dan het gebruik van een record type.
De volledige voorbeeldcode in de opslagplaats AspNetCore.Docs.Samples .
Aangepaste binding
Er zijn drie manieren om parameterbinding aan te passen:
- Voor route-, query- en headerbindingsbronnen bindt u aangepaste typen door een statische
TryParsemethode voor het type toe te voegen. - Beheer het bindingsproces door een
BindAsyncmethode voor een type te implementeren. - Implementeer de IBindableFromHttpContext<TSelf>-interface voor geavanceerde scenario's om aangepaste bindingslogica direct vanuit de
HttpContextte bieden.
TryParse
TryParse heeft twee API's:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
De volgende code toont Point: 12.3, 10.1 met de URI /map?Point=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 heeft de volgende API's:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
De volgende code toont SortBy:xyz, SortDirection:Desc, CurrentPage:99 met de URI /products?SortBy=xyz&SortDir=Desc&Page=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
}
Aangepaste parameterbinding met IBindableFromHttpContext
ASP.NET Core biedt ondersteuning voor aangepaste parameterbinding in minimale API's met behulp van de IBindableFromHttpContext<TSelf> interface. Met deze interface, geïntroduceerd met de statische abstracte leden van C# 11, kunt u typen maken die rechtstreeks vanuit een HTTP-context kunnen worden gebonden in route-handlerparameters.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Door de IBindableFromHttpContext<TSelf> interface te implementeren, kunt u aangepaste typen maken die hun eigen bindingslogica verwerken vanuit httpContext. Wanneer een route-handler een parameter van dit type bevat, roept het framework automatisch de statische BindAsync-methode aan om het exemplaar te maken:
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}";
});
Hier volgt een voorbeeld van een implementatie van een aangepaste parameter die wordt gekoppeld vanuit een HTTP-header:
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
});
}
}
U kunt ook validatie implementeren binnen uw aangepaste bindingslogica:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
De voorbeeldcode weergeven of downloaden (downloaden)
Bindingsfouten
Wanneer de binding mislukt, registreert het framework een foutopsporingsbericht en retourneert het verschillende statuscodes naar de client, afhankelijk van de foutmodus.
| Foutmodus | Type null-parameter | Bindingsbron | Statuscode |
|---|---|---|---|
{ParameterType}.TryParse Retourneert false |
ja | route/query/header | 400 |
{ParameterType}.BindAsync Retourneert null |
ja | gewoonte | 400 |
{ParameterType}.BindAsync Gooit |
maakt niet uit | gewoonte | 500 |
| Kan JSON-hoofdtekst niet deserialiseren | maakt niet uit | lichaam | 400 |
Onjuist inhoudstype (niet application/json) |
maakt niet uit | lichaam | 415 |
Bindingsvoorrang
De regels voor het bepalen van een bindingsbron van een parameter:
- Expliciet kenmerk dat is gedefinieerd voor de parameter (From*-kenmerken) in de volgende volgorde:
- Routewaarden:
[FromRoute] - Queryreeks:
[FromQuery] - Koptekst:
[FromHeader] - Lichaam:
[FromBody] - Dienst:
[FromServices] - Parameterwaarden:
[AsParameters]
- Routewaarden:
- Speciale typen
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)
- Het parametertype heeft een geldige statische
BindAsyncmethode. - Parametertype is een tekenreeks of heeft een geldige statische
TryParsemethode.- Als de parameternaam bestaat in de routesjabloon. In
app.Map("/todo/{id}", (int id) => {});,idis gebonden aan de route. - Afhankelijk van de querytekenreeks.
- Als de parameternaam bestaat in de routesjabloon. In
- Als het parametertype een service is die wordt geleverd door afhankelijkheidsinjectie, wordt die service als bron gebruikt.
- De parameter is afkomstig uit het lichaam.
Opties voor JSON-deserialisatie configureren voor hoofdtekstbinding
De hoofdtekstbindingsbron gebruikt System.Text.Json voor deserialisatie. Het is niet mogelijk om deze standaardinstelling te wijzigen, maar JSON-serialisatie- en deserialisatieopties kunnen worden geconfigureerd.
JSON-deserialisatieopties globaal configureren
Opties die globaal voor een app gelden, kunnen worden geconfigureerd door het aanroepen ConfigureHttpJsonOptions. Het volgende voorbeeld bevat openbare velden en indelingen voor JSON-uitvoer.
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
// }
Omdat de voorbeeldcode zowel serialisatie als deserialisatie configureert, kan deze de uitvoer-JSON lezen NameField en opnemen NameField .
JSON-deserialisatieopties configureren voor een eindpunt
ReadFromJsonAsync heeft overbelastingen die een JsonSerializerOptions object accepteren. Het volgende voorbeeld bevat openbare velden en indelingen voor JSON-uitvoer.
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
// }
Aangezien de voorgaande code de aangepaste opties alleen toepast op deserialisatie, is NameField niet opgenomen in de uitvoer-JSON.
De hoofdtekst van de aanvraag lezen
Lees de hoofdtekst van de aanvraag rechtstreeks met behulp van een HttpContext of HttpRequest parameter:
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();
De voorgaande code:
- Hiermee krijgt u toegang tot de verzoekbody met behulp van HttpRequest.BodyReader.
- Kopieert de hoofdtekst van de aanvraag naar een lokaal bestand.