Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActionResult<FileResult> doesn't work as expected

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?

like image 532
Mea Reindel Avatar asked Sep 24 '18 07:09

Mea Reindel


1 Answers

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).

like image 63
Kirk Larkin Avatar answered Sep 21 '22 06:09

Kirk Larkin