Skip to content

Commit 58ba1a1

Browse files
committed
TD-5348 stream files with keep alive.
1 parent 51477d4 commit 58ba1a1

File tree

2 files changed

+65
-11
lines changed

2 files changed

+65
-11
lines changed

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Net.Http.Headers;
8+
using System.Threading;
59
using System.Threading.Tasks;
610
using LearningHub.Nhs.Models.Enums;
711
using LearningHub.Nhs.Models.Resource;
812
using LearningHub.Nhs.Models.Resource.Activity;
913
using LearningHub.Nhs.Models.Resource.Contribute;
1014
using LearningHub.Nhs.WebUI.Interfaces;
1115
using Microsoft.AspNetCore.Authorization;
16+
using Microsoft.AspNetCore.Http;
1217
using Microsoft.AspNetCore.Mvc;
1318
using Microsoft.Extensions.Configuration;
1419

@@ -70,7 +75,15 @@ public async Task<IActionResult> DownloadResource(string filePath, string fileNa
7075
var file = await this.fileService.DownloadFileAsync(filePath, fileName);
7176
if (file != null)
7277
{
73-
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();
7487
}
7588
else
7689
{
@@ -105,7 +118,16 @@ public async Task<IActionResult> DownloadResourceAndRecordActivity(int resourceV
105118
ActivityStatus = ActivityStatusEnum.Completed,
106119
};
107120
await this.activityService.CreateResourceActivityAsync(activity);
108-
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();
109131
}
110132
else
111133
{
@@ -584,5 +606,20 @@ public async Task<List<string>> GetObsoleteResourceFile(int resourceVersionId, b
584606
var result = await this.resourceService.GetObsoleteResourceFile(resourceVersionId, deletedResource);
585607
return result;
586608
}
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+
}
587624
}
588625
}

LearningHub.Nhs.WebUI/Services/FileService.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,37 @@ public async Task<ShareFileDownloadInfo> DownloadFileAsync(string filePath, stri
135135
{
136136
var file = directory.GetFileClient(fileName);
137137

138-
if (await file.ExistsAsync())
138+
var properties = await file.GetPropertiesAsync();
139+
long fileSize = properties.Value.ContentLength;
140+
141+
try
142+
{
143+
if (fileSize <= 900 * 1024 * 1024)
139144
{
140-
return await file.DownloadAsync();
145+
// For smaller files, download the entire file as a stream.
146+
var response = await file.DownloadAsync();
147+
return new FileDownloadResponse
148+
{
149+
Content = response.Value.Content,
150+
ContentType = properties.Value.ContentType,
151+
ContentLength = fileSize,
152+
};
141153
}
142-
}
143-
else if (await sourceDirectory.ExistsAsync())
144-
{
145-
var file = sourceDirectory.GetFileClient(fileName);
146-
147-
if (await file.ExistsAsync())
154+
else
148155
{
149-
return await file.DownloadAsync();
156+
// For large files, open a read stream
157+
return new FileDownloadResponse
158+
{
159+
Content = await file.OpenReadAsync(),
160+
ContentType = properties.Value.ContentType,
161+
ContentLength = fileSize,
162+
};
150163
}
151164
}
165+
catch (Exception ex)
166+
{
167+
throw new Exception($"Error downloading file: {ex.Message}");
168+
}
152169

153170
return null;
154171
}

0 commit comments

Comments
 (0)