2020年4月11日 星期六

[ASP.Net Core] Swagger - Show non-declared API parameters


 ASP.NET Core   Web API   Swagger   Parameter  








Introduction


Sometimes we don’t declare the API’s parameter(s) on API controller’s Action, but we still want them to be shown on Swagger (OpenAPI document).  This article shows how to implement Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter to get the the parameters from Attribute.


While we complete this tutorial, we can add the custom Attribute (e.q. IncludePaginParamsAttribute) on an API Action,

[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
        [HttpGet]
        [IncludePagingParams]
         public async Task<IActionResultGet()
        {
         }
}



And what we see in Swagger,




We can also add more parameters with OpenApiParamAttribute,

[HttpGet]
[IncludePagingParams]
[OpenApiParam("query""(Paging) query keyword")]
public async Task<IActionResultGet()
{
}


Result:





Related articles





Implement


Create OpenApiParam Attribute and OperationFilter

OpenApiParamAttribute.cs

The Attribute is used for parameter declaration on an API Action.

/// <summary>
/// OPEN API (Swagger) parameter attribute
/// </summary>
[AttributeUsage(AttributeTargets.MethodInherited = falseAllowMultiple = true)]
public class OpenApiParamAttribute : Attribute
{
        /// <summary>
        /// Construcotr
        /// </summary>
        /// <param name="name">Name</param>
        /// <param name="description">Description</param>
        /// <param name="dataType">Data type that can be: "integer","string","boolean"</param>
        public OpenApiParamAttribute(string namestring descriptionstring dataType = "string")
        {
            this.Name = name;
            this.Description = description;
            this.DataType = dataType;
        }

        /// <summary>
        /// Name
        /// </summary>
        public string Name { getprivate set; }

        /// <summary>
        /// Data type
        /// </summary>
        /// <value>DataTypes can be: "integer","string","boolean"</value>
        public string DataType { getset; }

        /// <summary>
        /// Parameter type
        /// </summary>
        /// <value>ParamType can be "path", "body", "query"</value>
        public string ParamType { getset; } = "query";

        /// <summary>
        /// Description
        /// </summary>
        public string Description { getprivate set; }

        /// <summary>
        /// Is required
        /// </summary>
        public bool Required { getset; } = false;
}



OpenApiParamAttributeFilter.cs

The OperationFilter which implements Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter is for binding the parameters from OpenApiParamAttribute to OpenAPI document.


/// <summary>
/// OPEN API (Swagger) parameter attribute's operation filter
/// </summary>
public class OpenApiParamAttributeFilter : IOperationFilter
{
        /// <summary>
        /// Apply operation
        /// </summary>
        /// <param name="operation">Operation</param>
        /// <param name="context">Context</param>
        public void Apply(Operation operationOperationFilterContext context)
        {
            var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
                                    .Union(context.MethodInfo.GetCustomAttributes(true))
                                    .OfType<OpenApiParamAttribute>();

            foreach (var attr in attributes)
            {
                operation.Parameters.Add(new NonBodyParameter
                {
                    Name = attr.Name,
                    Description = attr.Description,
                    In = attr.ParamType,
                    Required = attr.Required,
                    Type = attr.DataType
                });
            }
        }
}


Set custom OperationFilter to Swagger

Now we have to tell Swagger to use our custom OperationFilter: OpenApiParamAttributeFilter, by setting the Swagger options.


Startup.cs: ConfigureServices

Inside ConfigureServices method, inject Swagger and set the options by

SwaggerGenOptions.OperationFilter<T>()



public void ConfigureServices(IServiceCollection services)
{

#region API Document (Swagger)

        services.AddVersionedApiExplorer(options => options.GroupNameFormat = "'v'VVV");
        services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerConfig>();
        services.AddSwaggerGen(c =>
        {
                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = System.IO.Path.Combine(System.AppContext.BaseDirectoryxmlFile);
                c.IncludeXmlComments(xmlPath);

                // Set the custom operation filter
              c.OperationFilter<OpenApiParamAttributeFilter>();
        });
        #endregion

}



Ok, now we can try it!

Usage

[HttpGet]
        [Route("{id}")]
        [OpenApiParam("query""Query keyword")]
        [OpenApiParam("id""ID"ParamType = "path")]
        [OpenApiParam("model""My model"ParamType = "body")]
        public async Task<IActionResultGet()
        {
            return this.Ok();
        }


What we will see in Swagger,





One attribute from defining multiple parameters

Furthermore, we can create an Attribute to generate all the paramters if we don’t want to put so many OpenApiParamAttribute on an API Action.

For example, we have some pagination url parameters like below on all HttpGet APIs,

Parameter
Value Type
Param Type
Description
page
integer
query
(Paging) page
limit
integer
query
(Paging) page size
orderBy

query
(Paging) column name to be sorted
Ascending
integer
query
(Paging) 0: descending, 1: ascending

Since we don’t want to put all the information on the APIs, so we create an Attribute: IncludePagingParamsAttribute, to put the information inside it.


IncludePagingParamsAttribute.cs

/// <summary>
/// Include paging parameters attribute
/// </summary>
[AttributeUsage(AttributeTargets.MethodInherited = falseAllowMultiple = false)]
public class IncludePagingParamsAttribute : Attribute
{
        /// <summary>
        /// Constructor
        /// </summary>
        public IncludePagingParamsAttribute()
        {
            this.OpenApiDocs = new List<OpenApiDoc>
            {
                new OpenApiDoc("page""(Paging) page""integer"),
                new OpenApiDoc("limit""(Paging) page size""integer"),
                new OpenApiDoc("orderBy""(Paging) column name to be sorted-by"),
                new OpenApiDoc("ascending""(Paging) 0: descending, 1: ascending""integer")
            };
        }

        /// <summary>
        /// Open API documents
        /// </summary>
        public IList<OpenApiDocOpenApiDocs { getset; }
}




Now we can update the logic of the previous custom OperationFilter to make it can read multiple parameters from the above Attribute.


IncludePagingParamsAttribute.cs

/// <summary>
/// OPEN API (Swagger) parameter attribute's operation filter
/// </summary>
public class IncludePagingParamsAttributeFilter : IOperationFilter
{
        /// <summary>
        /// Apply operation
        /// </summary>
        /// <param name="operation">Operation</param>
        /// <param name="context">Context</param>
        public void Apply(Operation operationOperationFilterContext context)
        {
            var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
                                    .Union(context.MethodInfo.GetCustomAttributes(true))
                                    .OfType<IncludePagingParamsAttribute>();

            if (attributes != null && attributes.Count() > 0)
            {
                var attr = attributes.ToList()[0];

                foreach (var doc in attr.OpenApiDocs)
                {
                    operation.Parameters.Add(new NonBodyParameter
                    {
                        Name = doc.Name,
                        Description = doc.Description,
                        In = doc.ParamType,
                        Required = doc.Required,
                        Type = doc.DataType
                    });
                }
            }
        }
}



Usage

[HttpGet]
[IncludePagingParams]
public async Task<IActionResultGet()
{
            return this.Ok();
}



Result:




Reference


N/A







沒有留言:

張貼留言