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