当前位置: 首页 > 知识库问答 >
问题:

Swashback:多态性不适用于外部nuget包

狄彬彬
2023-03-14

在我们的API中,当用户调用endpoint时,我们希望从外部Nuget包返回一个对象。

这个对象(可以在这里查看)有几个属性。其中一个叫做动作。此属性具有as typeIPaymentResponseAction,但可以是一组不同的操作类型(您可以在这里看到它们)。

生成的swagger不知道这些操作,也不生成所需的代码。即使设置了多态性设置。

    services.AddSwaggerGen(c =>
            {
                c.EnableAnnotations();
                c.UseOneOfForPolymorphism();
            });

有没有办法让这些东西在我的大摇大摆中出现?也许有一些定制的招摇选项?

使用c. SelectSubTypes使用代码在第一次回答后更新

    Adyen.Model.Checkout.PaymentResponse": {
        "type": "object",
        "properties": {
          "resultCode": {
            "$ref": "#/components/schemas/Adyen.Model.Checkout.PaymentResponse.ResultCodeEnum"
          },
          "action": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.IPaymentResponseAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutAwaitAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutDonationAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutOneTimePasscodeAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutQrCodeAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutRedirectAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutSDKAction"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutThreeDS2Action"
              },
              {
                "$ref": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutVoucherAction"
              }
            ],
            "nullable": true
          }......

而IPayment响应行动是:

    "Adyen.Model.Checkout.Action.IPaymentResponseAction": {
        "required": [
          "type"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "await": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutAwaitAction",
            "donation": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutDonationAction",
            "oneTimePasscode": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutOneTimePasscodeAction",
            "qrCode": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutQrCodeAction",
            "redirect": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutRedirectAction",
            "sdk": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutSDKAction",
            "threeDS2Action": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutThreeDS2Action",
            "voucher": "#/components/schemas/Adyen.Model.Checkout.Action.CheckoutVoucherAction"
          }
        }
      },

更新:我现在所有的行动看起来都是这样的,所以我认为还没有。但是很接近了!

    "CheckoutAwaitAction": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/Rig.Commercial.Reservation.Core.Settings.Swagger.Swagger_Models.PaymentResponseAction"
          }
        ],
        "additionalProperties": false
      }

共有1个答案

周马鲁
2023-03-14

这是解决问题的问题的更新答案:)很抱歉的长帖子。

您描述的问题是由于缺乏Swashuckle处理C#接口以反映多态层次结构的能力造成的(对我来说似乎是一个缺失的功能)。

这里有一个变通方法(请参见此处的MVP项目)。

    c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true);
    c.UseAllOfToExtendReferenceSchemas();
    c.UseAllOfForInheritance();
    c.UseOneOfForPolymorphism();

Swashback不将接口视为“父”类型。如果我们让它“思考”它仍然在处理一个类而不是一个接口呢?让我们介绍一下PaymentResponseActionclass:

    [DataContract]
    [SwaggerDiscriminator("type")]
    public class PaymentResponseAction : IPaymentResponseAction
    {
        [JsonProperty(PropertyName = "type")]
        public string Type { get; set; }
    }

AddSwaggerGen调用中,我们还应该提供正确的鉴别器选项:

    c.SelectDiscriminatorNameUsing(type =>
    {
        return type.Name switch
        {
            nameof(PaymentResponseAction) => "type",
            _ => null
        };
    });

    c.SelectDiscriminatorValueUsing(subType =>
    {
        return subType.Name switch
        {
            nameof(CheckoutAwaitAction) => "await",
            nameof(CheckoutBankTransferAction) => "bank",
            nameof(CheckoutDonationAction) => "donation",
            nameof(CheckoutOneTimePasscodeAction) => "oneTimePasscode",
            // rest of the action types ...
            _ => null
        };
    });

到目前为止,一切都很正常。唯一缺少的是实现类的allOf关键字。目前,仅使用Swashback的选项是不可能的,因为它在构造allOf时使用BaseType来解析子类型。

和以前一样,我们可以让Swashback认为它处理的是继承的类型。我们可以生成“假”类型,这些类型继承我们新的PaymentResponseAction类,并从我们感兴趣的实现类型复制属性。这些“假”类型不必是功能性的;它们应该包含足够的类型信息,以使swashback感到高兴。

下面是一个实现此功能的方法的示例。它接受源类型以从基类型复制属性,并返回新类型。使用自定义的附加设置,也可以使用自定义的附加属性。

请注意,此代码应进行改进,以便为生产做好准备;例如,它不应该“复制”具有JsonIgnore或类似属性的公共属性。

    private static Type GenerateReparentedType(Type originalType, Type parent)
    {
        var assemblyBuilder =
            AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("hack"), AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("hack");
        var typeBuilder = moduleBuilder.DefineType(originalType.Name, TypeAttributes.Public, parent);

        foreach (var property in originalType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            var newProperty = typeBuilder
                .DefineProperty(property.Name, property.Attributes, property.PropertyType, null);

            var getMethod = property.GetMethod;
            if (getMethod is not null)
            {
                var getMethodBuilder = typeBuilder
                    .DefineMethod(getMethod.Name, getMethod.Attributes, getMethod.ReturnType, Type.EmptyTypes);
                getMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
                newProperty.SetGetMethod(getMethodBuilder);
            }

            var setMethod = property.SetMethod;
            if (setMethod is not null)
            {
                var setMethodBuilder = typeBuilder
                    .DefineMethod(setMethod.Name, setMethod.Attributes, setMethod.ReturnType, Type.EmptyTypes);
                setMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
                newProperty.SetSetMethod(setMethodBuilder);
            }

            var customAttributes = CustomAttributeData.GetCustomAttributes(property).ToArray();
            foreach (var customAttributeData in customAttributes)
            {
                newProperty.SetCustomAttribute(DefineCustomAttribute(customAttributeData));
            }
        }

        var type = typeBuilder.CreateType();
        return type ?? throw new InvalidOperationException($"Unable to generate a re-parented type for {originalType}.");
    }

    private static CustomAttributeBuilder DefineCustomAttribute(CustomAttributeData attributeData)
    {
        // based on https://stackoverflow.com/a/3916313/8607180

        var constructorArguments = attributeData.ConstructorArguments
            .Select(argument => argument.Value)
            .ToArray();

        var propertyArguments = new List<PropertyInfo>();
        var propertyArgumentValues = new List<object?>();
        var fieldArguments = new List<FieldInfo>();
        var fieldArgumentValues = new List<object?>();

        foreach (var argument in attributeData.NamedArguments ?? Array.Empty<CustomAttributeNamedArgument>())
        {
            var fieldInfo = argument.MemberInfo as FieldInfo;
            var propertyInfo = argument.MemberInfo as PropertyInfo;

            if (fieldInfo != null)
            {
                fieldArguments.Add(fieldInfo);
                fieldArgumentValues.Add(argument.TypedValue.Value);
            }
            else if (propertyInfo != null)
            {
                propertyArguments.Add(propertyInfo);
                propertyArgumentValues.Add(argument.TypedValue.Value);
            }
        }

        return new CustomAttributeBuilder(
            attributeData.Constructor, constructorArguments,
            propertyArguments.ToArray(), propertyArgumentValues.ToArray(),
            fieldArguments.ToArray(), fieldArgumentValues.ToArray()
        );
    }

现在我们可以在AddSwaggerGen调用中使用它来使Swashuckle以我们想要的方式解析这些类型:

    var actionTypes = new[]
    {
        GenerateReparentedType(typeof(CheckoutAwaitAction), typeof(PaymentResponseAction)),
        GenerateReparentedType(typeof(CheckoutBankTransferAction), typeof(PaymentResponseAction)),
        GenerateReparentedType(typeof(CheckoutDonationAction), typeof(PaymentResponseAction)),
        GenerateReparentedType(typeof(CheckoutOneTimePasscodeAction), typeof(PaymentResponseAction)),
        // rest of the action types ...
    };

    c.SelectSubTypesUsing(type =>
    {
        var allTypes = typeof(Startup).Assembly.GetTypes().ToArray();
        return type.Name switch
        {
            nameof(PaymentResponseAction) => new[] { typeof(PaymentResponseAction) }.Union(actionTypes),
            nameof(IPaymentResponseAction) => new[] { typeof(PaymentResponseAction) }.Union(actionTypes),
            _ => allTypes.Where(t => t.IsSubclassOf(type))
        };
    });

现在Swashuckle应该正确生成所有内容:

paths:
  /api/someEndpoint:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
# ...

components:
  schemas:
    PaymentResponse:
      type: object
      properties:
        resultCode:
          allOf:
            - $ref: '#/components/schemas/ResultCodeEnum'
          nullable: true
        action:
          oneOf:
            - $ref: '#/components/schemas/PaymentResponseAction'
            - $ref: '#/components/schemas/CheckoutAwaitAction'
            - $ref: '#/components/schemas/CheckoutBankTransferAction'
            - $ref: '#/components/schemas/CheckoutDonationAction'
            - $ref: '#/components/schemas/CheckoutOneTimePasscodeAction'
            # ... rest of the actions
          nullable: true
        # ... rest of the properties
    PaymentResponseAction:
      required:
        - type
      type: object
      properties:
        type:
          type: string
          nullable: true
      additionalProperties: false
      discriminator:
        propertyName: type
        mapping:
          await: '#/components/schemas/CheckoutAwaitAction'
          bank: '#/components/schemas/CheckoutBankTransferAction'
          donation: '#/components/schemas/CheckoutDonationAction'
          oneTimePasscode: '#/components/schemas/CheckoutOneTimePasscodeAction'
          # ... rest of the action mapping
    CheckoutAwaitAction:
      type: object
      allOf:
        - $ref: '#/components/schemas/PaymentResponseAction'
      properties:
        # CheckoutAwaitAction's own properties
      additionalProperties: false
    CheckoutBankTransferAction:
      type: object
      allOf:
        - $ref: '#/components/schemas/PaymentResponseAction'
      properties:
        # CheckoutBankTransferAction's own properties
      additionalProperties: false
    CheckoutDonationAction:
      type: object
      allOf:
        - $ref: '#/components/schemas/PaymentResponseAction'
      properties:
        # CheckoutDonationAction's own properties
      additionalProperties: false
    CheckoutOneTimePasscodeAction:
      type: object
      allOf:
        - $ref: '#/components/schemas/PaymentResponseAction'
      properties:
        # CheckoutOneTimePasscodeAction's own properties
      additionalProperties: false
    # ... rest of the action classes

这可以使用Swashuckle来完成。AspNetCore。注释包。根据API设计,您可以使用以下方法之一。

这种方法利用了在响应模式中使用其中一个的优势。这样做的目的是让Swashback生成一个响应模式,该模式将有一个:

responses:
  '200':
    description: Success
    content:
      application/json:
        schema:
          oneOf:
            - $ref: '#/components/schemas/CheckoutAwaitAction'
            - $ref: '#/components/schemas/CheckoutBankTransferAction'
            - $ref: '#/components/schemas/CheckoutDonationAction'
            - $ref: '#/components/schemas/CheckoutOneTimePasscodeAction'
            # ...

以下是您需要做的:

>

  • 向您的AddSwaggerGen调用添加UseOneOfForPolymorismSelectSubTypes使用选项;确保您的SelectSubTypes使用解析IPayment响应动作接口到您的API从控制器方法返回的所有所需实现:

    services.AddSwaggerGen(c =>
        {
        // ...
    
        c.UseOneOfForPolymorphism();
        c.SelectSubTypesUsing(baseType =>
        {
            if (baseType == typeof(IPaymentResponseAction))
            {
                return new[]
                {
                    typeof(CheckoutAwaitAction),
                    typeof(CheckoutBankTransferAction),
                    typeof(CheckoutDonationAction),
                    typeof(CheckoutOneTimePasscodeAction),
                    // ...
                };
            }
    
            return Enumerable.Empty<Type>();
        });
    
    

    向控制器方法添加SwaggerResponse注释。仅指定IPaymentResponseAction接口。

    [HttpGet]
    [SwaggerResponse((int)HttpStatusCode.OK, "response description", typeof(IPaymentResponseAction))]
    public IPaymentResponseAction GetPaymentAction()
    {
        // ...
    
    

    这将在Swagger UI中为您提供所需的模式:

    在Swagger-UI响应

    请注意,Swagger UI不支持“示例值”部分,如果模式有一个oneOf定义:它只会显示selectSubsubstancessing调用中第一个解析类型的响应样本。

    看起来不像是你的案子,但我还是想提一下。

    如果不同响应代码的响应模式不同,则可以直接在控制器中指定相应的类型:

    [HttpPost]
    [SwaggerResponse((int)HttpStatusCode.Created, "response description", typeof(CheckoutAwaitAction))]
    [SwaggerResponse((int)HttpStatusCode.OK, "response description", typeof(CheckoutBankTransferAction))]
    // ...
    public IPaymentResponseAction PostPaymentAction()
    {
        // ...
    
    

  •  类似资料:
    • 当我使用POST方法向我的服务器发出多部分请求时,请求工作正常。 下面是我如何设置我的JMeter: 发帖请求 然而,当我试图向同一个URL发出PUT请求时,该请求不起作用。 提交请求 服务器上的错误是 org.springframework.web.multipart.MultipartException:当前请求不是多部分请求 这似乎是JMeter上的一个问题,因为当我尝试使用Postman执

    • 我有以下类层次结构: 我正在尝试读取并锁定一个具体实例,以便其他事务无法读取它,使用hibernate。 现在的函数: 正在工作-为刷新操作生成“SELECT for UPDATE”语法。 refresh()和get()函数之间的不同之处在于get()函数使用外部左联接来选择具体对象,而refresh()使用内部联接来选择具体对象。 在悲观锁定的上下文中,这些连接之间有区别吗?

    • 我想设置pythonpath,但它不适用于其他目录。 我的朋友: 导出PYTHONPATH=/usr/lib/python2.7 导出PYTHONPATH=$PYTHONPATH/plat-linux2:$PYTHONPATH/lib-dynload:$PYTHONPATH/dist-包:$PYTHONPATH/lib-tk:$PYTHONPATH 如果我只保留第一行(单个目录) export P

    • 我正在weblogic 10.3中部署一个ear应用程序,其格式为分解格式,支持快速交换,并处于开发模式。ear文件还包含一个分解格式的web应用程序。对web应用程序中的JSP所做的更改正在重新加载。但更改后,不会重新加载web inf下的类。 weblogic部署配置如下所示。weblogic-application.xml的内容 应用ear/META-INF中的xml内容 战争中的weblo

    • 问题内容: 我想做的事: 我想计算一个使用多类问题 我试图做的是: 这是使用虹膜数据集制作的可复制示例。 我一个热编码我的目标 我使用决策树分类器 最后我执行交叉val 失败的原因: 最后一行抛出以下错误 我的环境: python == 3.7.2 sklearn == 0.19.2 我的问题: 是一个错误,还是我在误用? 问题答案: scikit-learn的交叉验证功能不必要的麻烦在于,默认情