Introduction
This article of the series “Web API with ASP.NET Core” will focus on topics like returning HTTP Status Codes from API, their importance, and returning sub resources. We learned how to create an API in ASP.NET Core and how to return resources, in last article, and paused at Status Codes. We’ll continue to explore the importance of status codes and practical examples as well. We’ll also explore resource creation and returning the child resources as well in this article. We can use the same source code as we got at the completion of last article of the series.
HTTP Status Codes
Image credit: https://pixabay.com/en/error-not-found-404-lego-mistake-2129569/
While consuming an API, an HTTP Request is sent and in return, the response is sent along with the return data and a HTTP code. The HTTP Status Codes are important because they tell consumers about what exactly happened to their request; A wrong HTTP code can confuse the consumer. A consumer should know via the response if his/her request has been well taken care of or not, and if the response is not as expected, the Status Code should tell the consumer where the problem is - if it is at consumer level or at API level?
Suppose there is a situation where consumer gets response as status code 200 but at the service level, there is some problem or issue. In that case, consumer will get a false assumption of everything being fine, whereas that won’t be a case. So if there is something wrong at service or there occurs some error on the server, the status code 500 should be sent to consumer, so that the consumer knows that there actaully is something wrong with the request that it sent. In general, there are a lot of access codes. One can find the complete list here, but not all are so important except the few. Few status codes are very frequently used with the normal CRUD operations that a service performs, so service does not necessarily have to support all of them. Let’s have a glance over a few of the important status codes.
When we talk about the levels of status codes, there are 5 levels. Level 100 status codes are more of informal nature. Level 200 status codes are specifically for request being sent well. We get 200 codes for success of a GET request, 201 if a new resource has been successfully created. 204 status codes is also for success but in return it does not returns anything, just like if consumer has performed delete operation and in return doesn’t really expect something back. Level 300 http status codes are basically used for redirection, for example to tell a consumer that the requested resource like page, image has been moved to another location. Level 400 status codes are meant to state errors or client error for e.g. status code 400 means Bad Request, 401 is Unauthorized that is invalid authentication credentials or details have been provided by the consumer, 403 means that authentication is a success, but the user is not authorized. 404 are very common and we often encounter which mean that the requested resource is not available. Level 500 are for server errors. Internal Server Error exception is very common, that contains the code 500. This error means that there is some unexpected error on the server and client cannot do anything about it. We’ll cover how we can use these HTTP status codes in our application.
RoadMap
We’ll follow a roadmap to learn and cover all the aspects of Asp.net core in detail. Following is the roadmap or list of articles that will cover the entire series.
- Create API with ASP.Net Core (Day 1): Getting Started and ASP.NET Core Request Pipeline
- Create API with ASP.Net Core (Day 2): Create an API and return resources in ASP.NET Core
- Create API with ASP.Net Core (Day 3): Working with HTTP Status Codes and returning sub resources in ASP.NET Core API
- Create API with ASP.Net Core (Day 4): Working with Serializer Settings and Content Negotiation in ASP.NET Core API
- Create API with ASP.Net Core (Day 5): Understanding Resources in ASP.NET CORE API
- Create API with ASP.Net Core (Day 6): Inversion of Control and Dependency Injection in ASP.NET CORE API
- Create API with ASP.Net Core (Day 7): Getting Started with Entity Framework Core
- Create API with ASP.Net Core (Day 8): Entity Framework Core in ASP.NET CORE API
HTTP Status Codes Implementation
Let’s try to tweak our implementation, and try to return HTTP Status Codes from API. Now in the EmployeesController, we need to return the JSON result along with the status code in response. Right now we return only the json result as shown below.
If we look closely at JsonResult class and press F12 to see its definition, we find that it is derived from ActionResult class which purposely formats the object in the form of a JSON object. ActionResult class implements IActionResult interface.
Ideally, API should not only return JSON all the time, they should return what consumer is expecting i.e. via inspecting the request headers, and ideally we should be able to return the Status Code also along with the result.
We’ll see that it could also be possible with the JSonResult that we get in return. So, if you assign the JSON result that we create to any variable, say “employees”, you can find the StatusCode property associated with that variable,and we can set that StatusCode property.
We can set the status code to 200 and return this variable, as shown below.
- [HttpGet()]
- public JsonResult GetEmployees()
- {
- var employees= new JsonResult(EmployeesDataStore.Current.Employees);
- employees.StatusCode = 200;
- return employees;
- }
But that would be a tedious job to do everytime. There are predefined methods in ASP.NET core that create IActionResult, and all those are mapped to correct status codes i.e. NotFound if the resource doesn’t exis or BadRequest for erroneous requests, etc.
So, replace the return type of methods with IActionResult instead of JsonResult, and in the GetEmployee method make the implementation as if there is a request of Employee with the id that does not exist, and we can return NotFound , otherwise Employee data with the status code OK. In a similar way for the first method where we return the list of employees, we can directly return Ok with the data. There is no scope of NotFound in this method, because even if no records exist an empty list could be returned with HTTP Status Code OK. So, our code becomes something like shown below,
- using Microsoft.AspNetCore.Mvc;
- using System.Linq;
-
- namespace EmployeeInfo.API.Controllers
- {
- [Route("api/employees")]
- public class EmployeesInfoController : Controller
- {
- [HttpGet()]
- public IActionResult GetEmployees()
- {
- return Ok(EmployeesDataStore.Current.Employees);
- }
-
- [HttpGet("{id}")]
- public IActionResult GetEmployee(int id)
- {
- var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == id);
- if (employee == null)
- return NotFound();
- return Ok(employee);
-
- }
- }
- }
Compile the application and run it. Now, go to postman and try to make requests. In the last article, we made a request of an employee with the Id 8, that does not exist and we got the following result.
The result said null, that is not ideally a correct response. So try to make the same request now with this new implementation that we did. In this case, we get a proper status 404 with Not Found message as shown below,
Since we know that we can also send the request from web browsers as browsers support HTTP Request. If we try to access our API via browser, we get an empty page with the error or response in the developer’s tool as shown below.
We can make our responses more meaningful by displaying the same on the page itself as ASP.NET Core contains a middle ware for Status Codes as well. Just open the configure method of the startup class and add following line to the method.
- app.UseStatusCodePages();
That is adding it to our request pipe line. So the method will look like,
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
- {
- loggerFactory.AddConsole();
-
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
- else
- {
- app.UseExceptionHandler();
- }
-
- app.UseStatusCodePages();
-
- app.UseMvc();
-
-
-
-
-
-
-
-
-
-
-
- }
Now again make the same API request from browser and we get the following message on the browser page itself.
Returning Sub Resources
In the EmployeeDto, we have a property named NumberOfCompaniesWorkedWith, considering this as a sub resource or child resource, we might want to return this as a result as well. Let’s see how we can achieve that. Add a DTO class named NumberOfCompaniesWorkedDto to the Model folder, and add Id, name and Description to that newly added class.
NumberOfCompaniesWorkedDto.cs
- namespace EmployeeInfo.API.Models
- {
- public class NumberOfCompaniesWorkedDto
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- }
- }
Now in the EmployeeDto class add a property w.r.t. this NumberOfCompaniesWorkedDto that returns the collection of all companies an employee has worked with.
EmployeeDto.cs
- using System.Collections.Generic;
-
- namespace EmployeeInfo.API.Models
- {
- public class EmployeeDto
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Designation { get; set; }
- public string Salary { get; set; }
- public int NumberOfCompaniesWorkedWith
- {
- get
- {
- return CompaniesWorkedWith.Count;
- }
- }
-
- public ICollection<NumberOfCompaniesWorkedDto> CompaniesWorkedWith { get; set; } = new List<NumberOfCompaniesWorkedDto>();
-
- }
- }
In the above code, we added a new property that returns the list of companies an employee has worked with and for the property NumberOfCompaniesWorkedWith, we calculated the count from the collection that we have. We initialized CompaniesWorkedWith property to return an empty list so that we do not end up having null-reference exceptions. Now add some mock data of CompaniesWorkedWith to the EmployeesDataStore class.
EmployeesDataStore.cs
- using EmployeeInfo.API.Models;
- using System.Collections.Generic;
-
- namespace EmployeeInfo.API
- {
- public class EmployeesDataStore
- {
- public static EmployeesDataStore Current { get; } = new EmployeesDataStore();
- public List<EmployeeDto> Employees { get; set; }
-
- public EmployeesDataStore()
- {
-
- Employees = new List<EmployeeDto>()
- {
- new EmployeeDto()
- {
- Id = 1,
- Name = "Akhil Mittal",
- Designation = "Technical Manager",
- Salary="$50000",
- CompaniesWorkedWith=new List<NumberOfCompaniesWorkedDto>()
- {
- new NumberOfCompaniesWorkedDto()
- {
- Id=1,
- Name="Eon Technologies",
- Description="Financial Technologies"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=2,
- Name="CyberQ",
- Description="Outsourcing"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=3,
- Name="Magic Software Inc",
- Description="Education Technology and Fin Tech"
- }
- }
- },
- new EmployeeDto()
- {
- Id = 2,
- Name = "Keanu Reaves",
- Designation = "Developer",
- Salary="$20000",
- CompaniesWorkedWith=new List<NumberOfCompaniesWorkedDto>()
- {
- new NumberOfCompaniesWorkedDto()
- {
- Id=1,
- Name="Eon Technologies",
- Description="Financial Technologies"
- }
- }
- },
- new EmployeeDto()
- {
- Id = 3,
- Name = "John Travolta",
- Designation = "Senior Architect",
- Salary="$70000",
- CompaniesWorkedWith=new List<NumberOfCompaniesWorkedDto>()
- {
- new NumberOfCompaniesWorkedDto()
- {
- Id=1,
- Name="Eon Technologies",
- Description="Financial Technologies"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=2,
- Name="CyberQ",
- Description="Outsourcing"
- }
- }
- },
- new EmployeeDto()
- {
- Id = 4,
- Name = "Brad Pitt",
- Designation = "Program Manager",
- Salary="$80000",
- CompaniesWorkedWith=new List<NumberOfCompaniesWorkedDto>()
- {
- new NumberOfCompaniesWorkedDto()
- {
- Id=1,
- Name="Infosys Technologies",
- Description="Financial Technologies"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=2,
- Name="Wipro",
- Description="Outsourcing"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=3,
- Name="Magic Software Inc",
- Description="Education Technology and Fin Tech"
- }
- }
- },
- new EmployeeDto()
- {
- Id = 5,
- Name = "Jason Statham",
- Designation = "Delivery Head",
- Salary="$90000",
- CompaniesWorkedWith=new List<NumberOfCompaniesWorkedDto>()
- {
- new NumberOfCompaniesWorkedDto()
- {
- Id=1,
- Name="Fiserv",
- Description="Financial Technologies"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=2,
- Name="Wipro",
- Description="Outsourcing"
- },
- new NumberOfCompaniesWorkedDto()
- {
- Id=3,
- Name="Magic Software Inc",
- Description="Education Technology and Fin Tech"
- }
- ,
- new NumberOfCompaniesWorkedDto()
- {
- Id=4,
- Name="Sapient",
- Description="Education Technology and Fin Tech"
- }
- }
- }
- };
-
- }
- }
- }
Now add a new controller named CompaniesWorkedWithController in the same way as we created EmployeesController in the last article. The new controller should be derived from Controller class.
Since, CompaniesWorkedWith is directly related to Employees so if we put a default route to “api/companiesworkedwith” won’t look good and justifiable. If this is related to employees, the URI of the API should also show that. CompaniesWorkedWith could be considered as a sub resource of Employees or child resource of Employee. So, the companies worked with as a resource should be accessed via employees, therefore the URI will be somewhat like “api/employees/<employee id>/companiesworkedwith”. Therefore, the controller route will be “api/employees” as it would be common to all the actions.
Since the child resource is dependent on parent resource, we take Id of the parent resource to get child resource.
Now following is the actual implementation for returning the list of companies of an employee.
- [HttpGet("{employeeid}/companiesworkedwith")]
- public IActionResult GetCompaniesWorkedWith(int employeeId)
- {
- var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == employeeId);
- if (employee == null) return NotFound();
- return Ok(employee.CompaniesWorkedWith);
- }
If we look at the above code, we first find an employee with the passed id, and we’ll have to do so because if there is no employee with that id then it is understood that his companies won't even exist, and that gives us the liberty to send the status code NotFound if there is no employee, on the other hand if we find an employee, we can send the companiesworkedwith to the consumer.
In a similar way, we write the code for getting the information for a single company he's worked with. In that case, we should pass two id’s, one for the employee and the other for the company, like shown in the below code.
- [HttpGet("{employeeid}/companyworkedwith/{id}")]
- public IActionResult GetCompanyWorkedWith(int employeeId, int Id)
- {
- var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == employeeId);
- if (employee == null) return NotFound();
-
- var companyWorkedWith = employee.CompaniesWorkedWith.FirstOrDefault(comp => comp.Id == Id);
- if (companyWorkedWith == null) return NotFound();
- return Ok(companyWorkedWith);
- }
So, in the code above, we first get the employee for the id passed. If the employee exists we go further to fetch the company with the passed id else, we return Not Found. In case the company does not exist, we again return NotFound otherwise we return the result OK with the company object. We can try to check these implementations on Postman.
Companiesworkedwith for existing employee
Companiesworkedwith for non existing employee
Particular Companiesworkedwith for a particular employee
Non existing Companyworkedwith for a particular employee
Get All Employees
So, we see that we get the results as expected; i.e. in a similar way as the API is written, and in those cases the consumer also gets the correct response and the response do esnot confuse the consumer as well. We also see that we get the related or child resources along with Employees if we send a request to get all employees. Well this scenario would not be ideal for every request, for example the consumer may only want employees and not the related companies worked with data. Yes, we can also control this with the help of Entity Framework that we’ll cover in the later articles of the series. Notice that we can also control the casing of the JSON data that is returned as the consumer may expect the properties to be in upper case, we can also do this with the help of serializer settings that we’ll cover in the next article.
Conclusion
In this article, we learned about the HTTP Codes and their importance and how we can configure our service to use HTTP Codes. We also focused on how sub resources or child resources could be sent via an API. In the next article we’ll learnt about serializer settings and most importantly the formatters and how we can enable the API to support content negotiation as well.
Source Code on Github
References
- https://www.asp.net/core
- https://www.pluralsight.com/courses/asp-dotnet-core-api-building-first