Swashbuckle 生成的 Swagger 有错误

Swashbuckle generated Swagger has errors

我正在针对 http://editor.swagger.io/ 测试由 Swashbuckle nuget 包生成的 swagger 元数据,并在元数据中包含以下部分:

     "/User":{
     "get":{
              <snip>
     }
     "post":{
        "tags":[
           "User"
        ],
        "summary":"Create a new user, adds them to a space and sets dashboard view.",
        "operationId":"User_Post",
        "consumes":[
           "application/json",
           "text/json"
        ],
        "produces":[

        ],
        "parameters":[ // This line is marked as an error.
           {
              "name":"user",
              "in":"body",
              "description":"New user's username and assigned space.",
              "required":true,
              "schema":{
                 "$ref":"#/definitions/F1.Birst.CreateUserRequest"
              }
           },
           {
              "name":"Authorization",
              "in":"header",
              "description":"access token",
              "required":true,
              "type":"string"
           }
        ],
        "responses":{
           "204":{
              "description":"No Content"
           }
        },
        "deprecated":false
     }
  },

报告的错误是:

Swagger Error
Not a valid parameter definition
Jump to line 330
Details
 Object
code:  "ONE_OF_MISSING"
 params: Array [0]
message:  "Not a valid parameter definition"
 path: Array [5]
schemaId:  "http://swagger.io/v2/schema.json#"
 inner: Array [2]
level: 900
type:  "Swagger Error"
description:  "Not a valid parameter definition"
lineNumber: 330

使用 NSwag 生成代码似乎仍然没有问题。这是 Swagger 编辑器的问题,还是 Swasbuckle 的问题,为什么 NSwag 能够毫无问题地处理这个问题?

其他 swagger 代码生成器会遇到这个问题吗?

如果这不是 Swagger Editor 或 Swashbuckle 的问题,我将如何在我的代码中解决这个问题?当前定义如下:

    /// <summary>
    /// Create a new user, adds them to a space and sets dashboard view.
    /// </summary>
    /// <param name="user">New user's username and assigned space.</param>
    public void Post([FromBody] CreateUserRequest user)

我试过了

    /// <summary>
    /// Create a new user, adds them to a space and sets dashboard view.
    /// </summary>
    /// <param name="user">New user's username and assigned space.</param>
    public void Post([FromBody][ModelBinder] CreateUserRequest user)

但这最终将变量放入查询字符串中。

编辑:

这里是 CreateUserRequest 的定义。验证器抛出正则表达式错误(这是有效的 C# 正则表达式)。我假设这是由于验证器使用 JS 正则表达式语法造成的?

这会是 "Not a valid parameter definition" 的根本原因吗?如果是这样,那么我想这只是另一个错误的重复。在正则表达式中显示正则表达式错误,当 class 被引用为参数时显示另一个错误?

  "F1.Birst.CreateUserRequest":{
     "required":[
        "username",
        "space"
     ],
     "type":"object",
     "properties":{
        "username":{
           "pattern":"(?i:^f1(\.(test|churchstaff|churchuser|internal))?\.\d+\.\d+$)",
           "type":"string"
        },
        "space":{
           "pattern":"(?i:^f1\.[\d\w]+$)",
           "type":"string"
        }
     }
  },

Class定义:

public class CreateUserRequest
{
    [Required]
    [RegularExpression(@"(?i:^f1(\.(test|churchstaff|churchuser|internal))?\.\d+\.\d+$)")]
    public string Username { get; set; }

    [Required]
    [RegularExpression(@"(?i:^f1\.[\d\w]+$)")]
    public string Space { get; set; }
}

我能够通过创建一个接受选项参数的 RegularExpressionAttribute 版本并从正则表达式中删除不区分大小写的命名组来解决这个问题。

属性定义

/// <summary>
/// Regular expression validation attribute with ability to specify options.
/// </summary>
/// <remarks>Swagger schema validation fails if you use the (?i:) named group for case insensitive regexes.</remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class RegularExpressionWithOptionsAttribute : RegularExpressionAttribute
{
    private Regex Regex { get; set; }
    private RegexOptions Options { get; }

    /// <summary>
    /// Constructor that accepts the regular expression pattern
    /// </summary>
    /// <param name="pattern">The regular expression to use.  It cannot be null.</param>
    /// <param name="options">The options to use for the regular expression.</param>
    public RegularExpressionWithOptionsAttribute(string pattern, RegexOptions options)
        : base(pattern)
    {
        Options = options;
    }

    /// <summary>
    /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
    /// </summary>
    /// <remarks>This override performs the specific regular expression matching of the given <paramref name="value"/></remarks>
    /// <param name="value">The value to test for validity.</param>
    /// <returns><c>true</c> if the given value matches the current regular expression pattern</returns>
    /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
    /// <exception cref="ArgumentException"> is thrown if the <see cref="Pattern"/> is not a valid regular expression.</exception>
    public override bool IsValid(object value)
    {
        SetupRegex();

        // Convert the value to a string
        var stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);

        // Automatically pass if value is null or empty. RequiredAttribute should be used to assert a value is not empty.
        if (string.IsNullOrEmpty(stringValue))
        {
            return true;
        }

        var match = Regex.Match(stringValue);

        // We are looking for an exact match, not just a search hit. This matches what
        // the RegularExpressionValidator control does
        return (match.Success && match.Index == 0 && match.Length == stringValue.Length);
    }

    /// <summary>
    /// Sets up the <see cref="Regex"/> property from the <see cref="Pattern"/> property.
    /// </summary>
    /// <exception cref="ArgumentException"> is thrown if the current <see cref="Pattern"/> cannot be parsed</exception>
    /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
    /// <exception cref="ArgumentOutOfRangeException"> thrown if <see cref="MatchTimeoutInMilliseconds" /> is negative (except -1),
    /// zero or greater than approximately 24 days </exception>
    private void SetupRegex()
    {
        if (Regex != null)
            return;

        // Ensure base.SetupRegex is called, to check for empty pattern and setup timeout.
        base.IsValid(null);

        Regex = MatchTimeoutInMilliseconds == -1
            ? new Regex(Pattern, Options)
            : Regex = new Regex(Pattern, Options, TimeSpan.FromMilliseconds(MatchTimeoutInMilliseconds));
    }
}

用法

public class CreateUserRequest
{
    [Required]
    [RegularExpressionWithOptions(@"^f1(\.(test|churchstaff|churchuser|internal))?\.\d+\.\d+$", RegexOptions.IgnoreCase)]
    public string Username { get; set; }
    [Required]
    //[RegularExpressionWithOptions(@"^f1\.[\d\w]+$", RegexOptions.IgnoreCase)]
    public string Space { get; set; }
}