How do you use Web APIs at your work?
Simple, I need to open code to check what request is expected by action, then I need to check what response will be returned to me. When I have all the information I can start writing client code. If there are problems, for example I don’t know how to use action, I need to ask developer responsible for this API to send me some code snippets. If I don’t know what the action does, I need to read some more code and understand it. It’s pretty simple right? Wrong! You can afford looking at API code only if it’s your own company code or Open Source and even then it’s pretty tiresome. Maybe you’re frontend developer and you don’t understand C#, then what?
Answer is OpenAPI Specification
As they (OpenAPI Initiative) say, it’s standard which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. It sounds like answer for every question asked above. You can read about OpenAPI Specification here , what is important for us, how it can help.
Swagger
You should know that OpenAPI is like interface and Swagger is implementation. You can use Swagger to create documentation, so other developers can understand your API without looking into code, what’s great in Swagger is, that creating this documentation isn’t as boring as creating normal documentation. Most of it is generated by Swagger automatically. You can add more information if you want, but just installing Swagger will generate great documentation for a start.
Instalation
- Add
Swashbuckle.AspNetCore
to Web API project - Add code to
public void ConfigureServices(IServiceCollection services)
method
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
- Add code to
public void Configure(IApplicationBuilder app)
method
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
That’s all, from that point you can run you API and navigate to www.localhost:[port]/swagger.
Swagger Inside
First of all it looks great, second it’s powerful. You can find there every action in whole API with URLs and Verbs. When you click on action you can learn what request and response models are. Additionally you can send request to try API. It’s easy to use and speeds up work. Swagger allows you to test API manually with minimal effort and helps discover API by other developers.
Image below show „Test Endpoint”. You can send test request to your API. Swagger will generate request with default values, you can easily change it, if you want to or send it as it is.
Swagger capabilities
For me the most important ability to check requests and responses. Documentation for requests will be generated automatically, but if you want to create documentation for responses you need to specify it by yourself. Consider code below:
[ProducesResponseType(typeof(IEnumerable<Student>), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(typeof(void), 401)]
[ProducesResponseType(typeof(void), 403)]
public async Task<ActionResult<Student>> GetAllStudents([FromQuery] StudentFilterDto filterDto, [FromQuery] string sort = "Id")
{
}
That’s all, pretty easy, right? You need to add attribute [ProducesResponseType] above your action in controller. First parameter is type that is returned, second parameter is status code you want to bind response to. Thanks to that, Swagger will generate documentation for success and errors. For example, on success this endpoint will return OK with IEnumerable of Students. For validation error it will return 400 with string message. Additionally endpoint supports Auth, because it can response with status 401 (Unauthorize) and 403 (Forbidden). It’s a lot of information, and all of it without looking into code.
Swagger with Auth
Some time ago Swagger was useless if you had Authorize on your endpoints. It was impossible to send Authorization Header with request. That meant you had to use Postman for Auth endpoints, but now you can specify token which will be send in every request. Consider code below:
public static void Configure(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Blog Post - Advanced", Version = "v1" });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. To use, put the following phrase: \"Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
});
}
This will create button which will let you specify token like screen below.
Swagger vs REST
Restful service has many rules, one of them says that user should guess easily what endpoint returns what data.
– GET api/students – array of students
– GET api/students/1 – one student
etc. problem is, when we want to let user paging through our data. Normally we’ll get page number and page size in query params, but then we can’t return all students without paging, because ASP.Net Core choose action via HTTP Verb and route. From here we have 2 choices, implement our own action selector or create another endpoint therefore we can stand against Rest or OpenAPI.
Implement own Action Selector
public class QueryStringConstraintAttribute : ActionMethodSelectorAttribute
{
public string ValueName { get; private set; }
public bool IsPresent { get; private set; }
public QueryStringConstraintAttribute(string valueName, bool isPresent)
{
ValueName = valueName;
IsPresent = isPresent;
}
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var value = routeContext.HttpContext.Request.Query[ValueName];
return IsPresent ? !StringValues.IsNullOrEmpty(value) : StringValues.IsNullOrEmpty(value);
}
}
Attribute will select action based on query params. It’s pretty simple. In overridden method we need to check if action is valid. So first of all we need to check if request contains query param that we’re looking for var value = routeContext.HttpContext.Request.Query[ValueName];
after that if we set that query param is needed or not we need to check if value for selected param is specified or not return IsPresent ? !StringValues.IsNullOrEmpty(value) : StringValues.IsNullOrEmpty(value);
. </Magic>
Usage
// GET: /api/students?sort=CourseId,Name-
[Authorize]
[HttpGet]
[QueryStringConstraint("PageNumber", false)]
public async Task<ActionResult<Student>> GetAllStudents([FromQuery] StudentFilterDto filterDto, [FromQuery] string sort = "Id")
{
...
}
Our Attribute takes 2 params, first is name of value we want to check, second tells us if this value must be present to be selected or can’t be present.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MyAttribute : Attribute
{}
If we want to allow multiple usage we have to add line on top of our attribute class. From now we can set that 2 or more params can or can’t be present in query params.
This solution stands against OpenAPI
This is because OpenAPI considers a unique operation as a combination of a path and the HTTP method, and additional parameters do not make the operation unique. Instead, you should use unique paths such as:
GET /users/findByName?firstName=value&lastName=value
GET /users/findByRole?role=value
This also means that it is impossible to have multiple paths that differ only in query string, such as:
GET /users?firstName=value&lastName=value
GET /users?role=value
Here you can learn that rules are made to be broken (Just remember to learn them before you broke them :D). In my opinion it’s better to break Rest rule, coz Rest was created to make your work easier, if it’s easier by dropping one rule in one place, then it’s ok. Clear documentation provided by Swagger is better. That’s why I recommend to create another endpoint.
GET api/students?PageNumber=1&PageSize=10 // for paging
GET api/students/all // for every student, here user will not select this option by accident, coz paging is default