2017-10-05 61 views
2

在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中的要求来匹配此行为。

回答

0

我找到了一个解决方案:我能够实现一个窍门的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); 
        } 
       } 
      } 
     } 
    } 
} 

要使用,在你的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输出中。我希望这可以帮助别人。

0

或者你可以试试这个

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>(); 
});