One of the new features of ASP.NET Core 2.2 is, API Controller conventions for better documentation experience. ASP.NET Core 2.1 introduced the ApiController
attribute to denote a web API controller specific conventions like performing automatic model validation and automatically responds with a 400 error. The ASP.NET Core 2.2 takes one step further to provide the metadata for API Explorer and a better end-to-end API documentation experience. To achieve this, ASP.NET Core 2.2 introduces API Analyzers which help in following a set of conventions for better API documentation. In this post, let’s find out how to use API Analyzers.
ASP.NET Core 2.2 introduces API Analyzers
Before we begin, please do the following installation (ignore if already done).
- .NET Core SDK for 2.2.0-preview2 — includes ASP.NET 2.2.0-preview2
- Install Visual Studio 2017 15.9 Preview 2
Open Visual Studio 2017 preview, hit Ctrl+Shift+N and select the ASP.NET Core Web Application (.NET Core) project type from the templates. When you click Ok, you will get the following prompt. Select ASP.NET Core 2.2 and choose the API template.
Once the project is created, we’ll need to enable the Swagger for API documentation. Here, I’ll not explain about enabling swagger, but you can follow this post to enable Swagger in ASP.NET Core WEB API.
Next, delete the ValuesController
class (default template) as it doesn’t have enough code to document. To show you how API Analyzers are helpful, we’ll need a real-world code sample (basically, a controller with CRUD operations). To do that, add a new folder “Model” at the root of the directory. Inside the Model folder, create an Employee
class with the following code.
public class Employee { public int Id { get; set; } public string EmpName { get; set; } public int Age{ get; set; } }
Next, we’ll create a new controller using the scaffolding process.
- Right-click on the Controllers folder in Solution Explorer and select Add -> Controller.
- Select API Controller with actions, using Entity Framework and click Ok.
- Next, in the pop-up window to set Model class “Employee” and create a new Data Context class using the + button.
This should create a new controller named EmployeeController
with the following code.
namespace ASPNETCoreAPIDemo.Controllers { [Route("api/[controller]")] [ApiController] public class EmployeesController : ControllerBase { private readonly ASPNETCoreAPIDemoContext _context; public EmployeesController(ASPNETCoreAPIDemoContext <span class="hiddenGrammarError" pre="">context) { _context</span> = context; } // GET: api/Employees [HttpGet] public async Task<ActionResult<IEnumerable<Employee>>> GetEmployee() { return await _context.Employee.ToListAsync(); } // GET: api/Employees/5 [HttpGet("{id}")] public async Task<ActionResult<Employee>> GetEmployee(int id) { var employee = await _context.Employee.FindAsync(id); if (employee == null) { return NotFound(); } return employee; } // PUT: api/Employees/5 [HttpPut("{id}")] public async Task<IActionResult> PutEmployee(int id, Employee employee) { if (id != employee.Id) { return BadRequest(); } _context.Entry(employee).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!EmployeeExists(id)) { return NotFound(); } else { throw; } } return NoContent(); } // POST: api/Employees [HttpPost] public async Task<ActionResult<Employee>> PostEmployee(Employee employee) { _context.Employee.Add(employee); await _context.SaveChangesAsync(); return CreatedAtAction("GetEmployee", new { id = employee.Id }, employee); } // DELETE: api/Employees/5 [HttpDelete("{id}")] public async Task<ActionResult<Employee>> DeleteEmployee(int id) { var employee = await _context.Employee.FindAsync(id); if (employee == null) { return NotFound(); } _context.Employee.Remove(employee); await _context.SaveChangesAsync(); return employee; } private bool EmployeeExists(int id) { return _context.Employee.Any(e => e.Id == id); } } }
To run this application successfully, we also need to run the database migration. To do that, run these following commands on the package manager console.
PM>Add-Migration Initial -OutputDir Data/Migrations PM>Update-Database
Now run the application and visit the Swagger URL. You should see the following.
In the above image, I have expanded the PUT API intentionally. The Swagger documentation just returns 200, but the actual code of PUT API returns 3 different HTTP status codes (400, 204 and 404).
// PUT: api/Employees/5 [HttpPut("{id}")] public async Task<IActionResult> PutEmployee(int id, Employee employee) { if (id != employee.Id) { return BadRequest(); } _context.Entry(employee).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!EmployeeExists(id)) { return NotFound(); } else { throw; } } return NoContent(); }
API analyzers can help to fix this missing piece of documentation.
Installing and using API Analyzers
By default, ASP.NET Core 2.2 ships with a set of default conventions – DefaultApiConventions
– that’s based on the controller that ASP.NET Core scaffolds. If your actions follow the pattern that scaffolding produces, you should be successful using the default conventions. These analyzers work with controllers annotated with ApiController
introduced in 2.1. To use API analyzers, first install the nuget package.
Microsoft.ASPNETCore.MVC.Api.Analyzers
Once the nuget package is installed, just build the application. Go to Error List window and you should see a couple of warnings in the Warning tab.
Clicking the particular warning will take you to the actual warning in the code. You can fix the individual warning, using Ctrl and .
on the green squiggly line. In case of PUT API, the API analyzers will put the following attributes on top of the method to fix all the warnings. But, fixing each warning will take a lot of time.
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType]
There are 3 ways to apply a convention to a controller action:
[assembly:ApiConventionType(typeof(DefaultApiConventions))]
ApiConventionType
attribute on a controller.
ApiConventionType(typeof(DefaultApiConventions))] [ApiController] [Route("/api/[controller]")] public class EmployeesController : ControllerBase { ... }
ApiConventionMethod
. This attributes accepts both the type and the convention method.
// PUT: api/Pets/5 [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Put))] [HttpPut("{id}")] public async Task<IActionResult> PutEmployee(int id, Employee employee) { ... }
The class DefaultApiConventions
is a static class, which defines a set of methods as a pattern and applicable metadata is applied to the method, if the pattern is matched. Here is the conventions for PUT in DefaultApiConventions
class.
Now build the application and notice all the warnings are gone. Run the application, visit the swagger URL and see the PUT API documentation. You should see the following.
Here, the swagger shows all the possible return types for the PUT API. If you don’t like the default conventions, you can also implement your custom implementation. Custom implementation will be ideal when your controller doesn’t follow the default CRUD conventions. As an example, if you have the following API in your controller. Here, the Action method name "Search"
doesn’t match with the default conventions.
[HttpGet("{name}")] public async Task<ActionResult<Employee>> SearchEmployee(string name) { if (name.Trim() == string.Empty) { return BadRequest(); } var employee = await _context.Employee.FindAsync(name); if (employee == null) { return NotFound(); } return employee; }
You can create a custom convention implementation like,
public static class MyCustomConventions { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Search([ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]string name) { } }
Ideally, you should copy the code of DefaultApiConventions
class and also add your custom code in your custom convention implementation as you can’t apply two conventions at a time. You can find the source code at GitHub.
Summary
The APIController
attribute was a good addition to ASP.NET Core 2.1 as it adds some great functionality which makes life easier for API developers. In continuation to API improvements, ASP.NET Core 2.2 comes with API analyzers. The API analyzers helps in producing the better API documentation. Along with the set of default conventions, you can also write your custom implementation of conventions suited for your requirement.
Thank you for reading. Keep visiting this blog and share this in your network. Please put your thoughts and feedback in the comments section.
3 thoughts to “ASP.NET Core 2.2 introduces API Analyzers for improved WEB API documentation”