NSwag: NSwag generate wrong code for FileContentResult type.

Hi Team,

I have Web.API core action method which returns FileContentResult, code example:

return new FileContentResult(fileViewModel.Content, fileViewModel.FileMetadataViewModel.MimeType)
{
      FileDownloadName = fileViewModel.FileMetadataViewModel.FileName
};

It generates code like:

var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
var result_ = default(FileContentResult);
try
{
   result_ = Newtonsoft.Json.JsonConvert.DeserializeObject<FileContentResult>(responseData_);
   return result_;
}
catch (System.Exception exception)
{
    throw new BusinessApiException("Could not deserialize the response body.", status_, result_.ToString(), headers_, exception);
}

Which cause SerializationException.

My manual fix looks like:

var result_ = new FileContentResult();
 
// Get file's byte array
byte[] responseData_ = await response_.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
 
// Set FileContentResult properties manually
result_.ContentType = headers_["Content-Type"].FirstOrDefault();
result_.FileDownloadName = headers_["Content-Disposition"].FirstOrDefault();
result_.FileContents = responseData_;
                       
try
{
    return result_;
}
catch (System.Exception exception)
{
    throw new BusinessApiException("Could not deserialize the response body.", status_, result_.ToString(), headers_, exception);
}

Could you please help me or/and suggest how to fix it. (Probably, I’m using wrong type or doing something wrong etc)

Thanks a lot in advance !!

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 5
  • Comments: 49 (24 by maintainers)

Most upvoted comments

Ok, just tried this with the latest master version. This operation

    [HttpPost]
    [Produces(@"application/json", @"application/pdf")]
    [ProducesResponseType(typeof(FileResult), 200)]
    [ProducesResponseType(typeof(void), 400)]
    [ProducesResponseType(typeof(void), 401)]
    [Route(@"api/[controller]/Report")]
    public async Task<IActionResult> GenerateReport()
    {
        Stream stream = new MemoryStream(new byte[] { 1, 2, 3 });
        return this.File(stream, @"application/pdf", @"report.pdf");
    }

gives me this swagger:

"/api/Values/api/Values/Report": {
  "post": {
    "tags": [
      "Values"
    ],
    "operationId": "Values_GenerateReport",
    "parameters": [],
    "responses": {
      "200": {
        "description": "",
        "schema": {
          "type": "file"
        },
        "x-nullable": true
      },
      "400": {
        "description": ""
      },
      "401": {
        "description": ""
      }
    }
  }
}

and this client code:

    public async System.Threading.Tasks.Task<FileResponse> GenerateReportAsync(System.Threading.CancellationToken cancellationToken)
    {
        ...

                    var status_ = ((int)response_.StatusCode).ToString();
                    if (status_ == "200" || status_ == "206") 
                    {
                        var responseStream_ = await response_.Content.ReadAsStreamAsync().ConfigureAwait(false);
                        var fileResponse_ = new FileResponse(status_, headers_, responseStream_, client_, response_); 
                        client_ = null; response_ = null; // response and client are disposed by FileResponse
                        return fileResponse_;
                    }
                    else
                    if (status_ == "400") 
                    {
                        var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); 
                        throw new SwaggerException("A server side error occurred.", status_, responseData_, headers_, null);
                    }
                    else

so it looks good to me…

Ok,

So, If I set [ProducesResponseType(typeof(FileResult), 200)] in the service, the NSwag returns a FileResult instead of a FileResponse which contains a Stream property.

If I set a [ProducesResponseType(typeof(byte[]), 200)], then NSwag generates the FileResponse class.

instead of typeof(byte[]) use one of these types to specify a file response:

image

Not sure if I fully grasp the problems OP is experiencing, but here’s my take on it. [SwaggerResponse(typeof(byte[]))] is not correct, since it generates string for schema.type. Instead, simply using IHttpActionResult or any other type listed in IsFileResponse makes it work.

[SwaggerResponse(HttpStatusCode.OK, typeof(IHttpActionResult))]
public IHttpActionResult GetData(string id)
{
    var deliveryData = Data.GetDeliveryData(id);

    var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StreamContent(new MemoryStream(deliveryData.Data))
    };
    responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
    responseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = deliveryData.Name
    };

    return ResponseMessage(responseMessage);
}

I don’t think you can simply add byte[] to IsFileResponse either, because (I assume) it’s technically possible to have a a byte array represented by JSON? Anyway, the problem really is that Web API doesn’t come with a built-in type for file responses that implements IHttpActionResult, you need to roll your own. If it did, everyone could use the same type and it could be added to IsFileResponse.

Thanks for the answer.

Method with signature:

public async Task<IActionResult> Get(Guid fileId)
{
            if (fileId == Guid.Empty)
            {
                return new BadRequestResult();
            }

            var fileViewModel = await this.fileService.GetFile(fileId);

            if (fileViewModel == null)
            {
                return new NotFoundObjectResult($"File {fileId} doesn't exist.");
            }

            return new FileContentResult(fileViewModel.Content, fileViewModel.FileMetadataViewModel.MimeType)
            {
                FileDownloadName = fileViewModel.FileMetadataViewModel.FileName
            };
}
  • Content field - byte[]
  • FileName and MimeType - string