【问题标题】:Swashbuckle 5 and multipart/form-data HelpPagesSwashbuckle 5 和 multipart/form-data 帮助页面
【发布时间】:2017-01-02 07:38:49
【问题描述】:

我一直试图让 Swashbuckle 5 使用 multipart/form-data 参数为带有 Post 请求的 ApiController 生成完整的帮助页面。该操作的帮助页面出现在浏览器中,但没有包含有关表单中传递的参数的信息。我创建了一个操作过滤器并在 SwaggerConfig 中启用它,包含 URI 参数、返回类型和其他从 XML cmets 派生的信息的网页显示在浏览器帮助页面中;但是,操作过滤器中没有指定任何有关参数的信息,并且帮助页面不包含有关参数的信息。

我一定错过了什么。对我可能错过的内容有什么建议吗?

操作过滤代码:

public class AddFormDataUploadParamTypes : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)         { 
         if (operation.operationId == "Documents_Upload") 
         { 
            operation.consumes.Add("multipart/form-data");
            operation.parameters = new[]
            {
                new Parameter
                 {
                     name = "anotherid",
                     @in  = "formData",
                     description = "Optional identifier associated with the document.",
                     required = false,
                     type = "string",
                     format = "uuid"

                 },
                 new Parameter
                 {
                     name = "documentid",
                     @in  = "formData",
                     description = "The document identifier of the slot reserved for the document.",
                     required = false,
                     type = "string",
                     format = "uuid"
                 },
                 new Parameter
                 {
                     name = "documenttype",
                     @in  = "formData",
                     description = "Specifies the kind of document being uploaded. This is not a file name extension.",
                     required = true,
                     type = "string"
                 },
                 new Parameter
                 {
                     name = "emailfrom",
                     @in  = "formData",
                     description = "A optional email origination address used in association with the document if it is emailed to a receiver.",
                     required = false,
                     type = "string"
                 },
                new Parameter
                 {
                     name = "emailsubject",
                     @in  = "formData",
                     description = "An optional email subject line used in association with the document if it is emailed to a receiver.",
                     required = false,
                     type = "string"
                 },
                 new Parameter 
                 { 
                     name = "file", 
                     @in = "formData", 
                     description = "File to upload.",
                     required = true, 
                     type = "file" 
                 }
             }; 
         } 
     } 
}

【问题讨论】:

  • 能把控制器的方法的代码加到问题里吗?
  • 希望您在 swagger 配置中附加了操作过滤器..

标签: asp.net-web-api2 multipartform-data swashbuckle


【解决方案1】:

我想你已经弄清楚你的问题是什么了。我能够使用您发布的代码制作一个完美的“swagger ui”界面,并带有file [BROWSE...] 输入控件。

我只是稍微修改了您的代码,以便在检测到我首选的ValidateMimeMultipartContentFilter 属性stolen from Damien Bond 时应用它。因此,我对您的课程稍作修改的版本如下所示:

public class AddFormDataUploadParamTypes<T> : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var actFilters = apiDescription.ActionDescriptor.GetFilterPipeline();
        var supportsDesiredFilter = actFilters.Select(f => f.Instance).OfType<T>().Any();

        if (supportsDesiredFilter)
        {
            operation.consumes.Add("multipart/form-data");
            operation.parameters = new[]
            {
             //other parameters omitted for brevity
             new Parameter
             {
                 name = "file",
                 @in = "formData",
                 description = "File to upload.",
                 required = true,
                 type = "file"
             }
         };
        }
    }
}

这是我的 Swagger 用户界面:

FWIW:

我的 NuGet

<package id="Swashbuckle" version="5.5.3" targetFramework="net461" />
<package id="Swashbuckle.Core" version="5.5.3" targetFramework="net461" />

Swagger 配置示例

public class SwaggerConfig
{
    public static void Register()
    {
        var thisAssembly = typeof(SwaggerConfig).Assembly;

        GlobalConfiguration.Configuration 
            .EnableSwagger(c =>
                {

                    c.Schemes(new[] { "https" });

                    // Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
                    // hold additional metadata for an API. Version and title are required but you can also provide
                    // additional fields by chaining methods off SingleApiVersion.
                    //
                    c.SingleApiVersion("v1", "MyCorp.WebApi.Tsl");


                    c.OperationFilter<MyCorp.Swashbuckle.AddFormDataUploadParamTypes<MyCorp.Attr.ValidateMimeMultipartContentFilter>>();

                })
            .EnableSwaggerUi(c =>
                {

                    // If your API supports ApiKey, you can override the default values.
                    // "apiKeyIn" can either be "query" or "header"                                                
                    //
                    //c.EnableApiKeySupport("apiKey", "header");
                });
    }


}

2019 年 3 月更新


我无法快速访问上面的原始项目,但是,这里有一个来自不同项目的示例 API 控制器...

控制器签名:

    [ValidateMimeMultipartContentFilter]
    [SwaggerResponse(HttpStatusCode.OK, Description = "Returns JSON object filled with descriptive data about the image.")]
    [SwaggerResponse(HttpStatusCode.NotFound, Description = "No appropriate equipment record found for this endpoint")]
    [SwaggerResponse(HttpStatusCode.BadRequest, Description = "This request was fulfilled previously")]
    public async Task<IHttpActionResult> PostSignatureImage(Guid key)

您会注意到签名中没有代表我的文件的实际参数,您可以在下面看到我只是启动了MultipartFormDataStreamProvider 来吸出传入的 POST 表单数据。

控制器主体:

        var signatureImage = await db.SignatureImages.Where(img => img.Id == key).FirstOrDefaultAsync();
        if (signatureImage == null)
        {
            return NotFound();
        }

        if (!signatureImage.IsOpenForCapture)
        {
            ModelState.AddModelError("CaptureDateTime", $"This equipment has already been signed once on {signatureImage.CaptureDateTime}");
        }

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        string fileName = String.Empty;
        string ServerUploadFolder = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/");

        DirectoryInfo di = new DirectoryInfo(ServerUploadFolder + key.ToString());

        if (di.Exists == true)
            ModelState.AddModelError("id", "It appears an upload for this item is either in progress or has already occurred.");
        else
            di.Create();

        var fullPathToFinalFile = String.Empty;
        var streamProvider = new MultipartFormDataStreamProvider(di.FullName);
        await Request.Content.ReadAsMultipartAsync(streamProvider);


        foreach (MultipartFileData fileData in streamProvider.FileData)
        {
            if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
            {
                return StatusCode(HttpStatusCode.NotAcceptable);
            }
            fileName = cleanFileName(fileData.Headers.ContentDisposition.FileName);

            fullPathToFinalFile = Path.Combine(di.FullName, fileName);

            File.Move(fileData.LocalFileName, fullPathToFinalFile);

            signatureImage.Image = File.ReadAllBytes(fullPathToFinalFile);

            break;
        }

        signatureImage.FileName = streamProvider.FileData.Select(entry => cleanFileName(entry.Headers.ContentDisposition.FileName)).First();
        signatureImage.FileLength = signatureImage.Image.LongLength;
        signatureImage.IsOpenForCapture = false;
        signatureImage.CaptureDateTime = DateTimeOffset.Now;
        signatureImage.MimeType = streamProvider.FileData.Select(entry => entry.Headers.ContentType.MediaType).First();

        db.Entry(signatureImage).State = EntityState.Modified;

        try
        {
            await db.SaveChangesAsync();

            //cleanup...
            File.Delete(fullPathToFinalFile);
            di.Delete();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!SignatureImageExists(key))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        char[] placeHolderImg = paperClipIcon_svg.ToCharArray();
        signatureImage.Image = Convert.FromBase64CharArray(placeHolderImg, 0, placeHolderImg.Length);

        return Ok(signatureImage);

【讨论】:

  • 我觉得它需要控制器的例子来使这个成为一个完整的答案
【解决方案2】:

上面列出的 Swashbuckle v5.0.0-rc4 方法不起作用。但是通过阅读 OpenApi 规范,我设法实现了一个上传单个文件的有效解决方案。其他参数可以轻松添加:

    public class FileUploadOperationFilter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var isFileUploadOperation =
                context.MethodInfo.CustomAttributes.Any(a => a.AttributeType == typeof(YourMarkerAttribute));
            if (!isFileUploadOperation) return;

            var uploadFileMediaType = new OpenApiMediaType()
            {
                Schema = new OpenApiSchema()
                {
                    Type = "object",
                    Properties =
                    {
                        ["uploadedFile"] = new OpenApiSchema()
                        {
                            Description = "Upload File",
                            Type = "file",
                            Format = "binary"
                        }
                    },
                    Required = new HashSet<string>()
                    {
                        "uploadedFile"
                    }
                }
            };
            operation.RequestBody = new OpenApiRequestBody
            {
                Content =
                {
                    ["multipart/form-data"] = uploadFileMediaType
                }
            };
        }
    }

【讨论】:

  • 谢谢。我能够扩展它来处理多个文件上传。
【解决方案3】:

扩展@bkwdesign 非常有用的答案...

他/她的代码包括:

//other parameters omitted for brevity

您实际上可以将所有参数信息(对于非多部分表单参数)从参数拉到过滤器。在检查supportsDesiredFilter 中,执行以下操作:

if (operation.parameters.Count != apiDescription.ParameterDescriptions.Count)
{
    throw new ApplicationException("Inconsistencies in parameters count");
}
operation.consumes.Add("multipart/form-data");

var parametersList = new List<Parameter>(apiDescription.ParameterDescriptions.Count + 1);
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; ++i)
{
    var schema = schemaRegistry.GetOrRegister(apiDescription.ParameterDescriptions[i].ParameterDescriptor.ParameterType);

    parametersList.Add(new Parameter
    {
        name = apiDescription.ParameterDescriptions[i].Name,
        @in = operation.parameters[i].@in,
        description = operation.parameters[i].description,
        required = !apiDescription.ParameterDescriptions[i].ParameterDescriptor.IsOptional,
        type = apiDescription.ParameterDescriptions[i].ParameterDescriptor.ParameterType.FullName,
        schema = schema,
    });
}

parametersList.Add(new Parameter
{
    name = "fileToUpload",
    @in = "formData",
    description = "File to upload.",
    required = true,
    type = "file"
});
operation.parameters = parametersList;

首先它会检查以确保传入的两个数组是一致的。然后它遍历数组以提取所需的信息以放入 Swashbuckle Parameters 的集合中。

最难的事情是弄清楚需要在“模式”中注册类型才能让它们显示在 Swagger UI 中。但是,这对我有用。

我所做的一切都与@bkwdesign 的帖子一致。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-03
    • 1970-01-01
    • 2022-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-22
    相关资源
    最近更新 更多