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<IActionResult> Get()
{
}
}
And what we
see in Swagger,
We can also
add more parameters with OpenApiParamAttribute,
[HttpGet]
[IncludePagingParams]
[OpenApiParam("query", "(Paging) query keyword")]
public async Task<IActionResult> Get()
{
}
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.Method, Inherited = false, AllowMultiple = 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 name, string description, string dataType = "string")
{
this.Name = name;
this.Description = description;
this.DataType = dataType;
}
/// <summary>
/// Name
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Data type
/// </summary>
/// <value>DataTypes can be: "integer","string","boolean"</value>
public string DataType { get; set; }
/// <summary>
/// Parameter type
/// </summary>
/// <value>ParamType can be "path", "body", "query"</value>
public string ParamType { get; set; } = "query";
/// <summary>
/// Description
/// </summary>
public string Description { get; private set; }
/// <summary>
/// Is required
/// </summary>
public bool Required { get; set; } = 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 operation, OperationFilterContext 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>()
For example, (Reference: [ASP.Net
Core] Integrate API Versioning on Swagger)
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.BaseDirectory, xmlFile);
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<IActionResult> Get()
{
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.Method, Inherited = false, AllowMultiple = 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<OpenApiDoc> OpenApiDocs { get; set; }
}
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 operation, OperationFilterContext 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<IActionResult> Get()
{
return this.Ok();
}
Result:
▌Reference
▋N/A
沒有留言:
張貼留言