Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .
Viktigt!
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .
Parameterbindning är processen att konvertera begärandedata till starkt skrivna parametrar som uttrycks av routningshanterare. En bindningskälla avgör var parametrarna är bundna från. Bindningskällor kan vara explicita eller härledda baserat på HTTP-metod och parametertyp.
Bindningskällor som stöds:
- Vägvärden
- Frågesträng
- Rubrik
- Kropp (som JSON)
- Formulärvärden
- Tjänster som tillhandahålls av dependency injection
- Skräddarsydd
Följande GET routningshanterare använder några av dessa parameterbindningskällor:
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 { }
I följande tabell visas relationen mellan de parametrar som används i föregående exempel och de associerade bindningskällorna.
| Parameter | Bindningskälla |
|---|---|
id |
ruttvärde |
page |
Frågesträngen |
customHeader |
rubrik |
service |
Tillhandahålls av beroendeinjektion |
HTTP-metoderna GET, HEAD, OPTIONSoch DELETE binder inte implicit från brödtexten. Om du vill binda från brödtexten (som JSON) för dessa HTTP-metoder du uttryckligen binda med [FromBody] eller läsa från HttpRequest.
I följande exempel använder POST-routningshanteraren en bindningskälla för brödtext (som JSON) för parametern person:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Parametrarna i föregående exempel är alla bundna från begärandedata automatiskt. För att demonstrera bekvämligheten med parameterbindningen visar följande routningshanterare hur du läser begärandedata direkt från begäran:
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 parameterbindning
Attribut kan användas för att explicit deklarera var parametrarna är bundna från.
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);
| Parameter | Bindningskälla |
|---|---|
id |
routningsvärde med namnet id |
page |
frågesträng med namnet "p" |
service |
Tillhandahålls av beroendeinjektion |
contentType |
rubrik med namnet "Content-Type" |
Explicit bindning från formulärvärden
Det [FromForm] attributet binder formulärvärden:
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.
Ett alternativ är att använda attributet [AsParameters] med en anpassad typ som har egenskaper som kommenterats med [FromForm]. Följande kod binder, till exempel, från formulärvärden till egenskaper för NewTodoRequest record struct:
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);
Mer information finns i avsnittet om AsParameters senare i den här artikeln.
Den fullständiga exempelkoden finns på lagringsplatsen AspNetCore.Docs.Samples.
Säker bindning från IFormFile och IFormFileCollection
Komplex formulärbindning stöds med hjälp av IFormFile och IFormFileCollection med hjälp av [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();
Parametrar som är kopplade till begäran med [FromForm] inkluderar en antiförfalsknings-token. Antiforgery-token verifieras när begäran bearbetas. För mer information, se Förfalskningsskydd med minimala API:er.
Mer information finns i Formulärbindning i minimala API:er.
Den fullständiga exempelkoden finns på lagringsplatsen AspNetCore.Docs.Samples.
Parameterbindning med beroendeinmatning
Parameterbindning för minimala API:er binder parametrar via beroendeinmatning när typen konfigureras som en tjänst. Det är inte nödvändigt att uttryckligen tillämpa attributet [FromServices] på en parameter. I följande kod returnerar båda åtgärderna tiden:
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();
Valfria parametrar
Parametrar som deklareras i routningshanterare behandlas efter behov:
- Om en begäran matchar vägen körs routningshanteraren endast om alla obligatoriska parametrar anges i begäran.
- Om du inte anger alla obligatoriska parametrar resulterar det i ett fel.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI (Uniform Resource Identifier) | resultat |
|---|---|
/products?pageNumber=3 |
3 returnerade |
/products |
BadHttpRequestException: Den obligatoriska parametern "int pageNumber" angavs inte från frågesträngen. |
/products/1 |
HTTP 404-fel, ingen matchande väg |
Om du vill göra pageNumber valfri definierar du typen som valfri eller anger ett standardvärde:
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) | resultat |
|---|---|
/products?pageNumber=3 |
3 returnerade |
/products |
1 returnerade |
/products2 |
1 returnerade |
Föregående null- och standardvärde gäller för alla källor:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Föregående kod anropar metoden med en null-produkt om ingen begärandetext skickas.
NOTE: Om ogiltiga data anges och parametern är null kan routningshanteraren inte köras.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI (Uniform Resource Identifier) | resultat |
|---|---|
/products?pageNumber=3 |
3 har returnerats |
/products |
1 har returnerats |
/products?pageNumber=two |
BadHttpRequestException: Det gick inte att binda parametern "Nullable<int> pageNumber" från "två". |
/products/two |
HTTP 404-fel, ingen matchande väg |
För mer information, se avsnittet bindningsfel.
Särskilda typer
Följande typer är bundna utan explicita attribut:
HttpContext: Kontexten som innehåller all information om den aktuella HTTP-begäran eller -svaret:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest och HttpResponse: HTTP-begäran och HTTP-svar:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Den annulleringstoken som är associerad med den aktuella HTTP-begäran:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: Användaren som är associerad med begäran, bunden från HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Binda begärandetexten som en Stream eller PipeReader
Begärandetexten kan bindas som en Stream eller PipeReader för att effektivt stödja scenarier där användaren måste bearbeta data och:
- Lagra data i bloblagring eller köa data till en köleverantör.
- Bearbeta lagrade data med en arbetsprocess eller molnfunktion.
Data kan till exempel läggas i kö för lagring i Azure Queue Storage eller lagras i Azure Blob Storage.
Följande kod implementerar en bakgrundskö:
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;
}
Följande kod binder begärandetexten till en 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);
});
Följande kod visar den fullständiga Program.cs filen:
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();
- När du läser data är
Streamsamma objekt somHttpRequest.Body. - Begärandetexten buffrades inte som standard. När brödtexten har lästs kan den inte spolas tillbaka. Dataströmmen kan inte läsas flera gånger.
-
StreamochPipeReaderkan inte användas utanför den minimala åtgärdshanteraren eftersom de underliggande buffertarna tas bort eller återanvänds.
Filuppladdningar med IFormFile och IFormFileCollection
Filuppladdningar med IFormFile och IFormFileCollection i minimala API:er kräver multipart/form-data-kodning. Parameternamnet i routningshanteraren måste matcha formulärfältets namn i begäran. Minimala API:er stöder inte bindning av hela begärandetexten direkt till en IFormFile parameter utan formulärkodning.
Om du behöver binda hela begärandetexten, till exempel när du arbetar med JSON, binära data eller andra innehållstyper, se:
Följande kod använder IFormFile och IFormFileCollection för att ladda upp filen:
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();
Autentiserade filuppladdningsbegäranden stöds med hjälp av ett auktoriseringshuvud, ett klientcertifikateller ett cookie-huvud.
Binda till formulär med IFormCollection, IFormFile och IFormFileCollection
Bindning från formulärbaserade parametrar med hjälp av IFormCollection, IFormFileoch IFormFileCollection stöds. OpenAPI- metadata härleds automatiskt för formulärparametrar för att stödja integrering med Swagger UI.
Följande kod laddar upp filer med hjälp av härledd bindning från typen IFormFile.
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();
Varning: När formulär implementeras måste appen förhindraXSRF-attacker (Cross-Site Request Forgery). I föregående kod används IAntiforgery-tjänsten för att förhindra XSRF-attacker genom att generera och verifiera en antiforgery-token:
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();
Mer information om XSRF-attacker finns i Antiforgery med minimala API:er
Mer information finns i Formulärbindning i minimala API:er;
Binda till samlingar och komplexa typer från formulär
Stöd för bindning gäller:
- Samlingar, till exempel List och Dictionary
- Komplexa typer, till exempel
TodoellerProject
Följande kod visar:
- En minimal slutpunkt som binder en formulärinmatning i flera delar till ett komplext objekt.
- Hur man använder antiforgery-tjänsterna för att stödja generering och validering av antiforgery-token.
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));
}
I koden ovan:
- Målparametern måste kommenteras med attributet
[FromForm]för att skilja sig från parametrar som ska läsas från JSON-brödtexten. - Bindning från komplexa typer eller samlingstyper stöds inte för minimala API:er som kompileras med generatorn för begärandedelegat.
- Markeringen visar ytterligare dolda indata med namnet
isCompletedoch värdetfalse. Om kryssrutanisCompletedmarkeras när formuläret skickas skickas både värdentrueochfalsesom värden. Om kryssrutan är avmarkerad skickas endast det dolda indatavärdetfalse. Den ASP.NET Core-modellbindningsprocessen läser bara det första värdet vid bindning till ettboolvärde, vilket resulterar itrueför markerade kryssrutor ochfalseför avmarkerade kryssrutor.
Ett exempel på formulärdata som skickas till föregående slutpunkt ser ut så här:
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
Binda matriser och strängvärden från rubriker och frågesträngar
Följande kod visar bindning av frågesträngar till en matris med primitiva typer, strängmatriser och 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]}");
Bindning av frågesträngar eller sidhuvudvärden till en matris med komplexa typer stöds när typen har TryParse implementerats. Följande kod binder till en strängmatris och returnerar alla objekt med de angivna taggarna:
// 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();
});
Följande kod visar modellen och den nödvändiga TryParse implementeringen:
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;
}
}
Följande kod binder till en int matris:
// 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();
});
Om du vill testa koden ovan lägger du till följande slutpunkt för att fylla databasen med Todo objekt:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Använd ett verktyg som HttpRepl för att skicka följande data till föregående slutpunkt:
[
{
"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"
}
}
]
Följande kod binder till huvudnyckeln X-Todo-Id och returnerar Todo objekt med matchande Id värden:
// 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();
});
Anmärkning
När du binder en string[] från en frågesträng resulterar frånvaron av ett matchande frågesträngsvärde i en tom matris i stället för ett null-värde.
Parameterbindning för argumentlistor med [AsParameters]
AsParametersAttribute möjliggör enkel parameterbindning till typer och inte komplex eller rekursiv modellbindning.
Överväg följande kod:
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.
Överväg följande GET slutpunkt:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Följande struct kan användas för att ersätta de föregående markerade parametrarna:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Den omstrukturerade GET-slutpunkten använder föregående struct med attributet 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());
Följande kod visar ytterligare slutpunkter i appen:
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();
});
Följande klasser används för att omstrukturera parameterlistorna:
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!;
}
Följande kod visar de refaktorerade slutpunkterna med hjälp av AsParameters samt de tidigare struct och klasser.
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();
});
Följande record typer kan användas för att ersätta de föregående parametrarna:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Att använda en struct med AsParameters kan vara mer högpresterande än att använda en record typ.
Den kompletta exempelkoden i repositoryn AspNetCore.Docs.Samples.
Anpassad bindning
Det finns tre sätt att anpassa parameterbindning:
- För routnings-, fråge- och rubrikbindningskällor binder du anpassade typer genom att lägga till en statisk
TryParsemetod för typen. - Kontrollera bindningsprocessen genom att implementera en
BindAsync-metod för en typ. - För avancerade scenarier implementerar du IBindableFromHttpContext<TSelf> gränssnittet för att tillhandahålla anpassad bindningslogik direkt från
HttpContext.
TryParse
TryParse har två API:er:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Följande kod visar Point: 12.3, 10.1 med 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 har följande API:er:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Följande kod visar SortBy:xyz, SortDirection:Desc, CurrentPage:99 med 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
}
Anpassad parameterbindning med IBindableFromHttpContext
ASP.NET Core har stöd för anpassad parameterbindning i Minimala API:er med hjälp av IBindableFromHttpContext<TSelf> gränssnittet. Med det här gränssnittet, som introducerades med C# 11:s statiska abstrakta medlemmar, kan du skapa typer som kan bindas från en HTTP-kontext direkt i routningshanterarparametrarna.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Genom att implementera IBindableFromHttpContext<TSelf>kan du skapa anpassade typer som hanterar sin egen bindningslogik från HttpContext. När en routningshanterare innehåller en parameter av den här typen anropar ramverket automatiskt den statiska BindAsync metoden för att skapa instansen:
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}";
});
Följande är ett exempel på implementering av en anpassad parameter som binder från ett HTTP-huvud:
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
});
}
}
Du kan också implementera validering i din anpassade bindningslogik:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
Visa eller ladda ned exempelkoden (ladda ned)
Bindningsfel
När bindningen misslyckas loggar ramverket ett felsökningsmeddelande och returnerar olika statuskoder till klienten beroende på felläget.
| Felläge | Parametertyp som kan vara null | Bindningskälla | Statuskod |
|---|---|---|---|
{ParameterType}.TryParse returnerar false |
Ja | rutt/förfrågan/huvud | 400 |
{ParameterType}.BindAsync returnerar null |
Ja | anpassad | 400 |
{ParameterType}.BindAsync kastar |
spelar ingen roll | anpassad | 500 |
| Det gick inte att deserialisera JSON-data | spelar ingen roll | kropp | 400 |
Fel innehållstyp (inte application/json) |
spelar ingen roll | kropp | 415 |
Bindningsprioritet
Reglerna för att fastställa en bindningskälla från en parameter:
- Den explicita attribut som definieras på parametern (From*-attribut) i följande ordning:
- Routvärden:
[FromRoute] - Frågesträng:
[FromQuery] - Rubrik:
[FromHeader] - Brödtext:
[FromBody] - Formulär:
[FromForm] - Tjänst:
[FromServices] - Parametervärden:
[AsParameters]
- Routvärden:
- Särskilda typer
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)
- Parametertypen har en giltig statisk
BindAsync-metod. - Parametertypen är en sträng eller har en giltig statisk
TryParse-metod.- Om parameternamnet finns i routemallen, till exempel
app.Map("/todo/{id}", (int id) => {});, då binds det från routen. - Bunden till frågesträngen.
- Om parameternamnet finns i routemallen, till exempel
- Om parametertypen är en tjänst som tillhandahålls av beroendeinmatning använder den tjänsten som källa.
- Parametern kommer från kroppen.
Konfigurera JSON-deserialiseringsalternativ för brödtextbindning
Kroppsbindningskällan använder System.Text.Json för deserialisering. Det är inte möjligt att ändra den här standardinställningen, men JSON-serialiserings- och deserialiseringsalternativ kan konfigureras.
Konfigurera JSON-deserialiseringsalternativ globalt
Alternativ som gäller globalt för en app kan konfigureras genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
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
// }
Eftersom exempelkoden konfigurerar både serialisering och deserialisering kan den läsa NameField och inkludera NameField i utdata-JSON.
Konfigurera JSON-deserialiseringsalternativ för en slutpunkt
ReadFromJsonAsync har överbelastningar som accepterar ett JsonSerializerOptions-objekt. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
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
// }
Eftersom föregående kod endast tillämpar de anpassade alternativen för deserialisering utesluter utdata-JSON NameField.
Läs begärandetexten
Läs begärandetexten direkt med hjälp av en HttpContext- eller 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();
Föregående kod:
- Åtkomst till begärandetexten genom HttpRequest.BodyReader.
- Kopierar begärandetexten till en lokal fil.
Parameterbindning är processen att konvertera begärandedata till starkt skrivna parametrar som uttrycks av routningshanterare. En bindningskälla avgör var parametrarna är bundna från. Bindningskällor kan vara explicita eller härledda baserat på HTTP-metod och parametertyp.
Bindningskällor som stöds:
- Vägvärden
- Frågesträng
- Rubrik
- Kropp (som JSON)
- Tjänster som tillhandahålls av dependency injection
- Skräddarsydd
Bindning från formulärvärden är inte stöds internt i .NET 6 och 7.
Följande GET routningshanterare använder några av dessa parameterbindningskällor:
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 { }
I följande tabell visas relationen mellan de parametrar som används i föregående exempel och de associerade bindningskällorna.
| Parameter | Bindningskälla |
|---|---|
id |
ruttvärde |
page |
Frågesträngen |
customHeader |
rubrik |
service |
Tillhandahålls av beroendeinjektion |
HTTP-metoderna GET, HEAD, OPTIONSoch DELETE binder inte implicit från brödtexten. Om du vill binda från brödtexten (som JSON) för dessa HTTP-metoder du uttryckligen binda med [FromBody] eller läsa från HttpRequest.
I följande exempel använder POST-routningshanteraren en bindningskälla för brödtext (som JSON) för parametern person:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Parametrarna i föregående exempel är alla bundna från begärandedata automatiskt. För att demonstrera bekvämligheten med parameterbindningen visar följande routningshanterare hur du läser begärandedata direkt från begäran:
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 parameterbindning
Attribut kan användas för att explicit deklarera var parametrarna är bundna från.
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);
| Parameter | Bindningskälla |
|---|---|
id |
routningsvärde med namnet id |
page |
frågesträng med namnet "p" |
service |
Tillhandahålls av beroendeinjektion |
contentType |
rubrik med namnet "Content-Type" |
Anmärkning
Bindning från formulärvärden är inte stöds internt i .NET 6 och 7.
Parameterbindning med beroendeinmatning
Parameterbindning för minimala API:er binder parametrar via beroendeinmatning när typen konfigureras som en tjänst. Det är inte nödvändigt att uttryckligen tillämpa attributet [FromServices] på en parameter. I följande kod returnerar båda åtgärderna tiden:
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();
Valfria parametrar
Parametrar som deklareras i routningshanterare behandlas efter behov:
- Om en begäran matchar vägen körs routningshanteraren endast om alla obligatoriska parametrar anges i begäran.
- Om du inte anger alla obligatoriska parametrar resulterar det i ett fel.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI (Uniform Resource Identifier) | resultat |
|---|---|
/products?pageNumber=3 |
3 returnerade |
/products |
BadHttpRequestException: Den obligatoriska parametern "int pageNumber" angavs inte från frågesträngen. |
/products/1 |
HTTP 404-fel, ingen matchande väg |
Om du vill göra pageNumber valfri definierar du typen som valfri eller anger ett standardvärde:
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) | resultat |
|---|---|
/products?pageNumber=3 |
3 returnerade |
/products |
1 returnerade |
/products2 |
1 returnerade |
Föregående null- och standardvärde gäller för alla källor:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Föregående kod anropar metoden med en null-produkt om ingen begärandetext skickas.
NOTE: Om ogiltiga data anges och parametern är null kan routningshanteraren inte köras.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI (Uniform Resource Identifier) | resultat |
|---|---|
/products?pageNumber=3 |
3 har returnerats |
/products |
1 har returnerats |
/products?pageNumber=two |
BadHttpRequestException: Det gick inte att binda parametern "Nullable<int> pageNumber" från "två". |
/products/two |
HTTP 404-fel, ingen matchande väg |
För mer information, se avsnittet bindningsfel.
Särskilda typer
Följande typer är bundna utan explicita attribut:
HttpContext: Kontexten som innehåller all information om den aktuella HTTP-begäran eller -svaret:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest och HttpResponse: HTTP-begäran och HTTP-svar:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: Den annulleringstoken som är associerad med den aktuella HTTP-begäran:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: Användaren som är associerad med begäran, bunden från HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Binda begärandetexten som en Stream eller PipeReader
Begärandetexten kan bindas som en Stream eller PipeReader för att effektivt stödja scenarier där användaren måste bearbeta data och:
- Lagra data i bloblagring eller köa data till en köleverantör.
- Bearbeta lagrade data med en arbetsprocess eller molnfunktion.
Data kan till exempel läggas i kö för lagring i Azure Queue Storage eller lagras i Azure Blob Storage.
Följande kod implementerar en bakgrundskö:
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;
}
Följande kod binder begärandetexten till en 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);
});
Följande kod visar den fullständiga Program.cs filen:
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();
- När du läser data är
Streamsamma objekt somHttpRequest.Body. - Begärandetexten buffrades inte som standard. När brödtexten har lästs kan den inte spolas tillbaka. Dataströmmen kan inte läsas flera gånger.
-
StreamochPipeReaderkan inte användas utanför den minimala åtgärdshanteraren eftersom de underliggande buffertarna tas bort eller återanvänds.
Filuppladdningar med IFormFile och IFormFileCollection
Följande kod använder IFormFile och IFormFileCollection för att ladda upp filen:
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();
Autentiserade filuppladdningsbegäranden stöds med hjälp av ett auktoriseringshuvud, ett klientcertifikateller ett cookie-huvud.
Det finns inget inbyggt stöd för antiforgery i ASP.NET Core i .NET 7.
Antiforgery finns i ASP.NET Core i .NET 8 eller senare. Den kan dock implementeras med hjälp av IAntiforgery-tjänsten.
Binda matriser och strängvärden från rubriker och frågesträngar
Följande kod visar bindning av frågesträngar till en matris med primitiva typer, strängmatriser och 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]}");
Bindning av frågesträngar eller sidhuvudvärden till en matris med komplexa typer stöds när typen har TryParse implementerats. Följande kod binder till en strängmatris och returnerar alla objekt med de angivna taggarna:
// 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();
});
Följande kod visar modellen och den nödvändiga TryParse implementeringen:
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;
}
}
Följande kod binder till en int matris:
// 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();
});
Om du vill testa koden ovan lägger du till följande slutpunkt för att fylla databasen med Todo objekt:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Använd ett API-testverktyg som HttpRepl för att skicka följande data till föregående slutpunkt:
[
{
"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"
}
}
]
Följande kod binder till huvudnyckeln X-Todo-Id och returnerar Todo objekt med matchande Id värden:
// 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();
});
Anmärkning
När du binder en string[] från en frågesträng resulterar frånvaron av ett matchande frågesträngsvärde i en tom matris i stället för ett null-värde.
Parameterbindning för argumentlistor med [AsParameters]
AsParametersAttribute möjliggör enkel parameterbindning till typer och inte komplex eller rekursiv modellbindning.
Överväg följande kod:
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.
Överväg följande GET slutpunkt:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Följande struct kan användas för att ersätta de föregående markerade parametrarna:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Den omstrukturerade GET-slutpunkten använder föregående struct med attributet 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());
Följande kod visar ytterligare slutpunkter i appen:
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();
});
Följande klasser används för att omstrukturera parameterlistorna:
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!;
}
Följande kod visar de refaktorerade slutpunkterna med hjälp av AsParameters samt de tidigare struct och klasser.
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();
});
Följande record typer kan användas för att ersätta de föregående parametrarna:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Att använda en struct med AsParameters kan vara mer högpresterande än att använda en record typ.
Den kompletta exempelkoden i repositoryn AspNetCore.Docs.Samples.
Anpassad bindning
Det finns tre sätt att anpassa parameterbindning:
- För routnings-, fråge- och rubrikbindningskällor binder du anpassade typer genom att lägga till en statisk
TryParsemetod för typen. - Kontrollera bindningsprocessen genom att implementera en
BindAsync-metod för en typ. - För avancerade scenarier implementerar du IBindableFromHttpContext<TSelf> gränssnittet för att tillhandahålla anpassad bindningslogik direkt från
HttpContext.
TryParse
TryParse har två API:er:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Följande kod visar Point: 12.3, 10.1 med 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 har följande API:er:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Följande kod visar SortBy:xyz, SortDirection:Desc, CurrentPage:99 med 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
}
Anpassad parameterbindning med IBindableFromHttpContext
ASP.NET Core har stöd för anpassad parameterbindning i Minimala API:er med hjälp av IBindableFromHttpContext<TSelf> gränssnittet. Med det här gränssnittet, som introducerades med C# 11:s statiska abstrakta medlemmar, kan du skapa typer som kan bindas från en HTTP-kontext direkt i routningshanterarparametrarna.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Genom att implementera IBindableFromHttpContext<TSelf> gränssnittet kan du skapa anpassade typer som hanterar sin egen bindningslogik från HttpContext. När en routningshanterare innehåller en parameter av den här typen anropar ramverket automatiskt den statiska BindAsync-metoden för att skapa instansen:
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}";
});
Följande är ett exempel på implementering av en anpassad parameter som binder från ett HTTP-huvud:
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
});
}
}
Du kan också implementera validering i din anpassade bindningslogik:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
Visa eller ladda ned exempelkoden (ladda ned)
Bindningsfel
När bindningen misslyckas loggar ramverket ett felsökningsmeddelande och returnerar olika statuskoder till klienten beroende på felläget.
| Felläge | Parametertyp som kan vara null | Bindningskälla | Statuskod |
|---|---|---|---|
{ParameterType}.TryParse returnerar false |
Ja | rutt/förfrågan/huvud | 400 |
{ParameterType}.BindAsync returnerar null |
Ja | anpassad | 400 |
{ParameterType}.BindAsync kastar |
spelar ingen roll | anpassad | 500 |
| Det gick inte att deserialisera JSON-data | spelar ingen roll | kropp | 400 |
Fel innehållstyp (inte application/json) |
spelar ingen roll | kropp | 415 |
Bindningsprioritet
Reglerna för att fastställa en bindningskälla från en parameter:
- Den explicita attribut som definieras på parametern (From*-attribut) i följande ordning:
- Routvärden:
[FromRoute] - Frågesträng:
[FromQuery] - Rubrik:
[FromHeader] - Brödtext:
[FromBody] - Tjänst:
[FromServices] - Parametervärden:
[AsParameters]
- Routvärden:
- Särskilda typer
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)
- Parametertypen har en giltig statisk
BindAsync-metod. - Parametertypen är en sträng eller har en giltig statisk
TryParse-metod.- Om parameternamnet finns i vägmallen. På
app.Map("/todo/{id}", (int id) => {});äridbunden från rutten. - Bunden till frågesträngen.
- Om parameternamnet finns i vägmallen. På
- Om parametertypen är en tjänst som tillhandahålls av beroendeinmatning använder den tjänsten som källa.
- Parametern kommer från kroppen.
Konfigurera JSON-deserialiseringsalternativ för brödtextbindning
Kroppsbindningskällan använder System.Text.Json för deserialisering. Det är inte möjligt att ändra den här standardinställningen, men JSON-serialiserings- och deserialiseringsalternativ kan konfigureras.
Konfigurera JSON-deserialiseringsalternativ globalt
Alternativ som gäller globalt för en app kan konfigureras genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
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
// }
Eftersom exempelkoden konfigurerar både serialisering och deserialisering kan den läsa NameField och inkludera NameField i utdata-JSON.
Konfigurera JSON-deserialiseringsalternativ för en slutpunkt
ReadFromJsonAsync har överbelastningar som accepterar ett JsonSerializerOptions-objekt. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.
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
// }
Eftersom föregående kod endast tillämpar de anpassade alternativen för deserialisering utesluter utdata-JSON NameField.
Läs begärandetexten
Läs begärandetexten direkt med hjälp av en HttpContext- eller 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();
Föregående kod:
- Åtkomst till begärandetexten genom HttpRequest.BodyReader.
- Kopierar begärandetexten till en lokal fil.
ASP.NET Core