Skip to content

Commit e3887aa

Browse files
committed
TD-5348 stream files with keep alive.
1 parent 1db59a2 commit e3887aa

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.IO;
56
using System.Linq;
7+
using System.Net.Http.Headers;
8+
using System.Threading;
69
using System.Threading.Tasks;
710
using LearningHub.Nhs.Models.Enums;
811
using LearningHub.Nhs.Models.Resource;
912
using LearningHub.Nhs.Models.Resource.Activity;
1013
using LearningHub.Nhs.Models.Resource.Contribute;
1114
using LearningHub.Nhs.WebUI.Interfaces;
1215
using Microsoft.AspNetCore.Authorization;
16+
using Microsoft.AspNetCore.Http;
1317
using Microsoft.AspNetCore.Mvc;
1418
using Microsoft.Extensions.Configuration;
1519

@@ -71,7 +75,15 @@ public async Task<IActionResult> DownloadResource(string filePath, string fileNa
7175
var file = await this.fileService.DownloadFileAsync(filePath, fileName);
7276
if (file != null)
7377
{
74-
return this.File(file.Content, file.ContentType, fileName);
78+
// Set response headers.
79+
this.Response.ContentType = file.ContentType;
80+
this.Response.ContentLength = file.ContentLength;
81+
var contentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = fileName };
82+
this.Response.Headers["Content-Disposition"] = contentDisposition.ToString();
83+
84+
// Stream the file in chunks with periodic flushes to keep the connection active.
85+
await this.StreamFileWithKeepAliveAsync(file.Content, this.Response.Body, this.HttpContext.RequestAborted);
86+
return this.Ok();
7587
}
7688
else
7789
{
@@ -106,7 +118,16 @@ public async Task<IActionResult> DownloadResourceAndRecordActivity(int resourceV
106118
ActivityStatus = ActivityStatusEnum.Completed,
107119
};
108120
await this.activityService.CreateResourceActivityAsync(activity);
109-
return this.File(file.Content, file.ContentType, fileName);
121+
122+
// Set response headers.
123+
this.Response.ContentType = file.ContentType;
124+
this.Response.ContentLength = file.ContentLength;
125+
var contentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = fileName };
126+
this.Response.Headers["Content-Disposition"] = contentDisposition.ToString();
127+
128+
// Stream the file in chunks with periodic flushes to keep the connection active.
129+
await this.StreamFileWithKeepAliveAsync(file.Content, this.Response.Body, this.HttpContext.RequestAborted);
130+
return this.Ok();
110131
}
111132
else
112133
{
@@ -585,5 +606,20 @@ public async Task<List<string>> GetObsoleteResourceFile(int resourceVersionId, b
585606
var result = await this.resourceService.GetObsoleteResourceFile(resourceVersionId, deletedResource);
586607
return result;
587608
}
609+
610+
/// <summary>
611+
/// Reads from the source stream in chunks and writes to the destination stream,
612+
/// flushing after each chunk to help keep the connection active.
613+
/// </summary>
614+
private async Task StreamFileWithKeepAliveAsync(Stream source, Stream destination, CancellationToken cancellationToken)
615+
{
616+
byte[] buffer = new byte[8192];
617+
int bytesRead;
618+
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
619+
{
620+
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);
621+
await destination.FlushAsync(cancellationToken);
622+
}
623+
}
588624
}
589625
}

LearningHub.Nhs.WebUI/Services/FileService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public async Task<FileDownloadResponse> DownloadFileAsync(string filePath, strin
147147
{
148148
if (fileSize <= 900 * 1024 * 1024)
149149
{
150-
// Directly download the entire file as a stream
150+
// For smaller files, download the entire file as a stream.
151151
var response = await file.DownloadAsync();
152152
return new FileDownloadResponse
153153
{
@@ -158,6 +158,7 @@ public async Task<FileDownloadResponse> DownloadFileAsync(string filePath, strin
158158
}
159159
else
160160
{
161+
// For large files, open a read stream
161162
return new FileDownloadResponse
162163
{
163164
Content = await file.OpenReadAsync(),

0 commit comments

Comments
 (0)