Swashbuckle:使不可为空的属性成为必需的
Swashbuckle: Make non-nullable properties required
在 ASP.NET 核心网络应用程序中使用 Swashbuckle.AspNetCore,我们的响应类型如下:
public class DateRange
{
[JsonConverter(typeof(IsoDateConverter))]
public DateTime StartDate {get; set;}
[JsonConverter(typeof(IsoDateConverter))]
public DateTime EndDate {get; set;}
}
当使用 Swashbuckle 发出招摇 API JSON 时,这变成:
{ ...
"DateRange": {
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
}
...
}
这里的问题是DateTime
是一个值类型,永远不能为null;但是发出的 Swagger API JSON 没有将 2 个属性标记为 required
。此行为对于所有其他值类型都是相同的:int、long、byte 等 - 它们都被认为是可选的。
为了完成这幅画,我们将我们的 Swagger API JSON 提供给 dtsgenerator 来为 JSON 响应模式生成打字稿接口。例如上面的 class 变成:
export interface DateRange {
startDate?: string; // date-time
endDate?: string; // date-time
}
这显然是不正确的。在稍微深入研究之后,我得出结论,dtsgenerator 在使打字稿中的非必需属性可为 null 方面做的是正确的。也许 swagger 规范需要明确支持 nullable 与 required,但目前这两者是混为一谈的。
我知道我可以向每个值类型 属性 添加一个 [Required]
属性,但这跨越多个项目和数百个 classes,是冗余信息,并且必须维护。所有不可为 null 的值类型属性都不能为 null,因此将它们表示为可选似乎是不正确的。
WebAPI、Entity Framework、Json.net都明白值类型属性不能是null
;因此使用这些库时不需要 [Required]
属性。
我正在寻找一种方法来自动标记所有不可为 null 的值类型,以符合我的 swagger JSON 的要求,以匹配此行为。
我找到了一个解决方案:我能够实现一个 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: </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);
}
}
}
}
}
}
要使用,请在您的 Startup
class 中注册:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(c_swaggerDocumentName, new Info { Title = "Upfront API", Version = "1.0" });
c.SchemaFilter<RequireValueTypePropertiesSchemaFilter>(/*camelCasePropertyNames:*/ true);
});
这导致上面的 DateRange
类型变为:
{ ...
"DateRange": {
"required": [
"startDate",
"endDate"
],
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
},
...
}
在 swagger JSON 架构中,并且:
export interface DateRange {
startDate: string; // date-time
endDate: string; // date-time
}
在 dtsgenerator 输出中。我希望这对其他人有帮助。
或者你可以试试这个
public class AssignPropertyRequiredFilter : ISchemaFilter {
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type) {
var requiredProperties = type.GetProperties()
.Where(x => x.PropertyType.IsValueType)
.Select(t => char.ToLowerInvariant(t.Name[0]) + t.Name.Substring(1));
if (schema.required == null) {
schema.required = new List<string>();
}
schema.required = schema.required.Union(requiredProperties).ToList();
}
}
并使用
services.AddSwaggerGen(c =>
{
...
c.SchemaFilter<AssignPropertyRequiredFilter>();
});
让我根据 json 模式提出解决方案。
此方案在 RFC 中有描述,因此它应该像通用解决方案一样工作 https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1
public class AssignPropertyRequiredFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Properties == null || schema.Properties.Count == 0)
{
return;
}
var typeProperties = context.SystemType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in schema.Properties)
{
if (IsSourceTypePropertyNullable(typeProperties, property.Key))
{
continue;
}
// "null", "boolean", "object", "array", "number", or "string"), or "integer" which matches any number with a zero fractional part.
// see also: https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1
switch (property.Value.Type)
{
case "boolean":
case "integer":
case "number":
AddPropertyToRequired(schema, property.Key);
break;
case "string":
switch (property.Value.Format)
{
case "date-time":
case "uuid":
AddPropertyToRequired(schema, property.Key);
break;
}
break;
}
}
}
private bool IsNullable(Type type)
{
return Nullable.GetUnderlyingType(type) != null;
}
private bool IsSourceTypePropertyNullable(PropertyInfo[] typeProperties, string propertyName)
{
return typeProperties.Any(info => info.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)
&& IsNullable(info.PropertyType));
}
private void AddPropertyToRequired(Schema schema, string propertyName)
{
if (schema.Required == null)
{
schema.Required = new List<string>();
}
if (!schema.Required.Contains(propertyName))
{
schema.Required.Add(propertyName);
}
}
}
我能够使用以下架构过滤器和 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);
}
}
}
}
}
这依赖于 ISchemaFilter 必须先应用于每个 属性 的简单模式,然后才能应用于包含这些属性的复杂模式 - 因此我们要做的就是保持跟踪与 ValueType 相关的简单模式的一部分,如果我们以后遇到一个具有这些 ValueType 模式之一的模式 属性,我们可以根据需要标记该 属性 名称。
如果您使用的是 C# 8.0+ 并启用了可空引用类型,那么答案会更简单。假设所有不可为 null 的类型都是必需的并且所有其他明确定义为可为 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);
}
}
}
Apply 方法将遍历每个模型 属性 检查 Nullable 是否为 false 并将它们添加到所需对象列表中。从观察来看,Swashbuckle 似乎在根据 Nullable 属性 是否为可空类型方面做得很好。如果您不相信它,您可以随时使用反射来产生相同的效果。
与其他模式过滤器一样,不要忘记在您的 Startup class 中添加这个过滤器以及适当的 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
}
在我意识到两件重要的事情之前,我为类似的问题苦苦挣扎了好几天。
- 属性 的可空性及其必要性是完全正交的概念,不应混淆。
- 尽管 C# 的新可空特性可以帮助您避免空引用异常,但它仍然是一个编译时特性。就 CLR 而言,因此就反射 API 而言,所有字符串(实际上是所有引用类型)始终可为空。期间。
第二点确实对我编写的任何模式过滤器造成了问题,因为无论我输入的内容是 string
还是 string?
,[=18] 的 context
参数=] 函数总是将 MemberInfo.Nullable
属性 设置为 true
.
所以我想到了以下解决方案。
首先,创建 Nullable
属性。
using System;
[AttributeUsage(AttributeTargets.Property)]
public class NullableAttribute : Attribute {
public NullableAttribute(bool Property = true, bool Items = false) {
this.Property = Property;
this.Items = Items;
}
public bool Property { get; init; }
public bool Items { get; init; }
}
接下来,创建 NullableSchemaFilter
.
using MicroSearch.G4Data.Models;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class NullableSchemaFilter : ISchemaFilter {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
var attrs = context.MemberInfo?.GetInlineAndMetadataAttributes();
if (attrs != null) {
foreach (var attr in attrs) {
var nullableAttr = attr as NullableAttribute;
if (nullableAttr != null) {
schema.Nullable = nullableAttr.Property;
if (schema.Items != null)
schema.Items.Nullable = nullableAttr.Items;
}
}
}
}
}
当然,您必须在启动代码中添加架构过滤器。
services.AddSwaggerGen(config => {
config.SchemaFilter<NullableSchemaFilter>();
});
Nullable 属性有两个可选的布尔参数:
Property
控制 属性 本身是否可为空。
Items
控制数组中的项目是否可为空。显然,这仅适用于数组属性。
示例:
// these all express a nullable string
string? Name { get; set; }
[Nullable] string? Name { get; set; }
[Nullable(true)] string? Name { get; set; }
[Nullable(Property: true)] string? Name { get; set; }
// non-nullable string
[Nullable(false)] string Name { get; set; }
[Nullable(Property: false)] string Name { get; set; }
// non-nullable array of non-nullable strings
[Nullable(false)] string[] Names { get; set; }
[Nullable(Property: false, Items: false) Names { get; set; }
// nullable array of non-nullable strings
[Nullable(Property: true, Items: false)] string[]? Names { get; set; }
// non-nullable array of nullable strings
[Nullable(Property: false, Items: true)] string?[] Names { get; set; }
// nullable array of nullable strings
[Nullable(Property: true, Items: true)] string?[]? Names { get; set; }
[Required]
属性可以在必要时与 [Nullable]
属性一起自由使用。也就是说,这符合您的预期。
[Nullable][Required] string? Name { get; set; }
我正在使用 .NET 5 和 Swashbuckle.AspNetCore 6.2.3.
在 ASP.NET 核心网络应用程序中使用 Swashbuckle.AspNetCore,我们的响应类型如下:
public class DateRange
{
[JsonConverter(typeof(IsoDateConverter))]
public DateTime StartDate {get; set;}
[JsonConverter(typeof(IsoDateConverter))]
public DateTime EndDate {get; set;}
}
当使用 Swashbuckle 发出招摇 API JSON 时,这变成:
{ ...
"DateRange": {
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
}
...
}
这里的问题是DateTime
是一个值类型,永远不能为null;但是发出的 Swagger API JSON 没有将 2 个属性标记为 required
。此行为对于所有其他值类型都是相同的:int、long、byte 等 - 它们都被认为是可选的。
为了完成这幅画,我们将我们的 Swagger API JSON 提供给 dtsgenerator 来为 JSON 响应模式生成打字稿接口。例如上面的 class 变成:
export interface DateRange {
startDate?: string; // date-time
endDate?: string; // date-time
}
这显然是不正确的。在稍微深入研究之后,我得出结论,dtsgenerator 在使打字稿中的非必需属性可为 null 方面做的是正确的。也许 swagger 规范需要明确支持 nullable 与 required,但目前这两者是混为一谈的。
我知道我可以向每个值类型 属性 添加一个 [Required]
属性,但这跨越多个项目和数百个 classes,是冗余信息,并且必须维护。所有不可为 null 的值类型属性都不能为 null,因此将它们表示为可选似乎是不正确的。
WebAPI、Entity Framework、Json.net都明白值类型属性不能是null
;因此使用这些库时不需要 [Required]
属性。
我正在寻找一种方法来自动标记所有不可为 null 的值类型,以符合我的 swagger JSON 的要求,以匹配此行为。
我找到了一个解决方案:我能够实现一个 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: </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);
}
}
}
}
}
}
要使用,请在您的 Startup
class 中注册:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(c_swaggerDocumentName, new Info { Title = "Upfront API", Version = "1.0" });
c.SchemaFilter<RequireValueTypePropertiesSchemaFilter>(/*camelCasePropertyNames:*/ true);
});
这导致上面的 DateRange
类型变为:
{ ...
"DateRange": {
"required": [
"startDate",
"endDate"
],
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
},
...
}
在 swagger JSON 架构中,并且:
export interface DateRange {
startDate: string; // date-time
endDate: string; // date-time
}
在 dtsgenerator 输出中。我希望这对其他人有帮助。
或者你可以试试这个
public class AssignPropertyRequiredFilter : ISchemaFilter {
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type) {
var requiredProperties = type.GetProperties()
.Where(x => x.PropertyType.IsValueType)
.Select(t => char.ToLowerInvariant(t.Name[0]) + t.Name.Substring(1));
if (schema.required == null) {
schema.required = new List<string>();
}
schema.required = schema.required.Union(requiredProperties).ToList();
}
}
并使用
services.AddSwaggerGen(c =>
{
...
c.SchemaFilter<AssignPropertyRequiredFilter>();
});
让我根据 json 模式提出解决方案。 此方案在 RFC 中有描述,因此它应该像通用解决方案一样工作 https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1
public class AssignPropertyRequiredFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Properties == null || schema.Properties.Count == 0)
{
return;
}
var typeProperties = context.SystemType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in schema.Properties)
{
if (IsSourceTypePropertyNullable(typeProperties, property.Key))
{
continue;
}
// "null", "boolean", "object", "array", "number", or "string"), or "integer" which matches any number with a zero fractional part.
// see also: https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1
switch (property.Value.Type)
{
case "boolean":
case "integer":
case "number":
AddPropertyToRequired(schema, property.Key);
break;
case "string":
switch (property.Value.Format)
{
case "date-time":
case "uuid":
AddPropertyToRequired(schema, property.Key);
break;
}
break;
}
}
}
private bool IsNullable(Type type)
{
return Nullable.GetUnderlyingType(type) != null;
}
private bool IsSourceTypePropertyNullable(PropertyInfo[] typeProperties, string propertyName)
{
return typeProperties.Any(info => info.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)
&& IsNullable(info.PropertyType));
}
private void AddPropertyToRequired(Schema schema, string propertyName)
{
if (schema.Required == null)
{
schema.Required = new List<string>();
}
if (!schema.Required.Contains(propertyName))
{
schema.Required.Add(propertyName);
}
}
}
我能够使用以下架构过滤器和 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);
}
}
}
}
}
这依赖于 ISchemaFilter 必须先应用于每个 属性 的简单模式,然后才能应用于包含这些属性的复杂模式 - 因此我们要做的就是保持跟踪与 ValueType 相关的简单模式的一部分,如果我们以后遇到一个具有这些 ValueType 模式之一的模式 属性,我们可以根据需要标记该 属性 名称。
如果您使用的是 C# 8.0+ 并启用了可空引用类型,那么答案会更简单。假设所有不可为 null 的类型都是必需的并且所有其他明确定义为可为 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);
}
}
}
Apply 方法将遍历每个模型 属性 检查 Nullable 是否为 false 并将它们添加到所需对象列表中。从观察来看,Swashbuckle 似乎在根据 Nullable 属性 是否为可空类型方面做得很好。如果您不相信它,您可以随时使用反射来产生相同的效果。
与其他模式过滤器一样,不要忘记在您的 Startup class 中添加这个过滤器以及适当的 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
}
在我意识到两件重要的事情之前,我为类似的问题苦苦挣扎了好几天。
- 属性 的可空性及其必要性是完全正交的概念,不应混淆。
- 尽管 C# 的新可空特性可以帮助您避免空引用异常,但它仍然是一个编译时特性。就 CLR 而言,因此就反射 API 而言,所有字符串(实际上是所有引用类型)始终可为空。期间。
第二点确实对我编写的任何模式过滤器造成了问题,因为无论我输入的内容是 string
还是 string?
,[=18] 的 context
参数=] 函数总是将 MemberInfo.Nullable
属性 设置为 true
.
所以我想到了以下解决方案。
首先,创建 Nullable
属性。
using System;
[AttributeUsage(AttributeTargets.Property)]
public class NullableAttribute : Attribute {
public NullableAttribute(bool Property = true, bool Items = false) {
this.Property = Property;
this.Items = Items;
}
public bool Property { get; init; }
public bool Items { get; init; }
}
接下来,创建 NullableSchemaFilter
.
using MicroSearch.G4Data.Models;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class NullableSchemaFilter : ISchemaFilter {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
var attrs = context.MemberInfo?.GetInlineAndMetadataAttributes();
if (attrs != null) {
foreach (var attr in attrs) {
var nullableAttr = attr as NullableAttribute;
if (nullableAttr != null) {
schema.Nullable = nullableAttr.Property;
if (schema.Items != null)
schema.Items.Nullable = nullableAttr.Items;
}
}
}
}
}
当然,您必须在启动代码中添加架构过滤器。
services.AddSwaggerGen(config => {
config.SchemaFilter<NullableSchemaFilter>();
});
Nullable 属性有两个可选的布尔参数:
Property
控制 属性 本身是否可为空。Items
控制数组中的项目是否可为空。显然,这仅适用于数组属性。
示例:
// these all express a nullable string
string? Name { get; set; }
[Nullable] string? Name { get; set; }
[Nullable(true)] string? Name { get; set; }
[Nullable(Property: true)] string? Name { get; set; }
// non-nullable string
[Nullable(false)] string Name { get; set; }
[Nullable(Property: false)] string Name { get; set; }
// non-nullable array of non-nullable strings
[Nullable(false)] string[] Names { get; set; }
[Nullable(Property: false, Items: false) Names { get; set; }
// nullable array of non-nullable strings
[Nullable(Property: true, Items: false)] string[]? Names { get; set; }
// non-nullable array of nullable strings
[Nullable(Property: false, Items: true)] string?[] Names { get; set; }
// nullable array of nullable strings
[Nullable(Property: true, Items: true)] string?[]? Names { get; set; }
[Required]
属性可以在必要时与 [Nullable]
属性一起自由使用。也就是说,这符合您的预期。
[Nullable][Required] string? Name { get; set; }
我正在使用 .NET 5 和 Swashbuckle.AspNetCore 6.2.3.