Click here to Skip to main content
13,201,050 members (53,956 online)
Click here to Skip to main content
Add your own
alternative version

Stats

7.4K views
161 downloads
17 bookmarked
Posted 6 Jun 2017

Create API with ASP.NET Core (Day 3): Working with HTTP Status Codes, Serializer Settings and Content Negotiation in ASP.NET Core API

, 6 Jun 2017
Rate this:
Please Sign up or sign in to vote.
In this article, we’ll continue to explore the importance of statuscodes and practical examples as well. We’ll also explore resource creation andreturning the child resources as well.

Introduction

This article in the "Web API with ASP.NET Core" series will focus on topics like returning HTTP Status Codes from API and their importance, returning sub resources, serializer strings and content negotiation. 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

While consuming an API an Http Request is sent and in return, a response is sent along with return data and an HTTP code. The HTTP Status Codes are important because they tell the consumer about what exactly happened to their request; a wrong HTTP code can confuse the consumer. A consumer should know (via a response) that its request has been taken care of or not, and if the response is not as expected, then 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 the consumer gets a 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 it sent. In general, there are a lot of access codes. One can find the complete here, but not all are so important except the few. Few status code are very frequently used with the normal CURD operations that a service perform, so service does not necessarily have to support all of them.Let’s have a glance over 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 e.g. 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, Serializer Settings and Content Negotiation in ASP.NET Core API
  4. Create API with ASP.NET Core (Day 4): Understanding Resources in ASP.NET CORE API
  5. Create API with ASP.NET Core (Day 5): Inversion of Control and Dependency Injection in ASP.NET CORE API
  6. Create API with ASP.NET Core (Day 6): Getting Started with Entity Framework Core
  7. Create API with ASP.NET Core (Day 7): 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 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, we can set that StatusCode property.

So 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 exist 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 exists, we can return NotFound, else Employee data with the status code OK. In the 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();


      //app.Run(async (context) =>
      //{
      //  throw new Exception("Test Dev Exception Page");
      //});

      //app.Run(async (context) =>
      //{
      //  await context.Response.WriteAsync("Hello World!");
      //});
    }

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. For the property NumberOfCompaniesWorkedWith, we calculated the count from the collection that we have. We initialized the 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()
    {
      //Dummy data
      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, 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 resources, we take Id of the parent resource to get child resource.

The 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 worked with won’t exist even, 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 a single company worked with information. In that case, we should pass two IDs one for the employee and the other for the company like shown in 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. If the company does not exist, we again return NotFound, else, 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. For example, in the similar way as the API is written and in those cases consumer also gets the correct response and the response does not confuse the consumer. 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 an 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 now.

Serializer Settings in ASP.NET Core API

By default, ASP.NET Core uses JSON for serialization and de-serialization, but the best thing is that we can also configure this in our code. We used the ConfigureServices method in the startup class to configure the services use be the container, in a similar way we can configure for MVC.

We can add JSON options to the added MVC service.

Since the AddJsonOptions method expects an action, we supply a lambda expression there as options parameter through which we can easily access the serializer settings as shown in above image. We fetch the contract resolver as we’ll override the default settings of naming strategy. The line var resolver = opt.SerializerSettings.ContractResolver as DefaultContractResolver; takes out the contract resolver and cast ot to the default contract resolver, we can now override its NamingStrategy property and mark it to null. So now it should not typically follow that lower letters convention, which it followed by default. We use the DefaultContractResolver class which is part of Newtonsoft.Json.Serialization, so we’ll have to add the namespace for it as well. The code will be as shown below:

public void ConfigureServices(IServiceCollection services)
    {
      services.AddMvc().AddJsonOptions(opt =>
      {
        if (opt.SerializerSettings.ContractResolver != null)
        {
          var resolver = opt.SerializerSettings.ContractResolver as DefaultContractResolver;
          resolver.NamingStrategy = null;
        }
      });
    }

Now let’s test this with Postman. In the last request, we got the JSON properties in small case letters as shown below:

Now run the application and make a request for the same API from postman as consumer.

Now we get the JSON properties in capital letters (i.e. overridden by our implementation). This implementation is on need basis or based on the kind of JSON the consumer wants. Here comes the concept of content negotiation where a response is sent based on consumers request parameters.

Content Negotiation and Formatters in ASP.NET Core API

Content negotiation is one of the important concepts when we develop an API. This enables an API to select best representation for a desired response when there are more than one representations available. Suppose we build an API for multiple consumers and clients we are not sure that whether all the clients would be able to consume the default representation that an API sends in the form JSON. Some consumers may expect XML as a response or any other format. In that case it would be hard for the consumers to understand and work with JSON instead of XML.

There is always an option to the consumer to send a request for a specific format by specifying the requested media type in the Accept header. For example, if in the accept header the requested format is XML, the API should send the response in XML format and if it is JSON, the API should send the response in JSON format. If there is no header specified, then API can take liberty to send the response in the default format that it has. For example, JSON in our case. ASP.NET Core also supports this functionality via output formatters. Output formatter (as the name suggests) mostly deals with the output. The consumer in that case can request any specific type of output that it wants by setting the accept header to request media type such as Application/JSON or Application/XML. It also works with the input formats in a way that supposes there is a POST request to an API for creating a resource, the input media type and the content that comes along with the request is then identified by content-type header in the request. Let’s cover this with practical implementations. Let’s try to request the list of employees with the JSON accept header. So, we’ll set Header key as "Accept" and Value as "application/json", and we get JSON as shown below.

Now make a request with accept header application/xml, we still get JSON.

But ideally, the API should return XML. Let’s see how we can configure our service to return the same. If we go to the Startup class’s ConfigureService method, we have an option to add MvcOptions to the service, which in turn has options to add input and output formatters.

If we need to use the Xml Output formatter, we’ll have to install a nugget package named Microsoft.AspNetCore.MVC.Formatters.Xml as shown below. So right click on the project, go to manage nugget packages and search online for this package and install it.

Now if we can add the XML output formatter as shown in following code:

services.AddMvc()
        .AddMvcOptions(opt => opt.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()));

Similarly, Input formatters can also be added with XmlDataContractSerializerInputFormatter. So, build the solution, run the project and then now again try to request the employees list.

First with default or JSON formatter, we get JSON as shown below.

And now with the XML accept header we get XML as shown below.

Hence now a consumer can request the response in desired format and our API is capable of delivering that as well.

Our code for the Startup class looks like the following:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace EmployeeInfo.API
{
  public class Startup
  {
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddMvc()
        .AddMvcOptions(opt => opt.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()));
      //.AddJsonOptions(opt =>
      //{
      //  if (opt.SerializerSettings.ContractResolver != null)
      //  {
      //    var resolver = opt.SerializerSettings.ContractResolver as DefaultContractResolver;
      //    resolver.NamingStrategy = null;
      //  }
      //});
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
      loggerFactory.AddConsole();

      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
      else
      {
        app.UseExceptionHandler();
      }

      app.UseStatusCodePages();

      app.UseMvc();

      //app.Run(async (context) =>
      //{
      //  throw new Exception("Test Dev Exception Page");
      //});

      //app.Run(async (context) =>
      //{
      //  await context.Response.WriteAsync("Hello World!");
      //});
    }
  }
}

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. We learnt about serializer settings and most importantly the formatters and how we can enable the API to support content negotiation as well. In the next article of learning ASP.NET Core API we’ll do some more practical stuff when we perform CRUD operations.

<< Previous Article

Source Code on Github

Source Code

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Akhil Mittal
Architect Magic Software Inc.
India India
This member doesn't quite have enough reputation to be able to display their biography and homepage.
Group type: Collaborative Group

575 members


You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralMessage Closed Pin
18-Jun-17 7:36
membermilad7118-Jun-17 7:36 
GeneralMy vote of 5 Pin
D V L15-Jun-17 20:19
professionalD V L15-Jun-17 20:19 
GeneralRe: My vote of 5 Pin
Akhil Mittal 15-Jun-17 22:44
mvp Akhil Mittal 15-Jun-17 22:44 
GeneralMy vote of 5 Pin
Vaso Elias12-Jun-17 3:00
memberVaso Elias12-Jun-17 3:00 
GeneralRe: My vote of 5 Pin
Akhil Mittal 13-Jun-17 3:04
mvp Akhil Mittal 13-Jun-17 3:04 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey6-Jun-17 21:22
professionalManoj Kumar Choubey6-Jun-17 21:22 
GeneralRe: My vote of 5 Pin
Akhil Mittal 6-Jun-17 23:37
mvp Akhil Mittal 6-Jun-17 23:37 
GeneralMy vote of 5 Pin
prashita gupta6-Jun-17 7:38
memberprashita gupta6-Jun-17 7:38 
GeneralRe: My vote of 5 Pin
Akhil Mittal 6-Jun-17 17:38
mvp Akhil Mittal 6-Jun-17 17:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171020.1 | Last Updated 6 Jun 2017
Article Copyright 2017 by Akhil Mittal
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid