在我的ASP.NET Web API应用程序中,我有一个这样的控制器:
[RoutePrefix("api/ratings")]
public class RateCostumerController : ApiController
{
[AllowAnonymous]
[Route("Report/GetReport")]
[HttpGet]
public HttpResponseMessage ExportReport([FromUri] string costumer)
{
var rd = new ReportDocument();
/*No relevant code here*/
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(ms.ToArray())
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "Reporte.pdf"
};
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
}
因此,当我使用costumer参数发出一个简单的GET请求时,我会在浏览器中获得一个pdf文件作为响应。一些响应标题:
内容 - 处置:附件;文件名= Reporte.pdf 内容长度:22331 Content-Type:application / octet-stream
设置了swagger后,生成了json metadafile并在我的Xamarin PCL项目中用它生成了C#代码我尝试使用该服务。 但它失败了,因为生成的代码试图反序列化json,但不是json结果!
这是生成的代码失败的一部分:
[...]
var _result = new Microsoft.Rest.HttpOperationResponse<object>();
_result.Request = _httpRequest;
_result.Response = _httpResponse;
// Deserialize Response
if ((int)_statusCode == 200)
{
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
_result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject<object>(_responseContent, this.Client.DeserializationSettings);
}
catch (Newtonsoft.Json.JsonException ex)
{
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw new Microsoft.Rest.SerializationException("Unable to deserialize the response.", _responseContent, ex);
}
}
if (_shouldTrace)
{
Microsoft.Rest.ServiceClientTracing.Exit(_invocationId, _result);
}
return _result;
[...]
当我调试时,我发现文件的内容在正文中,因此反序列化会弄乱它。由于不建议编辑此生成的类文件,我需要在API中更改哪些内容以正确生成application / octet-stream内容响应的代码?
答案 0 :(得分:4)
创建一个返回文件的自定义过滤器:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class SwaggerFileResponseAttribute : SwaggerResponseAttribute
{
public SwaggerFileResponseAttribute(HttpStatusCode statusCode) : base(statusCode)
{
}
public SwaggerFileResponseAttribute(HttpStatusCode statusCode, string description = null, Type type = null) : base(statusCode, description, type)
{
}
public SwaggerFileResponseAttribute(int statusCode) : base(statusCode)
{
}
public SwaggerFileResponseAttribute(int statusCode, string description = null, Type type = null) : base(statusCode, description, type)
{
}
}
还有这个自定义的ResponseTypeFilter类:
public sealed class UpdateFileResponseTypeFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (apiDescription.GetControllerAndActionAttributes<SwaggerResponseRemoveDefaultsAttribute>().Any())
{
operation.responses.Clear();
}
var responseAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerFileResponseAttribute>()
.OrderBy(attr => attr.StatusCode);
foreach (var attr in responseAttributes)
{
var statusCode = attr.StatusCode.ToString();
Schema responseSchema = new Schema { format = "byte", type = "file" };
operation.produces.Clear();
operation.produces.Add("application/octet-stream");
operation.responses[statusCode] = new Response
{
description = attr.Description ?? InferDescriptionFrom(statusCode),
schema = responseSchema
};
}
}
private string InferDescriptionFrom(string statusCode)
{
HttpStatusCode enumValue;
if (Enum.TryParse(statusCode, true, out enumValue))
{
return enumValue.ToString();
}
return null;
}
}
然后在SwaggerConfig文件中注册:
c.OperationFilter<UpdateFileResponseTypeFilter>();
要使用此过滤器,只需将其添加到每个动作控制器中,如下所示:
[Route("Report/GetReport/{folio}")]
[SwaggerFileResponse(HttpStatusCode.OK, "File Response")]
[HttpGet]
public HttpResponseMessage ExportReport(string folio)
{
...
因此,当swagger生成json元数据时,autorest将正确创建一个返回Task&lt; Microsoft.Rest.HttpOperationResponse&LT; System.IO.Stream&gt; &GT;
答案 1 :(得分:2)
生成的代码将方法的输出视为json,因为将错误的类型写入了swagger.json(可能是....#/ definitions / ....)。它应包含“类型”:“文件”
您可以使用SwaggerGen选项操纵输出。
如果您的方法如下:
[Produces("application/pdf")]
[ProducesResponseType(200, Type = typeof(Stream))]
public IActionResult Download()
{
Stream yourFileStream = null; //get file contents here
return new FileStreamResult(yourFileStream , new MediaTypeHeaderValue("application/pdf"))
{
FileDownloadName = filename
};
}
在设置Swagger的启动公司中,配置要返回的Type和要显示在Swagger文件中的Type之间的映射
services.AddSwaggerGen(
options =>
{
options.MapType<System.IO.Stream>(() => new Schema { Type = "file" });
});
然后您生成的代码如下:
public async Task<HttpOperationResponse<System.IO.Stream>> DownloadWithHttpMessagesAsync()
答案 2 :(得分:1)
对于Swashbuckle版本4,我可以创建过滤器:
public class FileDownloadOperation : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var rt = context.MethodInfo.ReturnType;
if (rt == typeof(Stream) ||
rt == typeof(Task<Stream>) ||
rt == typeof(FileStreamResult) ||
rt == typeof(Task<FileStreamResult>))
{
operation.Responses["200"] = new Response
{
Description = "Success", Schema = new Schema {Type = "file"}
};
operation.Produces.Clear();
operation.Produces.Add("application/octet-stream");
}
}
}
将其分配给swagger生成器
services.AddSwaggerGen(c =>
{
...
c.OperationFilter<FileDownloadOperation>();
});
然后只有简单的控制器:
[HttpGet("{fileId}")]
public async Task<FileStreamResult> GetMyFile(int fileId)
{
var result = await _fileService.GetFile(fileId);
return File(result.Stream, result.ContentType, result.FileName);
}
答案 3 :(得分:0)
我使用@PetrŠtipek的答案并将其全球化:
public void Apply(Operation operation, OperationFilterContext context)
{
Type type = context.MethodInfo.ReturnType;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
{
type = type.GetGenericArguments()[0];
}
if (typeof(FileResult).IsAssignableFrom(type))
{
Response response = operation.Responses["200"];
operation.Responses["200"] = new Response
{
Description = string.IsNullOrWhiteSpace(response?.Description) ? "Success" : response.Description,
Schema = new Schema { Type = "file" }
};
operation.Produces.Clear();
operation.Produces.Add(MediaTypeNames.Application.Octet);
}
}
答案 4 :(得分:0)
.EnableSwagger(c =>
{
…
c.OperationFilter<FileManagementFilter>();
});
public class FileManagementFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.operationId.ToLower().IndexOf("_download") >= 0)
{
operation.produces = new[] { "application/octet-stream" };
operation.responses["200"].schema = new Schema { type = "file", description = "Download file" };
}
}
}
[ResponseType(typeof(HttpResponseMessage))]
//[SwaggerResponse(HttpStatusCode.OK, Type = typeof(byte[]))]
[HttpGet, Route("DownloadItemFile")]
public HttpResponseMessage DownloadItemFile(int itemId, string fileName)
{
var result = …
return result;
}
<块引用>
注意:动作名称必须“下载...”