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 9-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.
Belangrijk
Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.
Zie de .NET 9-versie van dit artikel voor de huidige release.
Door Justin Kotalik
Dit artikel legt uit hoe u gegevens kunt lezen uit de aanvraaginhoud en schrijven naar de antwoordinhoud. Code voor deze bewerkingen is mogelijk vereist bij het schrijven van middleware. Buiten het schrijven van middleware is aangepaste code doorgaans niet vereist omdat de bewerkingen worden verwerkt door MVC en Razor Pages.
Er zijn twee abstracties voor de aanvraag- en antwoordteksten: Stream en Pipe. Voor het lezen van aanvragen is HttpRequest.Body een Stream, en HttpRequest.BodyReader is een PipeReader. Voor antwoord schrijven is HttpResponse.Body een Stream, en HttpResponse.BodyWriter is een PipeWriter.
Pijplijnen worden aanbevolen boven streams. Streams kunnen eenvoudiger worden gebruikt voor sommige eenvoudige bewerkingen, maar pijplijnen hebben een prestatievoordeel en zijn in de meeste scenario's gemakkelijker te gebruiken. ASP.NET Core begint pijplijnen te gebruiken in plaats van stromen intern. Voorbeelden zijn:
FormReaderTextReaderTextWriterHttpResponse.WriteAsync
Streams worden niet verwijderd uit het framework. Streams worden nog steeds gebruikt in .NET:
- Veel stroomtypen hebben geen pijpequivalenten, zoals
FileStreamsenResponseCompression. - Het is eenvoudig om compressie toe te voegen aan een stream.
Stream-voorbeelden
Stel dat het doel is om een middleware te maken waarmee de hele aanvraagtekst wordt gelezen als een lijst met tekenreeksen die op nieuwe regels worden gesplitst. Een eenvoudige stream-implementatie kan eruitzien als in het volgende voorbeeld:
Waarschuwing
De volgende code:
- Wordt gebruikt om te demonstreren wat de problemen zijn bij het niet gebruiken van een pipe voor het lezen van de aanvraagbody.
- Is niet bedoeld voor gebruik in productie-apps.
private async Task<List<string>> GetListOfStringsFromStream(Stream requestBody)
{
// Build up the request body in a string builder.
StringBuilder builder = new StringBuilder();
// Rent a shared buffer to write the request body into.
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0, buffer.Length);
if (bytesRemaining == 0)
{
break;
}
// Append the encoded string into the string builder.
var encodedString = Encoding.UTF8.GetString(buffer, 0, bytesRemaining);
builder.Append(encodedString);
}
ArrayPool<byte>.Shared.Return(buffer);
var entireRequestBody = builder.ToString();
// Split on \n in the string.
return new List<string>(entireRequestBody.Split("\n"));
}
Als u codeopmerkingen wilt zien die zijn vertaald naar andere talen dan Engels, laat het ons dan weten in dit GitHub-discussieprobleem.
Deze code werkt, maar er zijn enkele problemen:
- Voordat u aan het
StringBuildervoorbeeld toevoegt, wordt er een andere tekenreeks (encodedString) gemaakt die onmiddellijk wordt weggegooid. Dit proces vindt plaats voor alle bytes in de stream, wat resulteert in een extra geheugentoewijzing ter grootte van de volledige verzoeklichaam. - In het voorbeeld wordt de hele tekenreeks gelezen voordat u op nieuwe regels splitst. Het is efficiƫnter om te controleren op nieuwe regels in de bytematrix.
Hier volgt een voorbeeld waarmee enkele van de voorgaande problemen worden opgelost:
Waarschuwing
De volgende code:
- Wordt gebruikt om de oplossingen voor sommige problemen in de voorgaande code te demonstreren, terwijl niet alle problemen worden opgelost.
- Is niet bedoeld voor gebruik in productie-apps.
private async Task<List<string>> GetListOfStringsFromStreamMoreEfficient(Stream requestBody)
{
StringBuilder builder = new StringBuilder();
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
List<string> results = new List<string>();
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0, buffer.Length);
if (bytesRemaining == 0)
{
results.Add(builder.ToString());
break;
}
// Instead of adding the entire buffer into the StringBuilder
// only add the remainder after the last \n in the array.
var prevIndex = 0;
int index;
while (true)
{
index = Array.IndexOf(buffer, (byte)'\n', prevIndex);
if (index == -1)
{
break;
}
var encodedString = Encoding.UTF8.GetString(buffer, prevIndex, index - prevIndex);
if (builder.Length > 0)
{
// If there was a remainder in the string buffer, include it in the next string.
results.Add(builder.Append(encodedString).ToString());
builder.Clear();
}
else
{
results.Add(encodedString);
}
// Skip past last \n
prevIndex = index + 1;
}
var remainingString = Encoding.UTF8.GetString(buffer, prevIndex, bytesRemaining - prevIndex);
builder.Append(remainingString);
}
ArrayPool<byte>.Shared.Return(buffer);
return results;
}
In dit voorgaande voorbeeld:
- De volledige aanvraagbody wordt niet gebufferd in een
StringBuilder, tenzij er geen nieuwe regeltekens zijn. - Roept de tekenreeks niet
Splitaan.
Er zijn echter nog enkele problemen:
- Als nieuwe regeltekens zeldzaam zijn, wordt een groot deel van de inhoud van het verzoek gebufferd in de tekenreeks.
- De code blijft tekenreeksen maken (
remainingString) en voegt deze toe aan de tekenreeksbuffer, wat resulteert in een extra toewijzing.
Deze problemen kunnen worden opgelost, maar de code wordt geleidelijk gecompliceerder met weinig verbetering. Pijplijnen bieden een manier om deze problemen op te lossen met minimale codecomplexiteit.
Pijpleidingen
In het volgende voorbeeld ziet u hoe het voorgaande stroomscenario kan worden verwerkt met behulp van een PipeReader:
private async Task<List<string>> GetListOfStringFromPipe(PipeReader reader)
{
List<string> results = new List<string>();
while (true)
{
ReadResult readResult = await reader.ReadAsync();
var buffer = readResult.Buffer;
SequencePosition? position = null;
do
{
// Look for a EOL in the buffer
position = buffer.PositionOf((byte)'\n');
if (position != null)
{
var readOnlySequence = buffer.Slice(0, position.Value);
AddStringToList(results, in readOnlySequence);
// Skip the line + the \n character (basically position)
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
}
}
while (position != null);
if (readResult.IsCompleted && buffer.Length > 0)
{
AddStringToList(results, in buffer);
}
reader.AdvanceTo(buffer.Start, buffer.End);
// At this point, buffer will be updated to point one byte after the last
// \n character.
if (readResult.IsCompleted)
{
break;
}
}
return results;
}
private static void AddStringToList(List<string> results, in ReadOnlySequence<byte> readOnlySequence)
{
// Separate method because Span/ReadOnlySpan cannot be used in async methods
ReadOnlySpan<byte> span = readOnlySequence.IsSingleSegment ? readOnlySequence.First.Span : readOnlySequence.ToArray().AsSpan();
results.Add(Encoding.UTF8.GetString(span));
}
In dit voorbeeld worden veel problemen opgelost die de streams-implementaties hadden:
- Er is geen buffer voor tekenreeksen nodig omdat de
PipeReaderde verwerking van bytes verzorgt die niet zijn gebruikt. - Gecodeerde tekenreeksen worden rechtstreeks toegevoegd aan de lijst met geretourneerde tekenreeksen.
- Behalve bij de
ToArrayaanroep en het geheugen dat door de tekenreeks wordt gebruikt, vindt het maken van tekenreeksen plaats zonder geheugenallocatie.
Wanneer u rechtstreeks naar HttpResponse.BodyWriter schrijft, moet u PipeWriter.FlushAsync handmatig aanroepen om ervoor te zorgen dat de gegevens worden doorgezet naar het onderliggende antwoordlichaam. Dit is de reden waarom:
-
HttpResponse.BodyWriteris eenPipeWriterdie gegevens buffert totdat een flush-operatie wordt geactiveerd. - Als u aanroept
FlushAsync, worden de gebufferde gegevens naar de onderliggende hoofdtekst van het antwoord geschreven.
Het is aan de ontwikkelaar om te bepalen wanneer moet worden aangeroepen FlushAsync, evenwichtsfactoren zoals buffergrootte, netwerkschrijfoverhead en of de gegevens in afzonderlijke segmenten moeten worden verzonden. Zie System.IO.Pipelines in .NET voor meer informatie.
Adapters
De Body, BodyReaderen BodyWriter eigenschappen zijn beschikbaar voor HttpRequest en HttpResponse. Wanneer u Body instelt op een andere stroom, past een nieuwe set adapters elk type automatisch aan het andere type aan. Als u HttpRequest.Body instelt op een nieuwe stroom, wordt HttpRequest.BodyReader automatisch ingesteld op een nieuwe PipeReader die HttpRequest.Body omhult.
StartAsync
HttpResponse.StartAsync wordt gebruikt om aan te geven dat headers niet kunnen worden gewijzigd en om callbacks uit te voeren OnStarting . Wanneer u Kestrel als server gebruikt, moet u StartAsync aanroepen voordat u PipeReader gebruikt, wordt gegarandeerd dat het geheugen dat door GetMemory wordt geretourneerd tot Kestrel's interne Pipe behoort in plaats van een externe buffer.