I'm using ASP.NET Core 2.1 ActionResult<T> to return a file from controller:
[HttpGet]
public ActionResult<FileResult> Download()
{
var someBinaryFile = "somebinaryfilepath";
return File(new FileStream(firstExe, FileMode.Open, FileAccess.Read, FileShare.Read), System.Net.Mime.MediaTypeNames.Application.Octet, true);
}
But it returns incomplete json instead of starting file download:
{"fileStream":{"handle":{"value":2676},"canRead":true,"canWrite":false,"safeFileHandle":
{"isInvalid":false,"isClosed":false},"name":"somebinaryfilepath","isAsync":false,"length":952320,"position":0,"canSeek":true,"canTimeout":false
Chrome devtools indicates request status as "(failed)" with tooltip "net::ERR_SPDY_PROTOCOL_ERROR"
If I change the code so it returns a FileContentResult then status turns to "200 ok" but still json is written instead of file download:
{"fileContents": "somebinaryfilecontent","contentType":"application/octet-stream","fileDownloadName":"","lastModified":null,"entityTag":null,"enableRangeProcessing":true}
If I change method signature to
public FileResult Download()
Or to
public IActionResult Download()
Then file download starts with both FileResult implementations.
How could I use ActionResult<T> to download file? Am I missing something or is it really some kind of bug?
When using ActionResult<T>
, the T
in this case is the specific type you want to return. By default, when returned from an action either directly or using ActionResult<T>
, this type is serialised as JSON, as you've demonstrated in your question.
For your example, where you're not returning a specific type at all, you'll want to use IActionResult
(as you've seen, according to your question), like this:
[HttpGet]
public IActionResult Download()
{
...
}
You could also use ActionResult
, FileResult
or FileStreamResult
, but IActionResult
is preferred.
Here's an explanation of why I think this is happening in the first place.
ActionResult<T>
contains two implicit operators:
public static implicit operator ActionResult<TValue>(TValue value)
{
return new ActionResult<TValue>(value);
}
public static implicit operator ActionResult<TValue>(ActionResult result)
{
return new ActionResult<TValue>(result);
}
In your example, when returning a FileStreamResult
, both of these implicit operators could be used. One operates on any type (TValue
) and the other operates on an ActionResult
. I expect that the C# compiler is choosing the first version, because TValue
(FileResult
) is a better match for FileStreamResult
(your actual return value).
This is covered in the C# spec under section "11.5.4 User-defined implicit conversions". Specifically, I think it's this description that applies (I'm no expert here):
Find the most-specific source type, SX, of the operators in U:
- If S exists and any of the operators in U convert from S, then SX is S.
- Otherwise, SX is the most-encompassed type in the combined set of source types of the operators in U. If exactly one most-encompassed type cannot be found, then the conversion is ambiguous and a compile-time error occurs.
In this case, FileResult
is the most specific source-type (it's more specific than ActionResult
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With