Swashbuckle:请非空的属性需要
在ASP.NET Web应用程序的核心使用Swashbuckle.AspNetCore,我们有响应类型,如:Swashbuckle:请非空的属性需要
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
是一个值类型,并且永远不能为空;但是发出的Swagger API JSON不会将这两个属性标记为required
。对于所有其他值类型,这种行为是相同的:int,long,byte等 - 它们都被认为是可选的。
要完成此图,我们正在将我们的Swagger API JSON提供给dtsgenerator以生成用于JSON响应模式的typescript接口。例如上面的类变为:
export interface DateRange {
startDate?: string; // date-time
endDate?: string; // date-time
}
这显然是不正确。在深入研究这一点之后,我已经得出结论认为dtsgenerator正在做正确的事情,使打字稿中的非必需属性可以为空。也许swagger规范需要明确支持可空对比,但现在这两个是混合的。
我知道我可以为每个值类型属性添加一个[Required]
属性,但是这涵盖了多个项目和数百个类,是冗余信息,并且必须保留。所有不可为空的值类型属性不能为null,因此将它们表示为可选项似乎不正确。
Web API,Entity Framework和Json.net都知道值类型属性不能是null
;因此使用这些库时不需要[Required]
属性。
我正在寻找一种方法来自动标记所有不可为空的值类型,如我在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: https://*.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);
}
}
}
}
}
}
要使用,在你的Startup
类进行注册:
这将导致DateRange
类型上面成为:
{ ...
"DateRange": {
"required": [
"startDate",
"endDate"
],
"type": "object",
"properties": {
"startDate": {
"format": "date-time",
"type": "string"
},
"endDate": {
"format": "date-time",
"type": "string"
}
}
},
...
}
在招摇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>();
});