问题
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
回答1:
Eric Gontier's solution works great for Swashbuckle 4 and NSwag 12. If you've upgraded to swashbuckle 5 (currently rc4) 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");
}
回答2:
Found the response of this problem :
In startup add :
services.AddSwaggerGen(options =>
{
options.MapType<FileContentResult>(() => new Schema
{
Type = "file",
});
}
And for your controller :
[HttpPost()]
[SwaggerResponse(200, typeof(FileContentResult))]
[ProducesResponseType(typeof(FileContentResult), 200)]
public async Task<FileResult> MyMethod(Viewmodel vm)
{
A late response but for people who has the same problem ...
回答3:
In the API, Required Nuget packages:
1. Microsoft.AspNetCore.StaticFiles // To determine MimeType
2. NSwag.Annotations // To map the return type of API with Angular Service Generated by NSwag
Search for the pacakges in Nuget and install them.
Then In Startup.cs,
services.AddSwaggerGen(options =>
{
// Swagger Configurations
options.MapType<FileContentResult>(() => new Schema
{
Type = "file"
});
});
Now add a method to get the MimeType of file
private string GetMimeType(string fileName)
{
var provider = new FileExtensionContentTypeProvider();
string contentType;
if (!provider.TryGetContentType(fileName, out contentType))
{
contentType = "application/octet-stream";
}
return contentType;
}
Now Add a method to download file
[SwaggerResponse(200, typeof(FileContentResult))]
[ProducesResponseType(typeof(FileContentResult), 200)]
public FileContentResult DownloadDocument(string fileName)
{
// _environment => IHostingEnvironment Instance
var filepath = Path.Combine($"{this._environment.WebRootPath}\\path-to\\filename}");
var mimeType = this.GetMimeType(filename);
// Checks if file exists
var fileBytes = File.ReadAllBytes(filepath);
return new FileContentResult(fileBytes, mimeType)
{
FileDownloadName = filename
};
}
Now the downloadFile method in angular service generated by NSwag will return Observable. To Consume the service, first install file-saver using npm i file-saver
. Then import it in component import { saveAs } from 'file-saver';
downloadDocument = (filename: string): void => {
this._service.downloadDocument(filename).subscribe((res) => {
saveAs(res.data, 'filename');
});
};
This will download file.
回答4:
The solution of @20B2 is working well, but instead of using
() => new Schema
You should use:
() => new OpenApiSchema
来源:https://stackoverflow.com/questions/43844261/what-is-the-correct-way-to-download-a-file-via-the-nswag-code-generator-angular