This is the third part of Building OData Service using Asp.Net Web API. The topics we’ll cover are:
- OData Introduction and Querying Existing OData Service – Part 1.
- Create read-only OData endpoint using Asp.Net Web API – Part 2.
- CRUD Operations on OData endpoint using Asp.Net Web API – Part 3 (This Post).
- Consuming OData Service using AngularJS and Breeze.js – Part 4 (Coming Soon).
CRUD Operations on OData endpoint using Asp.Net Web API
In this post We’ll add another OData controller which will support all the CRUD operations, as we talked before OData follows the conventions of HTTP and REST; so if we want to create a resources we’ll issue HTTP POST, if we want to delete a resource we’ll issue HTTP DELETE and so on. One more thing we want to add support for is partial updates (HTTP PATCH) which will be very efficient when we update certain properties on the entity, the request payload for PATCH will come on a key/value pair and will contain the properties changed only.
Step 1: Add OData Controller (Support for CRUD operations)
Let’s add a new Web API Controller which will handle all HTTP requests issued against the OData URI “/odata/Tutors”. To do this right-click on Controllers folder->Select Add->Name the controller “TutorsController” and choose “Empty API Controller” Template.
As we did before we have to derive our “TutorsController” from class “EntitySetController“, then we’ll add support for the Get() and GetEntityByKey(int key) methods and will implement creating a new Tutor; so when the client issue HTTP POST request to the URI “/odata/Tutors” the “CreateEntity(Tutor entity)” method will be responsible to handle the request. The code will look as the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class TutorsController : EntitySetController<Tutor, int> { LearningContext ctx = new LearningContext(); [Queryable()] public override IQueryable<Tutor> Get() { return ctx.Tutors.AsQueryable(); } protected override Tutor GetEntityByKey(int key) { return ctx.Tutors.Find(key); } protected override int GetKey(Tutor entity) { return entity.Id; } protected override Tutor CreateEntity(Tutor entity) { Tutor insertedTutor = entity; insertedTutor.UserName = string.Format("{0}.{1}",entity.FirstName, entity.LastName); insertedTutor.Password = Helpers.RandomString(8); ctx.Tutors.Add(insertedTutor); ctx.SaveChanges(); return entity; } } |
By looking at the code above you’ll notice that we overrided the CreateEntity() method which accepts a strongly typed Tutor entity, the nice thing here that once you override the CreateEntity() method the base class “EntitySetController” will be responsible of returning the right HTTP response message (201) and adding the correct location header for the created resource. To test this out let’s issue issue an HTTP POST request to URI “odata/Tutors”, the Post request will look as the below:
Step 2: Testing OData Prefer Header
By looking at the request above you will notice that status code is 201 created is returned as well the response body contains the newly created Tutor, but what if the client do not want to return a duplicate entity of the newly created Tutor from the server? The nice thing here that OData allow us to send Prefer header with request to indicate if we need to return the created entity back to the client. The default value for the header is “return-content” so if we tried to issue another POST request and pass the Prefer header with value “return-no-content” the server will create the resource and will return 204 no content response.
Step 3: Add Support for Resource Deletion
Adding support for delete is simple, we need to override method Delete(int key) so any HTTP DELETE request sent to the URI “/odata/Tutors “will be handled by this method, let’s implement this in the code below:
1 2 3 4 5 6 7 8 9 10 11 |
public override void Delete(int key) { var tutor = ctx.Tutors.Find(key); if (tutor == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } ctx.Tutors.Remove(tutor); ctx.SaveChanges(); } |
To test this out we need to issue HTTP DELETE request to the URI “OData/Tutors(12)” and if the tutor exists the HTTP response will be 204 no content. What worth mentioning here that if we tried to delete a tutor which doesn’t exists then by looking at code on line 6 you will notice that I’m throwing HttpResponseException with status code 404 along with empty response body; nothing wrong about this but if you want to build OData compliant service then handling the errors should be done on a different way; to fix this we need to return an HttpResponseException with an object of type “Microsoft.Data.OData.ODataError” in the response body.
Step 4: Return Complaint OData Errors
To fix the issue above I’ll create a helper class which will return ODataError, we’ll use this class in different methods and for another controllers, so add new class named “Helpers” and paste the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static class Helpers { public static HttpResponseException ResourceNotFoundError(HttpRequestMessage request) { HttpResponseException httpException; HttpResponseMessage response; ODataError error; error = new ODataError { Message = "Resource Not Found - 404", ErrorCode = "NotFound" }; response = request.CreateResponse(HttpStatusCode.NotFound, error); httpException = new HttpResponseException(response); return httpException; } } |
By looking at the code above you will notice that there is nothing special here, we only returning an object of ODataError in the response body, you can set the Message and ErrorCode properties for something meaningful for your OData service clients. Now insted of throwing the exception directly in the controller we’ll call this helper method as the code below:
1 2 3 4 |
if (tutor == null) { throw Helpers.ResourceNotFoundError(Request); } |
Step 5: Add Support for Partial Updates (PATCH)
In cases we want to update 1 or 2 properties of a resource which has 30 properties; it will be very efficient if we are able to send only over the wire the properties which have been changed, as well it will be very efficient on the database level if we generated an update statement which contains the update fields only. To achieve this we need to override the method PatchEntity() as the code below:
1 2 3 4 5 6 7 8 9 10 11 |
protected override Tutor PatchEntity(int key, Delta<Tutor> patch) { var tutor = ctx.Tutors.Find(key); if (tutor == null) { throw Helpers.ResourceNotFoundError(Request); } patch.Patch(tutor); ctx.SaveChanges(); return tutor; } |
As I mentioned before the changed properties will be in key/value form in the response body thanks to the Delta<T> OData class which makes it easy to perform partial updates on any entity, to test this out we’ll issue HTTP Patch request to the URI “/odata/Tutors(10)” where we will modify the LastName property only for this Tutor, request will be as the below:
Before we send the request Let’s open SQL Server Profile to monitor the database query which will be generated to update this Tutor, you will notice from the image below how efficient is using the Patch update when we want to modify certain properties on an entity, the query generated do an update only for the changed property.
In the next post we’ll see how we can consume this OData service from Single Page Application built using AngularJS and Breeze.js
Please drop your questions and comments on the comments section below.
Hi! Thanx for this post. But I have a problem. Do you know why the “entity” parameter in method CreateEntity is null, when I try to call service to create new entity?
This is object that I post from javascript:
{
EmployeeBirth: “2014-04-24”
EmployeeName: “qwe”
EmployeeSalary: 111}
This is DAL class:
[Serializable]
[DataContract]
public class Employee
{
[DataMember]
public int EmployeeId { get; set; }
[DataMember]
public string EmployeeName { get; set; }
[DataMember]
public DateTime EmployeeBirth { get; set; }
[DataMember]
public decimal EmployeeSalary { get; set; }
}
protected override Employee CreateEntity(Employee entity)
{
var empl = entity;//always null
///…///
}
Hello Salva,
I recommend you to move back a step and try to issue POST request using fiddler, do not forget to set Content-Type header to “application/json” and your request body should look like the below:
{
“EmployeeBirth”: “2014-04-23”,
“EmployeeName”: “qwe”,
“EmployeeSalary”: 111
}
If the above work then ensure that you set the right Content-Type header when you issue POST request in JS, as well check your data format in JS object.
Let me know if this helps.
Hi, Taiseer! The problem was in JS object as you said. For successful passing a date property of any object to OData controller I need to do this:
var date = new Date();
date.setTime(Date.parse(employee.EmployeeBirth));
employee.EmployeeBirth = date.toJSON();
$http.post(‘odata/Employees’, employee); (angularjs thing)
That’s the way the cookie crumbles! 🙂
Thanx for answer!
You are welcome and thanks for sharing the answer 🙂
Hi Taiseer, Thanks for the wonderful post.
A small query on POST.
Taking your example, if a new course needs to be created using existing TutorID and SubjectID, how can this be acheived?
Whenever SaveChanges method is called, it expects all foreign keys objects and creates a new records in Tutor and Subject table.
In your web api tutorial, I came across with “parse” method inside ModelFactory class. Can anything similar be used here?
Hi Taiseer, thanks for this tutorial, once again very clear and works well.
I noticed this doesn’t use the repository/model concept from the WebAPI tutorial,. Are these still relevant when using OData? If so, how would they fit into your example?
Many thanks
Hello Jeremy,
Apologies for the late reply, well you can use the repository pattern with OData endpoints for sure. Mainly you can use the repository pattern for creating an abstraction between your domain objects and data layer. Hope this answers your question.
Just want to say thank you for all your Web API tutorials. I like them so much and they are helpful. I prefer tutorials in text instead of videos. Keep up the good work! 😀
Thank you, really glad this was useful for you 🙂 subscribe to blog to keep updated.
Very good tutorial,look forward for consume OData service using AungularJs tutorial
Glad you liked it, will blog the last part soon.
Just curious, did you ever do the final part about Angular & OData?
Hi, not yet to be honest, but I’ll consider completing this tutorial soon. Too many things in the pipeline 🙁
A helpfull tutorial (as are all of your tutorials). Looking forward to the AngularJS part 4.
Hi, sir. Thank you for all your awesome tutorials!! (:
I just have a small question. Is there a way to add support for PATCH with traditional ApiController?
You are welcome Khai. I didn’t do it my self but yes sure there is way to implement patch, check this SO answer, hope this helps.
thank you, sir
Any plan soon for Part 4 of this series. Thanks and great blog
I will be lying if I said yes 🙂 I’m now working on another tutorial related to Angular, so keep tuned.
Hi Taiseer,
Thanks so much for the demo. Great learning tool. I’m having a problem. When I compile to the solution no database is created. I changed to connection string to connect to my MSSQL 2012 database server and still noting is being created when I compile the solution. Any help would be appreciated.
Regards,
Kai Wen
Hi Kai, the DB will be created once you do first hit to the DB not compilation, like calling DbContext.Students.ToList() as well make sure that initialization strategy is set correctly.
Hi Taiseer,
Not really sure what the problem is, but I’m still not able to run the app. I unzipped the demo code again and open the solution in VS2013 and I’m not able to get the site to run. I’m not making any code or web.config file changes. I made the Learning.Web project the start up site. Any help would be awesome. The code compiles with no errors.
Regards,
Kevin
Hi Kevin,
What do you mean the site is not running? You can not issue requests? Is there any errors after you execute the request?
Hi Taiseer,
I should have been more specific. Yes, I’m not able to issue any request. I don’t get any compile errors at all.
Kevin
Hi Taiseer,
Looks as if it a problem with the CreateEntity method found in the TutorsController.cs file.
Getting an error on the following line of code:
insertedTutor.UserName = string.Format(“{0}.{1}”,entity.FirstName, entity.LastName);
I’m wondering if this is because the database is not being created.
Kevin
Hey Taiseer,
I finally got the the code to work. I think my biggest problem was the database was not getting created for some reason. I changed the connection string to point to my local instance of MSSQL 2012 and everything works. Once I learn this I’m going to take a look at the password \ account demo you have posted. This is just want I need and what I’m trying to learn.
Best Regards,
Kevin
eagerly waiting for part 4