2017-10-05 3 views
2

L'utilisation Swashbuckle.AspNetCore dans un ASP.NET de base webapp, nous avons des types de réponse comme:Swashbuckle: Faire des propriétés non-nullables nécessaire

public class DateRange 
{ 
    [JsonConverter(typeof(IsoDateConverter))] 
    public DateTime StartDate {get; set;} 

    [JsonConverter(typeof(IsoDateConverter))] 
    public DateTime EndDate {get; set;} 
} 

Lorsque vous utilisez Swashbuckle pour émettre l'API JSON fanfaronnades, cela devient:

{ ... 

    "DateRange": { 
    "type": "object", 
    "properties": { 
     "startDate": { 
     "format": "date-time", 
     "type": "string" 
     }, 
     "endDate": { 
     "format": "date-time", 
     "type": "string" 
     } 
    } 
    } 
... 
} 

Le problème ici est que DateTime est un type de valeur, et ne peut jamais être nul; mais l'API Swagger JSON émise ne marque pas les 2 propriétés en tant que required. Ce comportement est le même pour tous les autres types de valeurs: int, long, byte, etc - ils sont tous considérés comme optionnels.

Pour compléter l'image, nous fournissons notre Swagger API JSON à dtsgenerator pour générer des interfaces de type dactylographié pour le schéma de réponse JSON. par exemple. la classe ci-dessus devient:

export interface DateRange { 
    startDate?: string; // date-time 
    endDate?: string; // date-time 
} 

Ce qui est manifestement erronée. Après avoir creusé cela un peu, j'ai conclu que dtsgenerator fait ce qui est juste en rendant les propriétés non-obligatoires nullables en tapuscrit. Peut-être que la spécification swagger nécessite un support explicite pour nullable vs requis, mais pour l'instant les 2 sont confondus. Je suis conscient que je peux ajouter un attribut [Required] à chaque propriété de type valeur, mais il couvre plusieurs projets et des centaines de classes, est redondant et doit être conservé. Toutes les propriétés de type de valeur non nullable ne peuvent pas être nulles, il semble donc incorrect de les représenter comme facultatives.

L'API Web, Entity Framework et Json.net comprennent tous que les propriétés de type de valeur ne peuvent pas être null; un attribut [Required] n'est donc pas nécessaire lors de l'utilisation de ces bibliothèques.

Je suis à la recherche d'un moyen de marquer automatiquement tous les types de valeur non-NULL comme requis dans mon Jagger Swagger pour correspondre à ce comportement.

Répondre

0

J'ai trouvé une solution pour cela: j'ai pu implémenter un Swashbuckle ISchemaFilter qui fait l'affaire. La mise en œuvre est:

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

Pour utiliser, enregistrez-le dans votre Startup classe:

Il en résulte du type DateRange ci-dessus devient:

{ ... 
    "DateRange": { 
    "required": [ 
     "startDate", 
     "endDate" 
    ], 
    "type": "object", 
    "properties": { 
     "startDate": { 
     "format": "date-time", 
     "type": "string" 
     }, 
     "endDate": { 
     "format": "date-time", 
     "type": "string" 
     } 
    } 
    }, 
    ... 
} 

Dans le fanfaronnades schéma JSON, et :

export interface DateRange { 
    startDate: string; // date-time 
    endDate: string; // date-time 
} 

dans la sortie dtsgenerator. J'espère que ça aidera quelqu'un d'autre.

0

Ou vous pouvez essayer celui-ci

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

et utiliser

services.AddSwaggerGen(c => 
{ 
    ... 
    c.SchemaFilter<AssignPropertyRequiredFilter>(); 
});