Create API with ASP.NET Core (Day 3): Working With HTTP Status Codes In ASP.NET Core API


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

ASP.NET Core
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 actually 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.

ASP.NET Core

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.

  1. Create API with ASP.Net Core (Day 1): Getting Started and ASP.NET Core Request Pipeline
  2. Create API with ASP.Net Core (Day 2): Create an API and return resources in ASP.NET Core
  3. Create API with ASP.Net Core (Day 3): Working with HTTP Status Codes and returning sub resources in ASP.NET Core API
  4. Create API with ASP.Net Core (Day 4): Working with Serializer Settings and Content Negotiation in ASP.NET Core API
  5. Create API with ASP.Net Core (Day 5): Understanding Resources in ASP.NET CORE API
  6. Create API with ASP.Net Core (Day 6): Inversion of Control and Dependency Injection in ASP.NET CORE API
  7. Create API with ASP.Net Core (Day 7): Getting Started with Entity Framework Core
  8. 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.

ASP.NET Core

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.

ASP.NET Core

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.

ASP.NET Core

We can set the status code to 200 and return this variable, as shown below.

  1. [HttpGet()]
  2.  public JsonResult GetEmployees()
  3.  {
  4.    var employees= new JsonResult(EmployeesDataStore.Current.Employees);
  5.    employees.StatusCode = 200;
  6.    return employees;
  7.  }

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,

  1. using Microsoft.AspNetCore.Mvc;
  2. using System.Linq;
  3. namespace EmployeeInfo.API.Controllers
  4. {
  5.   [Route(“api/employees”)]
  6.   public class EmployeesInfoController : Controller
  7.   {
  8.     [HttpGet()]
  9.     public IActionResult GetEmployees()
  10.     {
  11.       return Ok(EmployeesDataStore.Current.Employees);
  12.     }
  13.     [HttpGet(“{id}”)]
  14.     public IActionResult GetEmployee(int id)
  15.     {
  16.       var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == id);
  17.       if (employee == null)
  18.         return NotFound();
  19.       return Ok(employee);
  20.     }
  21.   }
  22. }

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.

ASP.NET Core

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,

ASP.NET Core

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.

ASP.NET Core

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.

  1. app.UseStatusCodePages();

That is adding it to our request pipe line. So the method will look like,

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2.     {
  3.       loggerFactory.AddConsole();
  4.       if (env.IsDevelopment())
  5.       {
  6.         app.UseDeveloperExceptionPage();
  7.       }
  8.       else
  9.       {
  10.         app.UseExceptionHandler();
  11.       }
  12.       app.UseStatusCodePages();
  13.       app.UseMvc();
  14.       //app.Run(async (context) =>
  15.       //{
  16.       //  throw new Exception(“Test Dev Exception Page”);
  17.       //});
  18.       //app.Run(async (context) =>
  19.       //{
  20.       //  await context.Response.WriteAsync(“Hello World!”);
  21.       //});
  22.     }

Now again make the same API request from browser and we get the following message on the browser page itself.

ASP.NET Core

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.

ASP.NET Core

NumberOfCompaniesWorkedDto.cs

  1. namespace EmployeeInfo.API.Models
  2. {
  3.   public class NumberOfCompaniesWorkedDto
  4.   {
  5.     public int Id { get; set; }
  6.     public string Name { get; set; }
  7.     public string Description { get; set; }
  8.   }
  9. }

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

  1. using System.Collections.Generic;
  2. namespace EmployeeInfo.API.Models
  3. {
  4.   public class EmployeeDto
  5.   {
  6.     public int Id { get; set; }
  7.     public string Name { get; set; }
  8.     public string Designation { get; set; }
  9.     public string Salary { get; set; }
  10.     public int NumberOfCompaniesWorkedWith
  11.     {
  12.       get
  13.       {
  14.         return CompaniesWorkedWith.Count;
  15.       }
  16.     }
  17.     public ICollection CompaniesWorkedWith { get; set; } = new List();
  18.   }
  19. }

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

  1. using EmployeeInfo.API.Models;
  2. using System.Collections.Generic;
  3. namespace EmployeeInfo.API
  4. {
  5.   public class EmployeesDataStore
  6.   {
  7.     public static EmployeesDataStore Current { get; } = new EmployeesDataStore();
  8.     public List Employees { get; set; }
  9.     public EmployeesDataStore()
  10.     {
  11.       //Dummy data
  12.       Employees = new List()
  13.             {
  14.                 new EmployeeDto()
  15.                 {
  16.                      Id = 1,
  17.                      Name = “Akhil Mittal”,
  18.                      Designation = “Technical Manager”,
  19.                      Salary=“$50000”,
  20.                      CompaniesWorkedWith=new List()
  21.                      {
  22.                        new NumberOfCompaniesWorkedDto()
  23.                        {
  24.                          Id=1,
  25.                          Name=“Eon Technologies”,
  26.                          Description=“Financial Technologies”
  27.                        },
  28.                        new NumberOfCompaniesWorkedDto()
  29.                        {
  30.                          Id=2,
  31.                          Name=“CyberQ”,
  32.                          Description=“Outsourcing”
  33.                        },
  34.                        new NumberOfCompaniesWorkedDto()
  35.                        {
  36.                          Id=3,
  37.                          Name=“Magic Software Inc”,
  38.                          Description=“Education Technology and Fin Tech”
  39.                        }
  40.                      }
  41.                 },
  42.                 new EmployeeDto()
  43.                 {
  44.                      Id = 2,
  45.                      Name = “Keanu Reaves”,
  46.                      Designation = “Developer”,
  47.                      Salary=“$20000”,
  48.                      CompaniesWorkedWith=new List()
  49.                      {
  50.                        new NumberOfCompaniesWorkedDto()
  51.                        {
  52.                          Id=1,
  53.                          Name=“Eon Technologies”,
  54.                          Description=“Financial Technologies”
  55.                        }
  56.                      }
  57.                 },
  58.                  new EmployeeDto()
  59.                 {
  60.                      Id = 3,
  61.                      Name = “John Travolta”,
  62.                      Designation = “Senior Architect”,
  63.                      Salary=“$70000”,
  64.                      CompaniesWorkedWith=new List()
  65.                      {
  66.                        new NumberOfCompaniesWorkedDto()
  67.                        {
  68.                          Id=1,
  69.                          Name=“Eon Technologies”,
  70.                          Description=“Financial Technologies”
  71.                        },
  72.                        new NumberOfCompaniesWorkedDto()
  73.                        {
  74.                          Id=2,
  75.                          Name=“CyberQ”,
  76.                          Description=“Outsourcing”
  77.                        }
  78.                      }
  79.                 },
  80.                   new EmployeeDto()
  81.                 {
  82.                      Id = 4,
  83.                      Name = “Brad Pitt”,
  84.                      Designation = “Program Manager”,
  85.                      Salary=“$80000”,
  86.                      CompaniesWorkedWith=new List()
  87.                      {
  88.                        new NumberOfCompaniesWorkedDto()
  89.                        {
  90.                          Id=1,
  91.                          Name=“Infosys Technologies”,
  92.                          Description=“Financial Technologies”
  93.                        },
  94.                        new NumberOfCompaniesWorkedDto()
  95.                        {
  96.                          Id=2,
  97.                          Name=“Wipro”,
  98.                          Description=“Outsourcing”
  99.                        },
  100.                        new NumberOfCompaniesWorkedDto()
  101.                        {
  102.                          Id=3,
  103.                          Name=“Magic Software Inc”,
  104.                          Description=“Education Technology and Fin Tech”
  105.                        }
  106.                      }
  107.                 },
  108.                    new EmployeeDto()
  109.                 {
  110.                      Id = 5,
  111.                      Name = “Jason Statham”,
  112.                      Designation = “Delivery Head”,
  113.                      Salary=“$90000”,
  114.                      CompaniesWorkedWith=new List()
  115.                      {
  116.                        new NumberOfCompaniesWorkedDto()
  117.                        {
  118.                          Id=1,
  119.                          Name=“Fiserv”,
  120.                          Description=“Financial Technologies”
  121.                        },
  122.                        new NumberOfCompaniesWorkedDto()
  123.                        {
  124.                          Id=2,
  125.                          Name=“Wipro”,
  126.                          Description=“Outsourcing”
  127.                        },
  128.                        new NumberOfCompaniesWorkedDto()
  129.                        {
  130.                          Id=3,
  131.                          Name=“Magic Software Inc”,
  132.                          Description=“Education Technology and Fin Tech”
  133.                        }
  134.                        ,
  135.                        new NumberOfCompaniesWorkedDto()
  136.                        {
  137.                          Id=4,
  138.                          Name=“Sapient”,
  139.                          Description=“Education Technology and Fin Tech”
  140.                        }
  141.                      }
  142.                 }
  143.             };
  144.     }
  145.   }
  146. }

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.

ASP.NET Core

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//companiesworkedwith”. Therefore, the controller route will be “api/employees” as it would be common to all the actions.

ASP.NET Core

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.

  1. [HttpGet(“{employeeid}/companiesworkedwith”)]
  2. public IActionResult GetCompaniesWorkedWith(int employeeId)
  3. {
  4.   var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == employeeId);
  5.   if (employee == nullreturn NotFound();
  6.   return Ok(employee.CompaniesWorkedWith);
  7. }

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.

  1. [HttpGet(“{employeeid}/companyworkedwith/{id}”)]
  2.  public IActionResult GetCompanyWorkedWith(int employeeId, int Id)
  3.  {
  4.    var employee = EmployeesDataStore.Current.Employees.FirstOrDefault(emp => emp.Id == employeeId);
  5.    if (employee == nullreturn NotFound();
  6.    var companyWorkedWith = employee.CompaniesWorkedWith.FirstOrDefault(comp => comp.Id == Id);
  7.    if (companyWorkedWith == nullreturn NotFound();
  8.    return Ok(companyWorkedWith);
  9.  }

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

ASP.NET Core

Companiesworkedwith for non existing employee

ASP.NET Core

Particular Companiesworkedwith for a particular employee

ASP.NET Core

Non existing Companyworkedwith for a particular employee

ASP.NET Core

Get All Employees

ASP.NET Core

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

Advertisements

Author: Akhil Mittal

Akhil Mittal is a Microsoft MVP,C# Corner MVP, Code project MVP, blogger, programmer by heart and currently working as a Sr. Analyst in Magic Software and have an experience of more than 9 years in C#.Net. He holds a B.Tech in Computer Science and hold a diploma in Information Security and Application Development. His work experience includes Development of Enterprise Applications using C#,.Net and Sql Server,Analysis, Product Management as well as Research and Development. He is a MCP in Web Applications(MCTS-70-528,MCTS-70-515) and .Net Framework 2.0 (MCTS-70-536).