Swashbuckle:多态性不适用于外部 nuget 包
Swashbuckle: Polymorphism not working with external nuget package
在我们的 API 中,我们希望在用户调用端点时 return 来自外部 Nuget 包的对象。
这个对象(Can be viewed here) has a couple of properties. One of them is called Action
. This property has as type IPaymentResponseAction
but can be a set of different action types (You can see them all over here)。
生成的 swagger 不知道这些操作,也不会生成所需的代码。即使设置了多态性设置。
services.AddSwaggerGen(c =>
{
c.EnableAnnotations();
c.UseOneOfForPolymorphism();
});
有没有办法让这些对象在我的招摇中出现?也许有一些习惯 SwaggerGenOptions
?
使用 c.SelectSubTypesUsing
代码回答第一个问题后更新
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
}......
IPaymentResponseAction 是:
"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
}
更新答案
这是解决问题的更新答案:) 很抱歉 post。
您描述的问题是由于 Swashbuckle
缺乏处理 C# 接口以反映多态层次结构的能力引起的(对我来说似乎是缺少的功能)。
这是一个解决方法(参见 MVP 项目 here)。
第 1 步。Swashbuckle
个选项
c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true);
c.UseAllOfToExtendReferenceSchemas();
c.UseAllOfForInheritance();
c.UseOneOfForPolymorphism();
第 2 步。鉴别器选项
Swashbuckle
不将接口视为“父”类型。如果我们让它“认为”它仍在处理 class 而不是接口怎么办?介绍一下 PaymentResponseAction
class:
[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
};
});
第 3 步。allOf
实施中的关键字 classes
至此,几乎一切正常。唯一缺少的是实现 classes 的 allOf
关键字。目前,它不可能只使用 Swashbuckle
的选项,因为它在构造 allOf
.
时使用 BaseType 来解析子类型
和以前一样,我们可以 Swashbuckle
认为它处理继承类型。我们可以生成继承我们新 PaymentResponseAction
class 的“假”类型,并从我们感兴趣的实现类型中复制属性。这些“假”类型不必是功能性的;它们应该包含足够的类型信息以使 Swashbuckle
满意。
这是执行此操作的方法示例。它接受一个源类型来从一个基本类型和 returns 一个新类型复制属性。它还 copies custom attributes 可以很好地与 AddSwaggerGenNewtonsoftSupport
.
等依赖设置一起使用
请注意,应该改进此代码以使其可以投入生产;例如,它不应该“复制”具有 JsonIgnore
或类似属性的 public 属性。
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
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
调用中使用它来使 Swashbuckle
以我们想要的方式解析这些类型:
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))
};
});
结果
现在 Swashbuckle
应该可以正确生成所有内容:
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
上一个(不完整的)答案
这可以使用 Swashbuckle.AspNetCore.Annotations
package 来完成。根据 API 设计,您可以使用以下方法之一。
响应模式不依赖于响应代码
此方法利用了在响应模式中使用 oneOf 的优势。这个想法是让 Swashbuckle 生成一个具有 oneOf
:
的响应模式
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'
# ...
这是您需要做的:
将 UseOneOfForPolymorphism
和 SelectSubTypesUsing
选项添加到您的 AddSwaggerGen
调用中;确保您的 SelectSubTypesUsing
将 IPaymentResponseAction
接口解析为您的 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
:
中为您提供所需的架构
Response in Swagger-UI
请注意 Swagger-UI
如果架构具有 oneOf
定义,“示例值”部分:它将只显示第一个已解析类型的响应示例SelectSubTypesUsing
呼唤。
响应模式取决于响应代码
这看起来不像你的情况,但我仍然想将其作为一个选项提及。
如果不同的响应码响应模式不同,可以直接在controller中指定对应的类型:
[HttpPost]
[SwaggerResponse((int)HttpStatusCode.Created, "response description", typeof(CheckoutAwaitAction))]
[SwaggerResponse((int)HttpStatusCode.OK, "response description", typeof(CheckoutBankTransferAction))]
// ...
public IPaymentResponseAction PostPaymentAction()
{
// ...
在我们的 API 中,我们希望在用户调用端点时 return 来自外部 Nuget 包的对象。
这个对象(Can be viewed here) has a couple of properties. One of them is called Action
. This property has as type IPaymentResponseAction
but can be a set of different action types (You can see them all over here)。
生成的 swagger 不知道这些操作,也不会生成所需的代码。即使设置了多态性设置。
services.AddSwaggerGen(c =>
{
c.EnableAnnotations();
c.UseOneOfForPolymorphism();
});
有没有办法让这些对象在我的招摇中出现?也许有一些习惯 SwaggerGenOptions
?
使用 c.SelectSubTypesUsing
代码回答第一个问题后更新
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
}......
IPaymentResponseAction 是:
"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
}
更新答案
这是解决问题的更新答案:) 很抱歉 post。
您描述的问题是由于 Swashbuckle
缺乏处理 C# 接口以反映多态层次结构的能力引起的(对我来说似乎是缺少的功能)。
这是一个解决方法(参见 MVP 项目 here)。
第 1 步。Swashbuckle
个选项
c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true);
c.UseAllOfToExtendReferenceSchemas();
c.UseAllOfForInheritance();
c.UseOneOfForPolymorphism();
第 2 步。鉴别器选项
Swashbuckle
不将接口视为“父”类型。如果我们让它“认为”它仍在处理 class 而不是接口怎么办?介绍一下 PaymentResponseAction
class:
[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
};
});
第 3 步。allOf
实施中的关键字 classes
至此,几乎一切正常。唯一缺少的是实现 classes 的 allOf
关键字。目前,它不可能只使用 Swashbuckle
的选项,因为它在构造 allOf
.
和以前一样,我们可以 Swashbuckle
认为它处理继承类型。我们可以生成继承我们新 PaymentResponseAction
class 的“假”类型,并从我们感兴趣的实现类型中复制属性。这些“假”类型不必是功能性的;它们应该包含足够的类型信息以使 Swashbuckle
满意。
这是执行此操作的方法示例。它接受一个源类型来从一个基本类型和 returns 一个新类型复制属性。它还 copies custom attributes 可以很好地与 AddSwaggerGenNewtonsoftSupport
.
请注意,应该改进此代码以使其可以投入生产;例如,它不应该“复制”具有 JsonIgnore
或类似属性的 public 属性。
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
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
调用中使用它来使 Swashbuckle
以我们想要的方式解析这些类型:
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))
};
});
结果
现在 Swashbuckle
应该可以正确生成所有内容:
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
上一个(不完整的)答案
这可以使用 Swashbuckle.AspNetCore.Annotations
package 来完成。根据 API 设计,您可以使用以下方法之一。
响应模式不依赖于响应代码
此方法利用了在响应模式中使用 oneOf 的优势。这个想法是让 Swashbuckle 生成一个具有 oneOf
:
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'
# ...
这是您需要做的:
将
UseOneOfForPolymorphism
和SelectSubTypesUsing
选项添加到您的AddSwaggerGen
调用中;确保您的SelectSubTypesUsing
将IPaymentResponseAction
接口解析为您的 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
:
Response in Swagger-UI
请注意 Swagger-UI
oneOf
定义,“示例值”部分:它将只显示第一个已解析类型的响应示例SelectSubTypesUsing
呼唤。
响应模式取决于响应代码
这看起来不像你的情况,但我仍然想将其作为一个选项提及。
如果不同的响应码响应模式不同,可以直接在controller中指定对应的类型:
[HttpPost]
[SwaggerResponse((int)HttpStatusCode.Created, "response description", typeof(CheckoutAwaitAction))]
[SwaggerResponse((int)HttpStatusCode.OK, "response description", typeof(CheckoutBankTransferAction))]
// ...
public IPaymentResponseAction PostPaymentAction()
{
// ...