Saturday, 24 August 2013

Performing CRUD Operations in Web API OData Service using ODataController

OData is a protocol for operating on data over HTTP. OData follows REST architecture. It provides a uniform interface for interacting with data over web and performing CRUD (Create, Read, Update and Delete) operations. It also provides metadata so that the consuming application gets awareness about the types used in the service.

If you are new to OData and want to learn more, checkout the official website for OData and read David Chappel’s whitepaper on OData.

ASP.NET Web API is a solution for creating HTTP services. ASP.NET team added OData support to Web API in the ASP.NET and Web Tools 2012.2 Update. In this post, we will create a simple Web API OData service that performs CRUD operations on an in-memory object collection.

Setting up the project and data:

Fire Visual Studio 2012 or 2013 and create an ASP.NET MVC 4 project with Web API template or, add the following NuGet packages to an empty ASP.NET project:

  • Microsoft.Aspnet.WebApi.OData
  • Microsoft.AspNet.WebApi.WebHost

Let’s create our data components now. Add a new class to the project, name it Employee and add following properties to it:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Salary { get; set; }
}
Add another class named EmployeesHolder. This class will hold a static collection of Employee class created above. We will perform CRUD operations on the data created in this class. Add the following code to the EmployeesHolder class:
public class EmployeesHolder
{
    public static List<Employee> Employees;

    static EmployeesHolder()
    {
        Employees = new List<Employee>();

        Employees.Add(new Employee() { Id = 1, Name = "Jack", Salary = 10000 });
        Employees.Add(new Employee() { Id = 2, Name = "Anthony", Salary = 7000 });
        Employees.Add(new Employee() { Id = 3, Name = "Tracey", Salary = 20000 });
        Employees.Add(new Employee() { Id = 4, Name = "Sherry", Salary = 6000 });
        Employees.Add(new Employee() { Id = 5, Name = "Hill", Salary = 16000 });
    }
}

Creating OData Route

We need to add some configurations when application starts to enable OData in the application. Before defining the route map, we need to build an EDM model with all entity sets to be exposed. It is done as shown below:

     var modelBuilder = new ODataConventionModelBuilder();
     modelBuilder.EntitySet<Employee>("Employees");
     Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();

The ODataConventionalModelBuilder is used to map all entity sets that have to be exposed through OData endpoint. Alternatively, ODataModelBuilder can also be used for this purpose. But if we do so, we need to define each property and relationship using fluent configuration model. ODataConventionalModelBuilder does all that work for us. We just need to add the entity sets to be exposed.

We need to define the OData route using the EDM model created above. It is shown below:

config.Routes.MapODataRoute("ODataRoute", "odata", model);


Creating API Controller and performing read operations


Let’s define a Web API controller to perform CRUD operations over the collection created above. Add an API controller to the application named EmployeesController. Modify the parent class of this class as ODataController.
public class EmployeesController : ODataController
{
}

ODataController is the low-level class to work with OData in Web API. We need to directly deal with HTTP verbs and build response by hand using this class. We have higher level classes available that deal with the verbs. While using them we have to just worry about the data, rest of the things are taken care by the framework. But it is important to understand what is going on behind the scenes to get much of the work done. So, in this post we will create the API using ODataController.

Delete all the default code inside the controller. Add the following method to the controller:

public IQueryable<Employee> Get()
{
    return EmployeesHolder.Employees.AsQueryable();
}

Build and run this application now. Change the URL on your browser to:


http://localhost:<port-no>/odata/Employees


You should be able to see the list of all employees created above in your browser.



Notice the metadata URL in the first statement. Metadata information for the entire application is exposed through this URL.

Let’s add another method to the service that gets details of an employee based on ID.


public HttpResponseMessage Get([FromODataUri]int key)
{
    Employee employee = EmployeesHolder.Employees.Where(e => e.Id == key).SingleOrDefault();
    if (employee==null)
    {
        return Request.CreateResponse(HttpStatusCode.NotFound);
    }

    return Request.CreateResponse(HttpStatusCode.OK, employee);
}


The attribute FromODataUri indicates that the value has to be taken from the requested URL. Change the URL on the browser as:


http://localhost:1908/odata/Employees(2)


Now you should be able to see details of the second employee on the browser:



Web API OData supports a number of query options that can be used to query data from client application. We will explore those options in a future post.

Performing Create operation

HTTP Post method is for adding an item to the resource. It usually accepts data from the client in the HTTP request body. Following Post method adds a new employee to the list we created earlier:

public HttpResponseMessage Post([FromBody]Employee entity)
{
    string name = entity.Name;
    HttpResponseMessage response;
    double salary = entity.Salary;

    try
    {
        int employeeId = EmployeesHolder.Employees.Max(e => e.Id) + 1;

        entity.Id = employeeId;
        EmployeesHolder.Employees.Add(entity);

        response = Request.CreateResponse(HttpStatusCode.Created, entity);
        response.Headers.Add("Location", Url.ODataLink(new EntitySetPathSegment("Employees")));
        return response;
    }
    catch (Exception)
    {
        response = Request.CreateResponse(HttpStatusCode.InternalServerError);
        return response;
    }
}

As you see, the POST method returns a HttpResponseMessage object with the newly created entity embedded in the body. Location is explicitly added to the message because according to the HTTP specification, the location header has to be added to the response header once a POST request is succeeded. The API checks it and the consuming client will return an error if the location is not added.

To test this method, open Fiddler and switch to composer tab. Select POST method from the dropdown and enter the new employee object in JSON format in the request body as shown:



We can assign any value to Id. It will be ignored as we are calculating the next Id in the logic. Hit the Execute button to invoke the service method. Once you see the success status in fiddler, refresh the browser to see the updated employees list.



Performing Update Operation

Update can be performed using either patch or put verb. The difference between these verbs is patch performs partial update on the resource, whereas put replaces the entire entry with the new object received from the client. One of these or both can be chosen based on the needs of the application.

Following Put method modifies the entry at the provided Id in the Employees collection:


public HttpResponseMessage Put([FromODataUri]int key, [FromBody]Employee employee)
{
    var employeeToDeleteEdit = EmployeesHolder.Employees.Where(e => e.Id == key).FirstOrDefault();
    HttpResponseMessage response;
    string name = employee.Name;
    var salary = employee.Salary;

    int index = EmployeesHolder.Employees.FindIndex(e => e.Id == key);
    if (index >= 0)
    {
        EmployeesHolder.Employees[index].Name = name;
        EmployeesHolder.Employees[index].Salary = salary;
    }
    else
    {
        response = Request.CreateResponse(HttpStatusCode.NotFound);
        return response;
    }

    response = Request.CreateResponse(HttpStatusCode.OK, employee);
    return response;
}


Compose a PUT request on Fiddler as shown below:


Click on the Execute button to invoke the corresponding service method. Once the response is received from the server, refresh the browser to see the updated result.



Performing Delete Operation

Delete is a straight forward operation. It just accepts a key and deletes the object at that entry from the resource.


public void Delete([FromODataUri]int key)
{
    var employeeToDelete = EmployeesHolder.Employees.Where(e => e.Id == key).FirstOrDefault();
    if (employeeToDelete != null)
    {
        EmployeesHolder.Employees.Remove(employeeToDelete);
    }
    else
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}


Just like Post and Put, let’s test this method using Fiddler. Set a delete request as shown below:



Delete doesn’t accept any object in the body. Click the execute button and once the response is received, refresh browser to see the updated values.



As mentioned earlier, ODataController is the low-level class that helps us in building OData service. Web API’s OData assembly includes another controller that handles most of the plumbing like defining API methods and dealing with HttpResponseMessage. It is EntitySetController.We just need to override a set of methods to work with data in the data while using EntitySetController. Most of the tutorials on ASP.NET site use EntitySetController to work with OData.

Happy coding!

7 comments:

  1. I followed this step by step and it all works as expected. However, when I add a Patch method to the API to do partial updates I get a HTTP 406 Not Acceptable on the Patch method. Any idea why?

    ReplyDelete
    Replies
    1. Hi,

      I can't answer unless I see your code. Can you send me code of your patch method using the contact form?

      Delete
    2. I'm also facing same issue.
      Some time throw The remote server returned an error: (415) Unsupported Media Type.

      public string SaveData(string ApiName, string JsonParam)
      {
      try
      {
      HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost:9093/api/" + ApiName + "/");
      req.Method = "POST";
      req.ContentType = "application/json; charset=utf-8";
      req.MediaType = "application/json; charset=utf-8";
      req.Accept = "application/json; charset=utf-8";
      JavaScriptSerializer serializer = new JavaScriptSerializer();
      using (var sw = new StreamWriter(req.GetRequestStream()))
      {
      sw.Write(JsonParam);
      sw.Flush();
      }
      HttpWebResponse rep = (HttpWebResponse)req.GetResponse();
      var outputData = new StreamReader(rep.GetResponseStream()).ReadToEnd();
      return outputData;
      }
      catch (Exception)
      {
      return "ERROR";
      }

      }


      [HttpPost]
      public IHttpActionResult Post([FromBody]HR_EmployeeMaster value)
      {
      HrEmployeeServiceFacadeClient employeeObj = new HrEmployeeServiceFacadeClient();
      string employeeJsonString = JsonConvert.SerializeObject(value);
      if (string.IsNullOrEmpty(Convert.ToString(value.EmployeeId)) || Convert.ToString(value.EmployeeId) == "00000000-0000-0000-0000-000000000000")
      {
      value.EmployeeId = Guid.NewGuid();
      var employeeResponse = employeeObj.Add(value);
      }
      else
      {
      var employeeResponse = employeeObj.Update(value);
      }
      return null;
      }

      Delete
  2. Hi Ravi,

    In my put function response = Request.CreateResponse(HttpStatusCode.OK, employee), I am unable to read the employee on the client side. I am using DataContractJsonSerializer. Let me know if you want me to post my code.

    ReplyDelete
  3. Thanks for the tutorial, nicely explained and easy to understand.

    ReplyDelete
  4. I always visit your blog.it's really informative for us..Thank you.

    Mobile Application Development and Testing Training

    ReplyDelete
  5. BlueHost is one of the best hosting provider for any hosting plans you require.

    ReplyDelete