@@ -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}
0 commit comments