@@ -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
@@ -60,6 +65,7 @@ public async Task<ActionResult> AcceptSensitiveContentAsync(int resourceVersionI
6065 /// <param name="fileName">File name.</param>
6166 /// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
6267 [ HttpGet ( "DownloadResource" ) ]
68+ [ AllowAnonymous ]
6369 public async Task < IActionResult > DownloadResource ( string filePath , string fileName )
6470 {
6571 if ( string . IsNullOrEmpty ( fileName ) )
@@ -70,7 +76,15 @@ public async Task<IActionResult> DownloadResource(string filePath, string fileNa
7076 var file = await this . fileService . DownloadFileAsync ( filePath , fileName ) ;
7177 if ( file != null )
7278 {
73- return this . File ( file . Content , file . ContentType , fileName ) ;
79+ // Set response headers.
80+ this . Response . ContentType = file . ContentType ;
81+ this . Response . ContentLength = file . ContentLength ;
82+ var contentDisposition = new ContentDispositionHeaderValue ( "attachment" ) { FileNameStar = fileName } ;
83+ this . Response . Headers [ "Content-Disposition" ] = contentDisposition . ToString ( ) ;
84+
85+ // Stream the file in chunks with periodic flushes to keep the connection active.
86+ await this . StreamFileWithKeepAliveAsync ( file . Content , this . Response . Body , this . HttpContext . RequestAborted ) ;
87+ return this . Ok ( ) ;
7488 }
7589 else
7690 {
@@ -105,7 +119,16 @@ public async Task<IActionResult> DownloadResourceAndRecordActivity(int resourceV
105119 ActivityStatus = ActivityStatusEnum . Completed ,
106120 } ;
107121 await this . activityService . CreateResourceActivityAsync ( activity ) ;
108- return this . File ( file . Content , file . ContentType , fileName ) ;
122+
123+ // Set response headers.
124+ this . Response . ContentType = file . ContentType ;
125+ this . Response . ContentLength = file . ContentLength ;
126+ var contentDisposition = new ContentDispositionHeaderValue ( "attachment" ) { FileNameStar = fileName } ;
127+ this . Response . Headers [ "Content-Disposition" ] = contentDisposition . ToString ( ) ;
128+
129+ // Stream the file in chunks with periodic flushes to keep the connection active.
130+ await this . StreamFileWithKeepAliveAsync ( file . Content , this . Response . Body , this . HttpContext . RequestAborted ) ;
131+ return this . Ok ( ) ;
109132 }
110133 else
111134 {
@@ -584,5 +607,20 @@ public async Task<List<string>> GetObsoleteResourceFile(int resourceVersionId, b
584607 var result = await this . resourceService . GetObsoleteResourceFile ( resourceVersionId , deletedResource ) ;
585608 return result ;
586609 }
610+
611+ /// <summary>
612+ /// Reads from the source stream in chunks and writes to the destination stream,
613+ /// flushing after each chunk to help keep the connection active.
614+ /// </summary>
615+ private async Task StreamFileWithKeepAliveAsync ( Stream source , Stream destination , CancellationToken cancellationToken )
616+ {
617+ byte [ ] buffer = new byte [ 8192 ] ;
618+ int bytesRead ;
619+ while ( ( bytesRead = await source . ReadAsync ( buffer , 0 , buffer . Length , cancellationToken ) ) > 0 )
620+ {
621+ await destination . WriteAsync ( buffer , 0 , bytesRead , cancellationToken ) ;
622+ await destination . FlushAsync ( cancellationToken ) ;
623+ }
624+ }
587625 }
588626}
0 commit comments