9

I am creating a public API that uses multiple private APIs (can not be accessed from outside). Business validations have been written for the private APIs and I do not want to re-write them for the public API. But I do want the swagger documentation to be the same.

That is why I wonder if I can mark property as mandatory, without using the Required attribute of ASP.NET. But that the swagger documentation indicates that it is mandatory. Is this possible?

Undeadparade
  • 1,512
  • 2
  • 14
  • 30
  • Related [upstream issue](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1224). – hlovdal Jan 31 '23 at 14:30

4 Answers4

10

Thanks to Mohsin, I solved my problem. The following I came up with, I created an attribute called SwaggerRequired. This attribute can be placed on any model. The AddSwaggerRequiredSchemaFilter then ensures that the Swagger documentation is modified. See below the code I wrote for this

A random model:

public class Foo
{
    [SwaggerRequired]
    public string FooBar{ get; set; }
}

The SwaggerRequiredAttribute:

[AttributeUsage(AttributeTargets.Property)] 
public class SwaggerRequiredAttribute : Attribute
{
}

And the AddSwaggerRequiredSchemaFilter to get it working:

public class AddSwaggerRequiredSchemaFilter : ISchemaFilter
{
    public void Apply(Swashbuckle.Swagger.Schema schema, SchemaRegistry schemaRegistry, Type type)
    {
        PropertyInfo[] properties = type.GetProperties();
        foreach (PropertyInfo property in properties)
        {
            var attribute = property.GetCustomAttribute(typeof(SwaggerRequiredAttribute));

            if (attribute != null)
            {
                var propertyNameInCamelCasing = char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1);

                if (schema.required == null)
                {
                    schema.required = new List<string>()
                    {
                        propertyNameInCamelCasing
                    };
                }
                else
                {
                    schema.required.Add(propertyNameInCamelCasing);
                }
            }
        }
    }
}
Undeadparade
  • 1,512
  • 2
  • 14
  • 30
  • nice solution. Have you found the way to set this attribute from the [Specification Extensions](https://swagger.io/specification/#specificationExtensions) ? To keep attribute after source code generation from the yml definition. – oleksa Jun 20 '19 at 14:35
9

Yes, it's possible. Add your custom class implementing IOperationFilter

public class UpdateParametersAsRequired : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry s, ApiDescription a)
    {
        if (operation.OperationId == "ControllerName_Action")
        {
            if (operation.Parameters != null)
            {
                foreach (var parameter in operation.Parameters)
                {
                    if (parameter.Name == "ParameterYouWantToEdit")
                    { 
                        // You can edit the properties here
                        parameter.Required = true;
                    }
                }
            }
            else
            {
              // Add parameters if doesn't exists any
                operation.Parameters = new List<IParameter>();
                operation.Parameters.Add(
                    new Parameter
                    {
                        name = "ParameterName",
                        @in = "body",
                        @default = "123",
                        type = "string",
                        description = "x y z",
                        required = true
                    }
                );
            }
        }
    }
}

Cheers!

Mohsin
  • 692
  • 1
  • 7
  • 15
1

I know its late. But someone else might get helped.

We can add [JsonRequired] over the properties which are needed to be required. Further, add the following code to suppress the validation error.

    [OnError]
    internal void OnError(StreamingContext context, ErrorContext errorContext)
    {
        errorContext.Handled = true;
    }

Over all, it would be like this:

    public class Model
    {
        [JsonRequired]
        public Property {get; set;}
        
        [OnError]
        internal void OnError(StreamingContext context, ErrorContext errorContext)
        {
            errorContext.Handled = true;
        }
    }
Mehawk
  • 31
  • 3
0

C#11 required keyword

Using C#11 required keyword, one can easily translate the constraint using this filter:

public class SwaggerRequiredSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (schema.Properties == null)
        {
            return;
        }

        var properties = context.Type.GetProperties();

        foreach (var schemProp in schema.Properties)
        {
            var codeProp =
                properties.SingleOrDefault(x => x.Name.ToCamelCase() == schemProp.Key)
                ?? throw new MissingFieldException(
                    $"Could not find property {schemProp.Key} in {context.Type}, or several names conflict."
                );

            var isRequired = Attribute.IsDefined(codeProp, typeof(RequiredMemberAttribute));
            if (isRequired)
            {
                schemProp.Value.Nullable = false;
                _ = schema.Required.Add(schemProp.Key);
            }
        }
    }
}
Alexandre Daubricourt
  • 3,323
  • 1
  • 34
  • 33