在生成 swagger 文件时,如何让 Swashbuckle 尊重数据类型?

How can I make Swashbuckle respect DataType when generating swagger files?

我正在使用以下数据协定的更复杂版本,但这足以作为示例:

using System;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

[DataContract(Namespace = "https://schemas.company.name/api-name/")]
public class Subscription
{
    [DataMember(IsRequired = true)]
    public long SubscriptionID { get; set; }

    [DataMember(IsRequired = true)]
    public long ProductID { get; set; }

    [DataMember(IsRequired = true)]
    public long AccountID { get; set; }

    [DataMember(IsRequired = true), DataType(DataType.Date)]
    public DateTime StartDate { get; set; }

    [DataMember(IsRequired = true), DataType(DataType.Date)]
    public DateTime EndDate { get; set; }
}

Swashbuckle 为上述数据契约生成的 swagger JSON 定义变成了这样:

...
    "Subscription": {
        "required": ["subscriptionID", "productID", "accountID", "startDate", "endDate"],
        "type": "object",
        "properties": {
            "subscriptionID": {
                "format": "int64",
                "type": "integer"
            },
            "productID": {
                "format": "int64",
                "type": "integer"
            },
            "accountID": {
                "format": "int64",
                "type": "integer"
            },
            "startDate": {
                "format": "date-time",
                "type": "string"
            },
            "endDate": {
                "format": "date-time",
                "type": "string"
            }
        }
    },
...

但是,您会注意到 JSON definitions.Subscription.properties.startDate.format"date-time",但 C# 代码中的 DateTypeAttribute 注释是 DataType.Date,而不是 DataType.DateTime.

如何在生成 swagger 文件时让 Swashbuckle 尊重 System.ComponentModel.DataAnnotations.DataTypeAttribute?或者更具体地说,使 [DataType(DataType.Date] 注释的 class 属性生成 format of "date"?

的 swagger format

我希望这是所有 classes 的默认行为,因为我有太多硬编码特定 class 属性的详细信息,这就是使用 Swashbuckle 生成的全部意义swagger JSON 基于同一命名空间内的其他注释(例如 System.ComponentModel.DataAnnotations.StringLengthAttribute)。

我最初的尝试是尝试在我的 Startup.cs 中使用 ISchemaFilter,例如:

    services.AddSwaggerGen(options =>
    {
        ...
        options.SchemaFilter<DataTypeSchemaFilter>();
        ...
    });

实施过滤器 class 的位置 应用:

public class DataTypeSchemaFilter : ISchemaFilter
{
    public void Apply(Schema model, SchemaFilterContext context)
    {
        ???
    }
}

但是,我发现无法使用提供的 Schema modelSchemaFilterContext context 参数从过滤器中调查 class 属性 属性。

如前所述,我知道 Swashbuckle 在处理 class 属性时会查看同一命名空间内的属性,因此我希望有人知道我可以在哪里绑定到 Swashbuckle 并执行类似的任务。

我写了下面的代码。从本质上讲,这个想法是将 class 上的 属性 名称连接到模式 属性 名称 (schema.properties)。鉴于您可能有自定义序列化程序设置(Camel Case),属性 名称的大小写在模式中可能与 class 中定义的不同。 我在 SetSchemaDetails 方法中也包含了父架构,以便您可以根据需要在父级添加属性。我们有偶尔使用的自定义必需属性,因此我们需要在父(包含 class)架构级别而不是 属性 架构级别指定必需属性。

public class DataAnnotationSchemaFilter : ISchemaFilter
{
    public void Apply(Schema schema, SchemaFilterContext schemaFilterContext)
    {
        var type = schemaFilterContext.SystemType;

        var propertyMappings = type
            .GetProperties()
            .Join(
                schema.Properties ?? new Dictionary<string, Schema>(),
                x => x.Name.ToLower(),
                x => x.Key.ToLower(),
                (x, y) => new KeyValuePair<PropertyInfo, KeyValuePair<string, Schema>>(x, y))
            .ToList();

        foreach (var propertyMapping in propertyMappings)
        {
            var propertyInfo = propertyMapping.Key;
            var propertyNameToSchemaKvp = propertyMapping.Value;

            foreach (var attribute in propertyInfo.GetCustomAttributes())
            {
                SetSchemaDetails(schema, propertyNameToSchemaKvp, propertyInfo, attribute);
            }
        }
    }

    private static void SetSchemaDetails(Schema parentSchema, KeyValuePair<string, Schema> propertyNameToSchemaKvp, PropertyInfo propertyInfo, object propertyAttribute)
    {
        var schema = propertyNameToSchemaKvp.Value;

        if (propertyAttribute is DataTypeAttribute)
        {
            var dataType = ((DataTypeAttribute)propertyAttribute).DataType;
            if (dataType == DataType.Date)
            {
                schema.Format = "date";
                schema.Type = "date";
            }
        }

        if (propertyAttribute is ReadOnlyAttribute)
        {
            schema.ReadOnly = ((ReadOnlyAttribute)propertyAttribute).IsReadOnly;
        }
    }
}