Sybeus
Sybeus

Reputation: 1189

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

I am working with a more complex version of the following data contract, but this should be sufficient as an example:

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

The swagger JSON definition generated by Swashbuckle for the above data contract becomes this:

...
    "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"
            }
        }
    },
...

However, you'll notice the JSON definitions.Subscription.properties.startDate.format is "date-time", but the DateTypeAttribute annotation in the C# code is DataType.Date, not DataType.DateTime.

How can I make Swashbuckle respect System.ComponentModel.DataAnnotations.DataTypeAttribute when generating swagger files? Or more specifically, make class properties annotated with [DataType(DataType.Date] generate a swagger format of "date"?

I wish to have this be the default behaviour for all classes, as I have too many to hard code details of specific class properties and is the whole point of using Swashbuckle to generate swagger JSON based on other annotations from within the same namespace (such as System.ComponentModel.DataAnnotations.StringLengthAttribute).

My initial attempt was to try using an ISchemaFilter in my Startup.cs, such as:

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

Where the filter class implemented Apply:

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

However, I see no ability to investigate the class property attributes from within the filter using the provided Schema model and SchemaFilterContext context parameters.

As mentioned previously, I know Swashbuckle looks at attributes from within the same namespace when processing the class properties, so I'm hoping someone knows where I can tie into Swashbuckle and perform a similar task.

Upvotes: 1

Views: 5524

Answers (1)

fbhdev
fbhdev

Reputation: 526

I've written the code below. Essentially, the idea is that you are joining the property names on the class to the schema property names (schema.properties). Given that you might have custom serializer settings (Camel Case), the casing for property names can be different in the schema than as defined in the class. I included the parent schema as well in the SetSchemaDetails method so that you can add attributes at the parent level if needed. We have custom required attributes that we use occasionally, so we need to specify required properties at the parent (enclosing class) schema level rather than at the property schema level.

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

Upvotes: 3

Related Questions