RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.71k stars 1.24k forks source link

Property annotations do not generate jsonschema if attributes exists on MetadataType buddy class #4543

Open JeffBarnard opened 11 months ago

JeffBarnard commented 11 months ago

I am using ef-core database first tools (efpt) to generate portable POCO models. I am then extending these classes by adding metadata 'buddy classes' to annotate them for validation rules.

public partial class Model 
{
    public int Id { get; set; }
}

[MetadataType(typeof(ModelMeta))]   
public partial class Model
{
    public class ModelMeta
    {       
        [Range(2,5)]
        public int Id { get; set; }
    }
}

You can also use interfaces:

[MetadataType(typeof(IModelMeta))]   
public partial class Model : IModelMeta
{       
}

public interface IModelMeta
{
    [Range(2,5)]
    int Id { get; set; }
}

Annotations do not work and output the expected jsonschema. Here are some examples I've tried:

[Required]
[Range(2,5)]
[ReadOnly(true)]
[DefaultValue(typeof(int), "1")]

They do work if the annotations are on the original partial class definition, but I cannot do this since they are tool-generated.

Thanks for reading.

JeffBarnard commented 11 months ago

When using the .net ValidationContext to validate our objects, the metadata classes need to be registered in order for that api to detect them. I don't know why they require us to do this, but perhaps this is a useful reference:

foreach (MetadataTypeAttribute attrib in type.GetCustomAttributes(typeof(MetadataTypeAttribute), true))
{
    System.ComponentModel.TypeDescriptor.AddProviderTransparent(
        new AssociatedMetadataTypeTypeDescriptionProvider(type, attrib.MetadataClassType), type);
}
JeffBarnard commented 11 months ago

We've worked around this issue by simply writing code to detect the attributes and applying/mapping them to the schema manually. This means we must maintain our own list of supported attributes so it's not great.

Edited for brevity:

public static void ApplyMetadataToJson(JsonSchema schema, Type type)
{
    var metadataType = type.GetCustomAttribute<MetadataTypeAttribute>()?.MetadataClassType;
    var properties = metadataType?.GetProperties();
    foreach (var property in properties)
    {
        var attributes = property.GetCustomAttributes(true);
        var schemaProperty = schema.Properties[property.Name];

        foreach (var attribute in attributes)
        {
            switch (attribute)
            {
                case RequiredAttribute requiredAtrribute:
                    schemaProperty.IsRequired = true;
                    break;                          
                case DescriptionAttribute descriptionAttribute:
                    schemaProperty.Description = descriptionAttribute.Description;
                    break;
               ...
            }
        }        
    }
}