【问题标题】:Swashbuckle polymorphism support issueSwashbuckle 多态性支持问题
【发布时间】:2018-08-05 08:38:10
【问题描述】:

我正在使用 Swashbuckle v3.0。

我不确定这是否是一个错误,但多态性并没有按应有的方式工作。我有以下课程:

BasePersonDocumentDto
{
   Id,
   Number
}

IdentityDto: BasePersonDocumentDto
{

}

PassportDto: BasePersonDocumentDto
{
 VisaNumber
}

为了应用继承和多态,我创建了一个模式和文档过滤器。 I followed this answer 下面是我使用的代码。

public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
    private List<Type> derivedTypes = new List<Type>() { typeof(IdentityDto), typeof(PassportDto) };

    public void Apply(Schema model, SchemaFilterContext context)
    {
        if (!derivedTypes.Contains(context.SystemType)) return;

        var baseSchema = new Schema() { Ref = "#/definitions/" + typeof(T).Name };
        var clonedBaseSchema = new Schema
        {
            Properties = model.Properties,
            Type = model.Type,
            Required = model.Required
        };

        model.AllOf = new List<Schema> { baseSchema, clonedBaseSchema };

        //Reset properties for they are included in allOf, should be null but code does not handle it
        model.Properties = new Dictionary<string, Schema>();
    }
}


public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
    private List<Type> derivedTypes = new List<Type>() { typeof(IdentityDto), typeof(PassportDto) };

    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        const string discriminatorName = "discriminator";

        var baseSchema = context.SchemaRegistry.Definitions[typeof(T).Name];

        //Discriminator property
        baseSchema.Discriminator = discriminatorName;
        baseSchema.Required = new List<string> { discriminatorName };

        if (!baseSchema.Properties.ContainsKey(discriminatorName))
            baseSchema.Properties.Add(discriminatorName, new Schema { Type = "string" });

        //Register dervied classes
        foreach (var item in derivedTypes)
            context.SchemaRegistry.GetOrRegister(item);
    }
}

生成的定义是:

//This is for BasePersonDocumentDto and for IdentityDto
"BasePersonDocumentDto":{  
         "required":[  
            "discriminator"
         ],
         "type":"object",
         "properties":{  
            "number":{  
               "type":"string"
            },
            "id":{  
               "format":"int32",
               "type":"integer"
            },
            "discriminator":{  
               "type":"string"
            }
         },
         "discriminator":"discriminator"
      }

"PassportDto":{  
         "required":[  
            "discriminator"
         ],
         "type":"object",
         "properties":{  
            "number":{  
               "type":"string"
            },
            "id":{  
               "format":"int32",
               "type":"integer"
            },
            "visaNumber":{  
               "type":"string"
            },
            "discriminator":{  
               "type":"string"
            }
         },
         "discriminator":"discriminator"
      }

现在进行测试,我创建了一个具有 List 属性的 InputDto。我尝试传递以下 JSON,但即使正确传递了鉴别器,两个传递的项目都显示为 BasePersonDocumentDto。这是我传递的 JSON:

{  
   "documentsDto":[  
      {  
         "discriminator":"IdentityDto"
      },
      {  
         "visaNumber":"RRXX323TR",
         "discriminator":"PassportDto"
      }
   ]
}

【问题讨论】:

  • 我已经使用 swagger 有一段时间了,遇到了相当复杂的场景......但从来没有需要discriminator,_你能告诉我们更多你为什么需要它的细节吗?跨度>
  • 我想要的只是当派生类型对象作为 Json 传递到后端时,Json 被反序列化为派生类型而不是基类。我假设因为在他们的文档中大摇大摆地声明他们支持继承和多态,这是可能的。我通过实现自定义 JsonConverter 来解决这个问题。我会尽快发布我的解决方案。
  • 如果你能在 GitHub 上创建一个最小的例子,我可以看看并给你一些指导,因为听起来你的 web api 实现过于复杂了

标签: c# inheritance polymorphism swagger swashbuckle


【解决方案1】:

一段时间后,我找到了解决问题的方法。我的情况很简单。我只有一个基类和一些派生类,但是 swagger 只生成基类而不生成派生类。

为了解决这个问题,我使用了我在问题中发布的代码,它应用了继承,并且还添加了一个名为“discriminator”的隐式列。此列用于区分子类型。

最后一个问题是在反序列化为对象时,swagger 无法使用“鉴别器”列,因此它总是映射到基本类型。我找到了两个解决方案,第一个是实现自定义模型绑定器,第二个是实现自定义 JsonConverter。

我选择了第二个选项,因为它看起来更合乎逻辑,因为我的问题是处理反序列化 Json。除了将 JsonConverter 与我的基类一起使用之外,我还装饰了它的“KnownType”属性。

[KnownType(typeof(IdentityDto))]
[KnownType(typeof(PassportDto))]
[JsonConverter(typeof(InheritanceConverter))]
BasePersonDocumentDto
{
   Id,
   Number
}

下面是 JsonConverter 的代码:

/// <summary>
/// Json deserialization for inheritance structure with discriminator
/// </summary>
public class InheritanceConverter : JsonConverter
{
    /// <summary>
    /// Default name for the discriminator property
    /// </summary>
    private string _discriminator { get; set; } = "discriminator";

    public InheritanceConverter()
    {

    }

    /// <summary>
    /// Discriminator name to map between types
    /// </summary>
    /// <param name="discriminator">The discriminator property name. The defualt value is 'discriminator'</param>
    public InheritanceConverter(string discriminator)
    {
        if (!string.IsNullOrWhiteSpace(discriminator))
            _discriminator = discriminator;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //Check null
        if (reader.TokenType == JsonToken.Null) return null;

        //Parse json
        var jDerivedObject = JObject.Load(reader);

        //Get discriminator
        var discriminator = jDerivedObject.Value<string>(_discriminator);
        if (string.IsNullOrWhiteSpace(discriminator))
            throw new Exception("Invalid discriminator value");

        //Get the type
        var derivedType = GetSubType(discriminator, objectType);

        //Create a new instance of the target type
        var derivedObject = Activator.CreateInstance(derivedType);

        //Populate the derived object
        serializer.Populate(jDerivedObject.CreateReader(), derivedObject);

        return derivedObject;
    }

    //TODO:- validate based on the base and sub-types via the KnownTypeAttributes
    public override bool CanConvert(Type objectType) => true;

    //Not required
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    //Not required
    public override bool CanWrite => false;

    #region Methods

    /// <summary>
    /// Get sub-type via KnownTypeAttributes
    /// </summary>
    /// <param name="derivedTypeName">The target type name which corosponds to the discriminator</param>
    private Type GetSubType(string derivedTypeName, Type baseType)
    {
        var knownTypes = baseType.GetCustomAttributes(false).Where(ca => ca.GetType().Name == "KnownTypeAttribute").ToList();

        if (knownTypes == null || knownTypes.Count == 0)
            throw new Exception(
                string.Format("Couldn't find any KnownAttributes over the base {0}. Please define at least one KnownTypeAttribute to determine the sub-type", baseType.Name));

        foreach (dynamic type in knownTypes)
        {
            if (type.Type != null && type.Type.Name.ToLower() == derivedTypeName.ToLower())
                return type.Type;
        }

        throw new Exception(string.Format("Discriminator '{0}' doesn't match any of the defined sub-types via KnownTypeAttributes", derivedTypeName));
    }

    #endregion
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-28
    • 1970-01-01
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 2019-10-01
    相关资源
    最近更新 更多