簡體   English   中英

附加到 C# .NET 核心中的響應正文

[英]Appending to a Response Body in C# .NET Core

我正在做一個項目,我在后端填充一些 pdf,然后將這些 pdf 轉換為 byte[] 列表,該列表合並到一個非常大的數組中,最后通過響應正文作為 Memory 發回Stream。 我的問題是這是大量數據,在獲取要合並的字節 arrays 列表的過程中,我使用了很多 memory。 我想知道是否不是將最終合並的 byte[] 轉換為 Memory Stream 並將其添加到響應正文中; 我可以創建幾個 Memory Stream 對象,我是 append 到 Response.Body 的對象嗎? 或者,我想知道是否有一種方法可以使用一個 Memory Stream 並繼續添加它作為為每個 Z437175BA4191210EE004E1D937494D0 創建每個新字節 [] ?

編輯:這可能有點啰嗦,但我對我原來的帖子太含糊了。 在我想做的事情的核心,我有幾個 pdf 文檔,它們每個都有幾頁長。 它們中的每一個都在下面的代碼中表示為 filesToMerge 列表中的 byte[] 項之一。 理想情況下,我想通過這些 go 將它們一一轉換為 memory stream 並在循環中一個接一個地發送給客戶端。 但是,當我嘗試執行此操作時,我收到響應正文已發送的錯誤。 有沒有辦法 append 對響應主體進行一些處理,以便每次通過循環更新它?

    [HttpGet("template/{formId}/fillforms")]
    public void FillForms(string formId/*, [FromBody] IList<IDictionary<string, string>> fieldDictionaries*/)
    {
        List<byte[]> filesToMerge = new List<byte[]>();

        // For testing
        var mockData = new MockData();
        IList<IDictionary<string, string>> fieldDictionaries = mockData.GetMock1095Dictionaries();


        foreach(IDictionary<string, string> dictionary in fieldDictionaries)
        {
            var populatedForm = this.dataRepo.PopulateForm(formId, dictionary);
            // write to rb
            filesToMerge.Add(populatedForm);
        }

        byte[] mergedFilesAsByteArray = this.dataRepo.GetMergedByteArray(filesToMerge);

        this.SendResponse(formId + "_filled.pdf", new MemoryStream(mergedFilesAsByteArray));
    }

    private void SendResponse(string formName, MemoryStream ms, IDictionary<string, string> fieldData = null)
    {
        Response.Clear();
        Response.ContentType = "application/pdf";
        Response.Headers.Add("content-disposition", $"attachment;filename={formName}.pdf");
        ms.WriteTo(Response.Body);
    }

Memory 流實際上只是字節 arrays 上面有一堆很好的方法。 所以切換到字節 arrays 不會有太大幫助。 A problem that a log of people run into when dealing with byte arrays and memory streams is not releasing the memory when you are done with the data since they occupy the memory of the machine you are running on so you can easily run out of memory. 因此,您應該以“ using statements ”為例,在不再需要數據時立即處理數據。 Memory 流有一個稱為 Dispose 的方法,它將釋放 stream 使用的所有資源

如果您想盡快從應用程序中傳輸數據,最好的方法是將 stream 切成更小的部分,然后在目的地以正確的順序重新組裝它們。 你可以將它們削減到 1mb 或 126kb,無論你想要什么。 當您將數據發送到目的地時,您還需要傳遞這部分的訂單號,因為這種方法允許您並行發布數據並且不保證訂單。

將 stream 拆分為多個流

private static List<MemoryStream> CreateChunks(Stream stream)
{
    byte[] buffer = new byte[4000000]; //set the size of your buffer (chunk)
    var returnStreams = new List<MemoryStream>();
    using (MemoryStream ms = new MemoryStream())
    {
        while (true) //loop to the end of the file
        {
            var returnStream = new MemoryStream();
            int read = stream.Read(buffer, 0, buffer.Length); //read each chunk
            returnStream.Write(buffer, 0, read); //write chunk to [wherever];
            if (read <= 0)
            { //check for end of file
                return returnStreams;
            }
            else
            {
                returnStream.Position = 0;
                returnStreams.Add(returnStream);
            }
        }
    }
}

然后,我遍歷創建的流以創建要發布到服務的任務,每個任務都會發布到服務器。 我會等待所有任務完成,然后再次調用我的服務器告訴它我已經完成上傳,它可以將所有數據以正確的順序組合成一個。 我的服務具有上傳 session 的概念,以跟蹤所有部件以及它們將 go 進入的順序。它還會在每個部件進入時將它們保存到數據庫中; 在我的情況下 Azure Blob 存儲。

目前尚不清楚為什么將多個MemoryStream的內容復制到Response.Body時會出錯。 您當然應該能夠做到這一點,盡管您需要確保在開始寫入數據后不要嘗試更改響應標頭或狀態代碼(也不要在開始寫入后嘗試調用Response.Clear()數據)。

這是一個啟動響應然后寫入數據的簡單示例:

[ApiController]
[Route("[controller]")]
public class RandomDataController : ControllerBase {
    private readonly ILogger<RandomDataController> logger;
    private const String CharacterData = "abcdefghijklmnopqrstuvwxyz0123456789 ";

    public RandomDataController(ILogger<RandomDataController> logger) {
        this.logger = logger;
    }

    [HttpGet]
    public async Task Get(CancellationToken cancellationToken) {
        this.Response.ContentType = "text/plain";
        this.Response.ContentLength = 1000;

        await this.Response.StartAsync(cancellationToken);
        logger.LogInformation("Response Started");

        var rand = new Random();
        for (var i = 0; i < 1000; i++) {
            // You should be able to copy the contents of a MemoryStream or other buffer here instead of sending random data like this does.
            await this.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(CharacterData[rand.Next(0, CharacterData.Length)].ToString()), cancellationToken);
            Thread.Sleep(50); // This is just to demonstrate that data is being sent to the client as it is written
            cancellationToken.ThrowIfCancellationRequested();

            if (i % 100 == 0 && i > 0) {
                logger.LogInformation("Response In Flight {PercentComplete}", (Double)i / 1000);
            }
        }

        logger.LogInformation("Response Complete");
    }
}

您可以使用 netcat 驗證這是否將數據流回客戶端:

% nc -nc 127.0.0.1 5000
GET /randomdata HTTP/1.1
Host: localhost:5000
Connection: Close

(在Connection: Close之后輸入一個額外的空白行以開始請求)。 當數據寫入服務器上的Response.Body時,您應該會看到數據出現在 netcat 中。

需要注意的一點是,這種方法涉及預先計算要發送的數據的長度。 如果您無法預先計算響應的大小,或者不願意,您可以查看Chunked Transfer Encoding ,如果您開始將數據寫入 Response.Body 而不指定Content-Length ,ASP.Net 應該自動使用它.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM