简体   繁体   中英

How to vary the required fields for POST and PUT in Swashbuckle?

I'm responsible for maintaining the company API documentation. Our API is written ASP.NET. I recently switched to using Swashbuckle 5.6.0 which is working nicely.

The issue I've come accross is this:

We separate our data models into Post data and Get data, for example WebAccountGetData.cs and WebAccountPostData.cs . The Post data can be used when creating (POST) and updating (PUT).

Most, if not all, fields in the Post data classes are nullable, when an API method is called, the stored proc returns error messages describing what fields are missing/required. The API doesn't handle required fields.

Using nullable fields means Swashbuckle will not add a Required flag to the documentation. But we would like to show whether a field is required or not, based on the Http method used (Post/Put).

必要的例子
The API Key is a required parameter as it is not nullable.

I'm aware that I can use the [Required] attribute from the System.ComponentModel.DataAnnotations namespace, but this will apply the Required flag to both the POST and PUT methods, which we do not want.

Idealy, I'd like to use a custom Attribute where I can specify whether a field is required in the Post or Put method.

public class ApiRequiredAttribute : Attribute
{
  public bool RequiredInPost
  {
    get;
    set;
  } = false;

  public bool RequiredInPut
  {
    get;
    set;
  } = false;
}

And then use it like so:

[ApiRequired(RequiredInPost = true)]
public int? ApprovalStatusId
{
  get;
  set;
}

Is there a way to use a custom IDocumentFilter , IOperationFilter or ISchemaFilter to apply changes (like toggling the required flag) to the schema properties of a model field? Or is it not possible in Swashbuckle to reference Attributes on a model?

I've managed to find a solution!

I created an IOperationFilter that creates new schemas for POST and PUT methods, based on properties with my custom ApiRequired attribute (see original question).

internal class ApplyRequiredAttributeFilter : IOperationFilter
{
  public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription )
  {
    HttpParameterBinding[] parameterBindings = apiDescription.ActionDescriptor.ActionBinding.ParameterBindings;

    foreach ( HttpParameterBinding binding in parameterBindings ) {

      PropertyInfo[] properties = binding.Descriptor.ParameterType.GetProperties();

      // If the type is not an object and has no properties, ignore it.
      if ( properties.Length == 0 ) {
        continue;
      }

      Parameter modelParamater = operation.parameters.Last();
      if ( modelParamater == null ) {
        continue;
      }

      string schemaPath = modelParamater.schema?.@ref;
      string schemaName = schemaPath?.Split( '/' ).Last();
      if ( schemaName == null ) {
        continue;
      }

      Schema oldSchema = schemaRegistry.Definitions[ schemaName ];

      // Copy the existing schema.
      Schema newSchema = new Schema
      {
        description = oldSchema.description,
        properties = new Dictionary<string, Schema>( oldSchema.properties ),
        required = oldSchema.required != null ? new List<string>( oldSchema.required ) : new List<string>(),
        @type = oldSchema.type,
        vendorExtensions = new Dictionary<string, object>( oldSchema.vendorExtensions )
      };

      // Find model properties with the custom attribute.
      foreach ( PropertyInfo property in properties ) {
        ApiRequiredAttribute attribute = property.GetCustomAttribute<ApiRequiredAttribute>();

        if ( attribute != null ) {

          // If the model property is required in POST/PUT and current HTTP method is POST/PUT
          // Add the property to the new schema's required flags.
          if ( attribute.RequiredInPut && apiDescription.HttpMethod.Method.Equals( "PUT" ) ||
               attribute.RequiredInPost && apiDescription.HttpMethod.Method.Equals( "POST" ) ) {

            newSchema.required.Add( property.Name );

            string newSchemaName = $"{schemaName}:{apiDescription.HttpMethod.Method}";

            if ( !schemaRegistry.Definitions.ContainsKey( newSchemaName ) ) {
              schemaRegistry.Definitions.Add( newSchemaName, newSchema );
            }

            // Change the current model schema reference to the new schema with the addition required flags.
            modelParamater.schema.@ref = $"{schemaPath}:{apiDescription.HttpMethod.Method}";
          }
        }
      }
    }
  }
}

I then add the filter to my EnableSwagger call.

GlobalConfiguration.Configuration
                   .EnableSwagger("docs/swagger/", c =>
                                    {
                                      // Other initialization code... 
                                      c.OperationFilter<ApplyRequiredAttributeFilter>();
                                    });

The attributes are used like so:

[ApiRequired( RequiredInPost = true, RequiredInPut = true)]
public bool? Active
{
  get;
  set;
}

[ApiRequired( RequiredInPost = true )]
public string UserName
{
  get;
  set;
}

Finally, on the docs, the required flags look like this. The POST method parameters are on the left while the PUT method paramaters are on the right:

在此处输入图片说明 在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM