cri*_*mbo 6 c# asp.net-web-api swagger swashbuckle asp.net-core
在ASP.NET Core Web应用程序中使用Swashbuckle.AspNetCore,我们具有以下响应类型:
public class DateRange
{
[JsonConverter(typeof(IsoDateConverter))]
public DateTime StartDate {get; set;}
[JsonConverter(typeof(IsoDateConverter))]
public DateTime EndDate {get; set;}
}
Run Code Online (Sandbox Code Playgroud)
当使用Swashbuckle发出不拘一格的API JSON时,它将变为:
{ ...
"DateRange": {
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
这里的问题在于这DateTime是一个值类型,并且永远不能为null。但发出的Swagger API JSON不会将2个属性标记为required。所有其他值类型的行为相同:int,long,byte等-它们都被视为可选值。
为了完成图片,我们将Swagger API JSON馈送到dtsgenerator来为JSON响应模式生成Typescript接口。例如,上面的类变为:
export interface DateRange {
startDate?: string; // date-time
endDate?: string; // date-time
}
Run Code Online (Sandbox Code Playgroud)
这显然是不正确的。在深入研究这一点之后,我得出结论,dtsgenerator在使不需要的属性在打字稿中可为空方面做了正确的事情。也许摇摇欲坠的规范需要对nullable vs required的明确支持,但是现在将2混为一谈。
我知道我可以[Required]向每个value-type属性添加一个属性,但这跨越多个项目和数百个类,是冗余信息,必须维护。所有不可为null的值类型属性都不能为null,因此将它们表示为可选值似乎是不正确的。
Web API,Entity Framework和Json.net都知道值类型属性不能为null; 因此[Required],使用这些库时不需要属性。
我正在寻找一种方法来自动标记所有必需的非null值类型,以适应这种行为。
Dan*_*nez 18
如果您使用 C# 8.0+ 并启用了可为 Null 的引用类型,那么答案可能会更简单。假设所有不可为空的类型都是必需的,并且所有其他显式定义为可为空的类型都不是可接受的划分,则以下模式过滤器将起作用。
public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
{
/// <summary>
/// Add to model.Required all properties where Nullable is false.
/// </summary>
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
var additionalRequiredProps = model.Properties
.Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
.Select(x => x.Key);
foreach (var propKey in additionalRequiredProps)
{
model.Required.Add(propKey);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Apply 方法将循环检查每个模型属性以查看 Nullable 是否为 false 并将它们添加到所需对象的列表中。从观察来看,Swashbuckle 在根据 Nullable 属性是否可为 null 类型来设置 Nullable 属性方面做得很好。如果您不信任它,您始终可以使用反射来产生相同的效果。
与其他架构过滤器一样,不要忘记在 Startup 类中添加此过滤器以及适当的 Swashbuckle 扩展来处理可为 null 的对象。
services.AddSwaggerGen(c =>
{
/*...*/
c.SchemaFilter<RequireNonNullablePropertiesSchemaFilter>();
c.SupportNonNullableReferenceTypes(); // Sets Nullable flags appropriately.
c.UseAllOfToExtendReferenceSchemas(); // Allows $ref enums to be nullable
c.UseAllOfForInheritance(); // Allows $ref objects to be nullable
}
Run Code Online (Sandbox Code Playgroud)
我找到了一个解决方案:我能够实现实现此目的的Swashbuckle ISchemaFilter。实现是:
/// <summary>
/// Makes all value-type properties "Required" in the schema docs, which is appropriate since they cannot be null.
/// </summary>
/// <remarks>
/// This saves effort + maintenance from having to add <c>[Required]</c> to all value type properties; Web API, EF, and Json.net already understand
/// that value type properties cannot be null.
///
/// More background on the problem solved by this type: https://stackoverflow.com/questions/46576234/swashbuckle-make-non-nullable-properties-required </remarks>
public sealed class RequireValueTypePropertiesSchemaFilter : ISchemaFilter
{
private readonly CamelCasePropertyNamesContractResolver _camelCaseContractResolver;
/// <summary>
/// Initializes a new <see cref="RequireValueTypePropertiesSchemaFilter"/>.
/// </summary>
/// <param name="camelCasePropertyNames">If <c>true</c>, property names are expected to be camel-cased in the JSON schema.</param>
/// <remarks>
/// I couldn't figure out a way to determine if the swagger generator is using <see cref="CamelCaseNamingStrategy"/> or not;
/// so <paramref name="camelCasePropertyNames"/> needs to be passed in since it can't be determined.
/// </remarks>
public RequireValueTypePropertiesSchemaFilter(bool camelCasePropertyNames)
{
_camelCaseContractResolver = camelCasePropertyNames ? new CamelCasePropertyNamesContractResolver() : null;
}
/// <summary>
/// Returns the JSON property name for <paramref name="property"/>.
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private string PropertyName(PropertyInfo property)
{
return _camelCaseContractResolver?.GetResolvedPropertyName(property.Name) ?? property.Name;
}
/// <summary>
/// Adds non-nullable value type properties in a <see cref="Type"/> to the set of required properties for that type.
/// </summary>
/// <param name="model"></param>
/// <param name="context"></param>
public void Apply(Schema model, SchemaFilterContext context)
{
foreach (var property in context.SystemType.GetProperties())
{
string schemaPropertyName = PropertyName(property);
// This check ensures that properties that are not in the schema are not added as required.
// This includes properties marked with [IgnoreDataMember] or [JsonIgnore] (should not be present in schema or required).
if (model.Properties?.ContainsKey(schemaPropertyName) == true)
{
// Value type properties are required,
// except: Properties of type Nullable<T> are not required.
var propertyType = property.PropertyType;
if (propertyType.IsValueType
&& ! (propertyType.IsConstructedGenericType && (propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))))
{
// Properties marked with [Required] are already required (don't require it again).
if (! property.CustomAttributes.Any(attr =>
{
var t = attr.AttributeType;
return t == typeof(RequiredAttribute);
}))
{
// Make the value type property required
if (model.Required == null)
{
model.Required = new List<string>();
}
model.Required.Add(schemaPropertyName);
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
要使用,请在您的Startup班级中注册:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(c_swaggerDocumentName, new Info { Title = "Upfront API", Version = "1.0" });
c.SchemaFilter<RequireValueTypePropertiesSchemaFilter>(/*camelCasePropertyNames:*/ true);
});
Run Code Online (Sandbox Code Playgroud)
这导致上述DateRange类型变为:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(c_swaggerDocumentName, new Info { Title = "Upfront API", Version = "1.0" });
c.SchemaFilter<RequireValueTypePropertiesSchemaFilter>(/*camelCasePropertyNames:*/ true);
});
Run Code Online (Sandbox Code Playgroud)
在敏捷的JSON模式中,以及:
export interface DateRange {
startDate: string; // date-time
endDate: string; // date-time
}
Run Code Online (Sandbox Code Playgroud)
在dtsgenerator输出中。我希望这可以帮助其他人。
我能够使用以下模式过滤器和 Swashbuckle 5.4.1 达到与接受的答案相同的效果:
public class RequireValueTypePropertiesSchemaFilter : ISchemaFilter
{
private readonly HashSet<OpenApiSchema> _valueTypes = new HashSet<OpenApiSchema>();
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsValueType)
{
_valueTypes.Add(model);
}
if (model.Properties != null)
{
foreach (var prop in model.Properties)
{
if (_valueTypes.Contains(prop.Value))
{
model.Required.Add(prop.Key);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这依赖于这样一个事实,即 ISchemaFilter 必须应用于每个属性的简单模式,然后才能应用于包含这些属性的复杂模式 - 所以我们要做的就是跟踪与 ValueType 相关的简单模式,并且如果我们稍后遇到将这些 ValueType 模式之一作为属性的模式,我们可以根据需要标记该属性名称。
| 归档时间: |
|
| 查看次数: |
2564 次 |
| 最近记录: |