【问题标题】:Autorest/Swagger generated code for Web Api controller that returns FileAutorest/Swagger 为返回 File 的 Web Api 控制器生成代码
【发布时间】:2017-02-14 23:16:41
【问题描述】:

在我的 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;
        }
}

所以,当我使用客户参数发出一个简单的 GET 请求时,我会在浏览器中得到一个 pdf 文件作为响应。一些响应头:

内容处置:附件;文件名=Reporte.pdf 内容长度:22331 内容类型:应用程序/八位字节流

在我的 Xamarin PCL 项目中设置 swagger、生成 json 元数据文件并使用它生成 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 content-response 的代码?

【问题讨论】:

  • 您是否尝试过使用 Swagger Codegen 来生成 C# API 客户端? Swagger Codegen 生成的 C# API 客户端应该能够处理file 下载。
  • 我找到了使它工作的过滤器的代码。但是生成的代码仍然存在问题。一旦解决,我会在这里发布

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


【解决方案1】:

创建返回文件的自定义过滤器:

 [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 >

的方法

【讨论】:

  • 这真的很有帮助。感谢您的解决方案
【解决方案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 生成,配置你返回的类型和你想要出现在你的 Swagger 文件中的类型之间的映射

     services.AddSwaggerGen(
            options =>
            {                   
                options.MapType<System.IO.Stream>(() => new Schema { Type = "file" });
            });

那么你生成的代码如下所示:

public async Task<HttpOperationResponse<System.IO.Stream>> DownloadWithHttpMessagesAsync()

【讨论】:

    【解决方案3】:

    对于 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);
    }
    

    【讨论】:

      【解决方案4】:

      我使用@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);
          }
      }
      

      【讨论】:

        【解决方案5】:
        .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;
        }
        

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

        【讨论】:

        • 欢迎您,感谢您的贡献。我已经编辑了您的答案以改进代码格式;请查看,以便您自己格式化未来的答案。此外,为了使这更有用,请考虑编辑您的答案以包含为什么此代码解决问题的解释。
        猜你喜欢
        • 2019-10-17
        • 2018-02-24
        • 2014-09-23
        • 2022-02-09
        • 1970-01-01
        • 2016-08-20
        • 2019-05-19
        • 1970-01-01
        • 2017-01-31
        相关资源
        最近更新 更多