Autorest / Swagger为返回File的Web Api控制器生成代码

时间:2017-02-14 23:16:41

标签: c# json asp.net-web-api swagger

在我的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内容响应的代码?

5 个答案:

答案 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;
}
<块引用>

注意:动作名称必须“下载...”

enter image description here