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 .
Den här artikeln beskriver hur du läser från begärandetexten och skriver till svarstexten. Kod för dessa åtgärder kan krävas när du skriver mellanprogram. Förutom att skriva mellanprogram krävs i allmänhet inte anpassad kod eftersom åtgärderna hanteras av MVC och Razor Pages.
Det finns två abstraktioner för begärande- och svarstexterna: Stream och Pipe. För begärandeläsning HttpRequest.Body är en Stream, och HttpRequest.BodyReader är en PipeReader. För att skriva svar, HttpResponse.Body är en Stream, och HttpResponse.BodyWriter är en PipeWriter.
Pipelines rekommenderas över strömmar. Strömmar kan vara enklare att använda för vissa enkla åtgärder, men pipelines har en prestandafördel och är enklare att använda i de flesta scenarier. ASP.NET Core börjar använda pipelines i stället för strömmar internt. Exempel är:
- FormReader
- TextReader
- TextWriter
- HttpResponse.WriteAsync
Strömmar tas inte bort från ramverket. Strömmar fortsätter att användas i .NET:
- Många strömtyper har inte pipe-motsvarigheter, till exempel FileStreamsochResponseCompression.
- Det är enkelt att lägga till komprimering i en ström.
Stream-exempel
Anta att målet är att skapa ett mellanprogram som läser hela begärandetexten som en lista med strängar och delar upp på nya rader. En enkel strömimplementering kan se ut som i följande exempel:
Varning
Följande kod:
- Används för att demonstrera problemen med att inte använda ett pipe för att läsa begärandetexten.
- Är inte avsedd att användas i produktionsappar.
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"));
}
Om du vill se kodkommentar översatta till andra språk än engelska kan du meddela oss i det här GitHub-diskussionsproblemet.
Den här koden fungerar, men det finns några problem:
- Innan du lägger till StringBuilderi skapar exemplet en annan sträng (encodedString) som kastas bort omedelbart. Den här processen sker för alla byte i dataströmmen, så resultatet är extra minnesallokering som är lika stor som hela begärandetexten.
- Exemplet läser hela strängen innan den delas upp på nya rader. Det är mer effektivt att söka efter nya rader i bytematrisen.
Här är ett exempel som åtgärdar några av de föregående problemen:
Varning
Följande kod:
- Används för att demonstrera lösningarna på vissa problem i föregående kod utan att lösa alla problem.
- Är inte avsedd att användas i produktionsappar.
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;
}
Det här föregående exemplet:
- Buffrar inte hela begärandetexten i en StringBuilderom det inte finns några tecken för ny rad.
- Anropar Splitinte strängen.
Det finns dock fortfarande några problem:
- Om radbrytningstecknen är glesa buffras en stor del av begärandetexten i strängen.
- Koden fortsätter att skapa strängar (remainingString) och lägger till dem i strängbufferten, vilket resulterar i en extra allokering.
Dessa problem kan åtgärdas, men koden blir allt mer komplicerad med liten förbättring. Pipelines är ett sätt att lösa dessa problem med minimal kodkomplexitet.
Rörledningar
I följande exempel visas hur det föregående strömscenariot kan hanteras med hjälp av en 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));
}
I det här exemplet åtgärdas många problem som strömimplementeringarna hade:
- Det finns inget behov av en strängbuffert eftersom den PipeReaderhanterar byte som inte har använts.
- Kodade strängar läggs till direkt i listan över returnerade strängar.
- Förutom anropet ToArrayoch det minne som används av strängen är det fritt att skapa strängar.
När du skriver direkt till HttpResponse.BodyWriteranropar du PipeWriter.FlushAsync manuellt för att säkerställa att data rensas till den underliggande svarstexten. Här är varför:
- 
              HttpResponse.BodyWriterär enPipeWritersom buffrar data tills en tömningsåtgärd utlöses.
- Anrop FlushAsyncskriver buffrade data till den underliggande svarstexten.
Det är upp till utvecklaren att bestämma när det ska anropas FlushAsync, balansera faktorer som buffertstorlek, nätverksskrivningskostnader och om data ska skickas i diskreta bitar. Mer information finns i System.IO.Pipelines i .NET.
Adaptrar
Egenskaperna Body, BodyReader, och BodyWriter är tillgängliga för HttpRequest och HttpResponse. När du ställer in Body en annan ström anpassar en ny uppsättning adaptrar automatiskt varje typ till den andra. Om du ställer in HttpRequest.Body på en ny ström, HttpRequest.BodyReader ställs automatiskt in på en ny PipeReader som omsluts HttpRequest.Body.
StartAsync
              HttpResponse.StartAsync används för att indikera att rubriker inte kan ändras och för att köra OnStarting återanrop. När du använder Kestrel som en server, garanterar anrop StartAsync innan du PipeReader använder att minnet som returneras av GetMemoryKestrel tillhör s interna Pipe snarare än en extern buffert.
Ytterligare resurser
ASP.NET Core