diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs index 0616474ff..9eddcc484 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs @@ -2,6 +2,10 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System; using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http.Headers; + using System.Threading; using System.Threading.Tasks; using LearningHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Resource; @@ -9,6 +13,7 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api using LearningHub.Nhs.Models.Resource.Contribute; using LearningHub.Nhs.WebUI.Interfaces; using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -60,6 +65,7 @@ public async Task AcceptSensitiveContentAsync(int resourceVersionI /// File name. /// A representing the result of the asynchronous operation. [HttpGet("DownloadResource")] + [AllowAnonymous] public async Task DownloadResource(string filePath, string fileName) { if (string.IsNullOrEmpty(fileName)) @@ -70,7 +76,15 @@ public async Task DownloadResource(string filePath, string fileNa var file = await this.fileService.DownloadFileAsync(filePath, fileName); if (file != null) { - return this.File(file.Content, file.ContentType, fileName); + // Set response headers. + this.Response.ContentType = file.ContentType; + this.Response.ContentLength = file.ContentLength; + var contentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = fileName }; + this.Response.Headers["Content-Disposition"] = contentDisposition.ToString(); + + // Stream the file in chunks with periodic flushes to keep the connection active. + await this.StreamFileWithKeepAliveAsync(file.Content, this.Response.Body, this.HttpContext.RequestAborted); + return this.Ok(); } else { @@ -105,7 +119,16 @@ public async Task DownloadResourceAndRecordActivity(int resourceV ActivityStatus = ActivityStatusEnum.Completed, }; await this.activityService.CreateResourceActivityAsync(activity); - return this.File(file.Content, file.ContentType, fileName); + + // Set response headers. + this.Response.ContentType = file.ContentType; + this.Response.ContentLength = file.ContentLength; + var contentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = fileName }; + this.Response.Headers["Content-Disposition"] = contentDisposition.ToString(); + + // Stream the file in chunks with periodic flushes to keep the connection active. + await this.StreamFileWithKeepAliveAsync(file.Content, this.Response.Body, this.HttpContext.RequestAborted); + return this.Ok(); } else { @@ -584,5 +607,20 @@ public async Task> GetObsoleteResourceFile(int resourceVersionId, b var result = await this.resourceService.GetObsoleteResourceFile(resourceVersionId, deletedResource); return result; } + + /// + /// Reads from the source stream in chunks and writes to the destination stream, + /// flushing after each chunk to help keep the connection active. + /// + private async Task StreamFileWithKeepAliveAsync(Stream source, Stream destination, CancellationToken cancellationToken) + { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken); + await destination.FlushAsync(cancellationToken); + } + } } } diff --git a/LearningHub.Nhs.WebUI/Services/FileService.cs b/LearningHub.Nhs.WebUI/Services/FileService.cs index 4e02db124..fefd834a7 100644 --- a/LearningHub.Nhs.WebUI/Services/FileService.cs +++ b/LearningHub.Nhs.WebUI/Services/FileService.cs @@ -135,20 +135,37 @@ public async Task DownloadFileAsync(string filePath, stri { var file = directory.GetFileClient(fileName); - if (await file.ExistsAsync()) + var properties = await file.GetPropertiesAsync(); + long fileSize = properties.Value.ContentLength; + + try + { + if (fileSize <= 900 * 1024 * 1024) { - return await file.DownloadAsync(); + // For smaller files, download the entire file as a stream. + var response = await file.DownloadAsync(); + return new FileDownloadResponse + { + Content = response.Value.Content, + ContentType = properties.Value.ContentType, + ContentLength = fileSize, + }; } - } - else if (await sourceDirectory.ExistsAsync()) - { - var file = sourceDirectory.GetFileClient(fileName); - - if (await file.ExistsAsync()) + else { - return await file.DownloadAsync(); + // For large files, open a read stream + return new FileDownloadResponse + { + Content = await file.OpenReadAsync(), + ContentType = properties.Value.ContentType, + ContentLength = fileSize, + }; } } + catch (Exception ex) + { + throw new Exception($"Error downloading file: {ex.Message}"); + } return null; }