What is the correct way to download a file via the NSwag Code Generator (angular 2 typescript)

.net-core, nswag, swagger-ui, typescript

Solution

Eric Gontier's solution works great for Swashbuckle 4 and NSwag 12. If you've upgraded to swashbuckle 5 and thus OpenApi 3 and NSwag 13, then the solution is different. Instead you'll need a custom operation filter, and an reusable attribute to indicate the content-type result:

Custom attribute

/// <summary>
/// Indicates swashbuckle should expose the result of the method as a file in open api (see https://swagger.io/docs/specification/describing-responses/)
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class FileResultContentTypeAttribute : Attribute
{
    public FileResultContentTypeAttribute(string contentType)
    {
        ContentType = contentType;
    }

    /// <summary>
    /// Content type of the file e.g. image/png
    /// </summary>
    public string ContentType { get; }
}

Operation filter

public class FileResultContentTypeOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var requestAttribute = context.MethodInfo.GetCustomAttributes(typeof(FileResultContentTypeAttribute), false)
            .Cast<FileResultContentTypeAttribute>()
            .FirstOrDefault();

        if (requestAttribute == null) return;

        operation.Responses.Clear();
        operation.Responses.Add("200", new OpenApiResponse
        {
            Content = new Dictionary<string, OpenApiMediaType>
            {
                {
                    requestAttribute.ContentType, new OpenApiMediaType
                    {
                        Schema = new OpenApiSchema
                        {
                            Type = "string",
                            Format = "binary"
                        }
                    }
                }
            }
        });
    }
}

Startup.cs

services.AddSwaggerGen(options =>
{
    ...
    options.OperationFilter<FileResultContentTypeOperationFilter>();
}

Sample Controller

Then annotate your controller with the attribute.

[HttpPost]
[Route("{fileName}.csv")]
[FileResultContentType("text/csv")]
public async Task<ActionResult> Generate(string fileName, [FromBody]MyDto myDto)
{
    var fileMemoryStream = GetCsvAsBytes(myDto);
    return File(fileMemoryStream,
        "text/csv", fileName + ".csv");
}

Problem

I try to download a file via an angular 2 typescript client. The link generated in Swagger UI works fine, but the generated typescript client does not. The controller looks like this: ``` [HttpGet("export")] [SwaggerResponse((int) HttpStatusCode.OK, Type = typeof(FileContentResult))] [ProducesResponseType(typeof(FileResult), (int) HttpStatusCode.OK)] [Produces("text/csv")] public virtual FileResult Export(int Id, string fileType, CsvFormat format, bool includeHeader) { . . . FileStreamResult file = new FileStreamResult(s, "text/csv"); file.FileDownloadName = ts.Name + "." + fileType; return file; } ``` Swagger UI: The generated typescript client looks like this. As you can see the responseText is set but never returned. What am I missing? ``` protected processRestTimeSeriesExportGet(response: Response): Observable<void> { const status = response.status; if (status === 200) { const responseText = response.text(); return Observable.of<void>(<any>null); } else if (status !== 200 && status !== 204) { const responseText = response.text(); return throwException("An unexpected server error occurred.", status, responseText); } return Observable.of<void>(<any>null); } ``` Best regards

Original source