In this article, we are going to discuss the Web API Analyzers in ASP.NET Core.
Let’s get going.
What are Web API Analyzers?
ASP.NET Core Web API supports OpenAPI documentation with Swagger, which describes the status codes and response types that a controller action may return. Web API Analyzers help us to improve the documentation one step further by enabling us to follow a set of conventions.
Once we annotate a Web API controller with the ApiController attribute, the analyzers will inspect it and identify the actions that don’t entirely document their responses. In other words, the Web API Analyzers report the missing documentation and provide us suggestions to fix it.
Now we’re going to see the Web API Analyzers in action. Before we do that, let’s understand what problem analyzers solve.
What Problem do Web API Analyzers Solve?
Let’s start by creating an ASP.NET Core Web API Controller called EmployeeController
and creating the GetById()
action method:
[Route("api/[controller]")] [ApiController] public class EmployeeController : ControllerBase { public IFakeRepository _repository; public EmployeeController(IFakeRepository repository) { _repository = repository; } [HttpGet("{id}")] public IActionResult GetById(int id) { if (id < 1) { return BadRequest(); } if (!_repository.TryGetEmployee(id, out var employee)) { return NotFound(); } return Ok(employee); } }
Here, the GetById()
action method can have three types of responses. If the Id
is invalid, it can return a 400 BadRequest response. On the other hand, if there is no employee corresponding to the specified Id
, it can return a 404 NotFound response. Finally, once it finds an employee with the specified Id
, it returns a 200 Ok response with the employee details. However, notice that we have not documented the response types for this action method.
Now let’s run the application and observe the output in Swagger:
The Swagger documentation describes just one response corresponding to the success status.
Even though the action method can return other response types, why are they missing in the Swagger documentation? This is because we missed adding the [ProducesResponseType]
attribute to the action, hence Swagger is unaware of the other responses that the action can produce. This is where the Web API analyzers can help us. Analyzers notify us about missing documentation and provide suggestions to fix those. We are going to see how.
Configuring Web API Analyzers
The .NET Core SDK comes with inbuilt Web API analyzers. To enable them in our projects, let’s add the IncludeOpenAPIAnalyzers
property in the project file:
<PropertyGroup> <IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers> </PropertyGroup>
Once we enable the Web API analyzer, it will start notifying us of the missing documentation by showing them as warnings in Visual Studio. It will highlight the return statements that are not documented with squiggly lines. Additionally, they appear as warnings in the error list as well. This way, we can know about the missing documentation and get an opportunity to fix those.
Fixing the Documentation
Once the Web API analyzers show the missing documentation warnings in Visual Studio, there are several ways in which we can fix it. We can either hover over the warning line or click on Ctrl
+ .
and bring up the quick actions light bulb. After that, we can click on the down arrow next to the light bulb or the Show potential fixes link. It will then display a list of potential fixes for us – in this case, adding the [ProducesResponseType]
attribute:
Once we click on the suggested fix, it adds the [ProducesResposeType]
attributes corresponding to all the possible responses of the controller action method. Additionally, it adds the [ProducesDefaultResponseType]
attribute, which produces a default response for the action method:
[HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public IActionResult GetById(int id) { if (id < 1) { return BadRequest(); } if (!_repository.TryGetEmployee(id, out var employee)) { return NotFound(); } return Ok(employee); }
The default response for an action method describes all the error status codes collectively which are not covered by other responses.
Of course, we can always add the attributes manually if we prefer to do it that way.
On adding the [ProducesResposeType]
attributes, we can see that the warnings go away. Let’s run the project once again and verify the Swagger output:
Now we can see that the additional response types corresponding to 400 Bad Request, 404 Not Found, etc. start appearing in the Swagger responses. Additionally, we can see a default error response that describes the general error status codes.
ASP.NET Core Web API Conventions
If our API controllers follow the common pattern and use the conventional GET, POST, PUT, and DELETE methods for CRUD operations, we can consider using conventions as a substitute for using the [ProducesResponseType]
or [Produces]
attributes.
Conventions let us define the most common return types and status codes that our actions return and apply them to individual actions, controllers, or all controllers in the assembly. ASP.NET Core comes with a set of default API conventions called DefaultApiConventions
and we could use that if it suits our scenario.
For instance, if we want to apply the default API conventions to our actions, we could do it in three ways.
The first way is to apply the [ApiConventionType]
attribute at the assembly level in the Program class:
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
This applies the specified convention to all controllers in the assembly.
The second way is to apply the [ApiConventionType]
attribute at the controller level:
[ApiConventionType(typeof(DefaultApiConventions))]
This applies the specified conventions just to that controller.
The third way is to use the [ApiConventionMethod]
attribute for the action method. While using this attribute, there is an option to specify the convention type and method as arguments:
[ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))]
This will apply the specified convention method to the action.
So what if the default API conventions do not suit our action method? In that case, we can create our conventions and use them across multiple action methods. We are going to see how to do it.
Creating Web API Conventions
First, let’s define a custom convention using a static class:
public static class CustomConventions { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public static void GetByIdConvention() { } }
Within the class, we can create an empty convention method GetByIdConvention()
and decorate it with the required attributes. Of course, we can create different convention methods corresponding to different types of actions. After that, we can apply this to any action methods.
For applying the custom convention to the controller action method, we can decorate the action with the [ApiConventionMethod]
attribute and specify the convention type and method name:
[ApiConventionMethod(typeof(CustomConventions), nameof(CustomConventions.GetByIdConvention))]
This will apply all the specified response types to the action.
Furthermore, to make things easier, we can apply the conventions at the controller or assembly levels. While doing so, by default, it applies only to those controllers that match the name of the method and parameters. What if we want to create some common API conventions and apply them to multiple action methods based on some patterns?
Matching Conventions with Action Methods
The good news is that we can use the [ApiConventionNameMatch]
and [ApiConventionTypeMatch]
attributes with the convention method and apply them to all action methods that match the specified naming pattern, data types, etc. The [ApiConventionNameMatch]
attribute matches a method or parameter by its name while the [ApiConventionTypeMatch]
attribute matches a parameter by its type.
Let’s modify the convention method to add these attributes:
public static class CustomConventions { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Get( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.AssignableFrom)] int id) { } }
Here we have renamed the method to Get()
and applied the [ApiConventionNameMatch]
attribute with the prefix name match behavior. Additionally, for the id
parameter, we have specified a suffix name match behavior and the [ApiConventionTypeMatch]
attribute to apply further constraints on the parameter type. Now the convention method will apply to any action method that starts with “Get” (eg: Get(), GetById(), etc.) and has exactly one parameter that ends with “id” (eg: EmployeeId, id, etc.) and is of type integer or a type derived from it.
With this, there is no need to apply this convention for individual action methods. Instead, let’s apply this at the assembly level:
[assembly: ApiConventionType(typeof(CustomConventions))]
This will apply the conventions to all action methods matching the specified criteria.
Conclusion
In this article, we discussed what Web API Analyzers are and how they help in improving the API documentation by notifying us about the missing documentation and providing us with an easy way to fix those. Additionally, we learned Conventions which is another excellent option to enhance the API documentation by creating some common API conventions and reusing them across our application.