I have a simple File Upload / Download app with the client in Blazor and the Server as an ASP.NET Core 8 Web API. I set the Content-Disposition
in the API with this code:
[HttpGet("{fileName}")]
public async Task<IActionResult> Get(string fileName)
{
var memoryStream = new MemoryStream();
try
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ApplicationException("FileName is required as a path parameter.");
}
string fullPathName = Path.Combine(_settings.UploadRootFolder!, fileName);
if (!System.IO.File.Exists(fullPathName))
{
throw new ApplicationException($"{fileName} does not exist in {_settings.UploadRootFolder}.");
}
var fi = new FileInfo(fullPathName);
var contentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
//ModificationDate = fi.LastAccessTimeUtc,
FileName = fileName,
FileNameStar = fileName,
Size = fi.Length,
//CreationDate = fi.CreationTimeUtc,
//DispositionType = "attachment",
//ReadDate = DateTime.UtcNow,
};
var mem = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64;
var memKB = mem / 1024;
Response.Headers.ContentDisposition = contentDisposition.ToString();
_logger.LogInformation($"Attempting to download {fileName} from {_settings.UploadRootFolder} using {memKB.ToString("n0")} KB memory.");
using (var fileStream = new FileStream(fullPathName, FileMode.Open, FileAccess.Read))
{
await fileStream.CopyToAsync(memoryStream, 64 * 1024);
memoryStream.Position = 0;
return new FileStreamResult(memoryStream, MimeTypes.GetMimeType(fileName));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception trying to download a file");
return BadRequest();
}
}
In my client (Blazor), I invoke the download with this code:
private async Task btnDownload_Clicked(string fileName)
{
if (!string.IsNullOrWhiteSpace(fileName))
{
try
{
var response = await _apiClient.GetAsync($"/api/FileDownload/{fileName}");
response.EnsureSuccessStatusCode();
//
// Even though the API successfully set the ContentDisposition header
// (verified using debugger) it is null here on the client.
//
// var serverFileName = response.Content.Headers.ContentDisposition.FileName;
var serverFileName = fileName;
using (var stremRef = new DotNetStreamReference(stream: response.Content.ReadAsStream()))
{
await JS.InvokeVoidAsync("downloadFileFromStream", serverFileName, stremRef);
}
}
catch (Exception ex)
{
JS.InvokeVoidAsync("alert", ex.Message);
throw;
}
}
}
I get a NullReferenceException
because the fName
is NULL because the Headers.ContentDisposition
is NULL.
I should be able to set serverFileName
to the name found in Content-Disposition
, but it is always NULL.
When I comment out the line to get the filename from the header and just use the argument I sent in the path, the download is successful, however, I need to be using Content-Disposition
.
Any help is appreciated, my API is running on https://localhost:7283
and my client is running on https://localhost:7203
so they are NOT a Blazor app downloaded from the Client/Server model that VS recommends as a template (separate Client and Server sites seems more real-world).
Thanks for any assistance!
I found the answer - in the API, you need to add the following:
Response.Headers.AccessControlExposeHeaders = HeaderNames.ContentDisposition;
This makes the download method look like this:
[HttpGet("{fileName}")]
public async Task<IActionResult> Get(string fileName)
{
var memoryStream = new MemoryStream();
try
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ApplicationException("FileName is required as a path parameter.");
}
string fullPathName = Path.Combine(_settings.UploadRootFolder!, fileName);
if (!System.IO.File.Exists(fullPathName))
{
throw new ApplicationException($"{fileName} does not exist in {_settings.UploadRootFolder}.");
}
var fi = new FileInfo(fullPathName);
var contentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
ModificationDate = fi.LastAccessTimeUtc,
FileName = fileName,
FileNameStar = fileName,
Size = fi.Length,
CreationDate = fi.CreationTimeUtc,
DispositionType = "attachment",
ReadDate = DateTime.UtcNow,
};
var mem = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64;
var memKB = mem / 1024;
Response.Headers.ContentDisposition = contentDisposition.ToString();
Response.Headers.AccessControlExposeHeaders = HeaderNames.ContentDisposition;
_logger.LogInformation($"Attempting to download {fileName} from {_settings.UploadRootFolder} using {memKB.ToString("n0")} KB memory.");
using (var fileStream = new FileStream(fullPathName, FileMode.Open, FileAccess.Read))
{
await fileStream.CopyToAsync(memoryStream, 64 * 1024);
memoryStream.Position = 0;
return new FileStreamResult(memoryStream, MimeTypes.GetMimeType(fileName));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception trying to download a file");
return BadRequest();
}
}