Saturday, September 13, 2014

Step by step WebAPI OData v4.0 RESTful HTTP Service with PUT PATCH support


In this post we'll create an OData RESTful  Service using an ODataController, based on the WebAPI architecture of Asp.Net MVC , with support for the updating CRUD operations (HTTP PUT & HTTP PATCH), complying to OData v4.0 .
This is the continuation of the previous article that included only GET, POST and DELETE operations, and can be found here. A smaller tutorial about how to create a Web API OData v3 Service with support for only Retrieve operations (HTTP GET), and inheriting from ApiController, can be seen in this article.

For this post, we'll upgrade that previous tutorial, in which we built from scratch an MVC Web API as a RESTful OData v4.0 service, handling HTTP  GET, HTTP POST, and HTTP DELETE requests.   
Therefore, you must follow the previous tutorial to build in 4 steps an entire OData service.

REST is a web architecture that handles requests according to its HTTP  methods : GET will allow reading data, HTTP POST allows saving a new item, PUT is for updating ALL the properties of some  entity, while HTTP PATCH is for updating partially one.   HTTP  DELETE is for removing an  item.
The routing conventions for Asp.Net Web API  OData v4 can be found in the Asp.Net official site . At the previous article we used as data model an XML file  ,  and exposed it by means of the OData protocol , supporting sorting ($orderby)  and paging ($skip & $top) :

mvc-webapi-odata-v40-odatacontroller-restful-put-patch-service

In the previous tutorial, we built from scratch an RESTful OData v4 Web API following just 4 steps:
1) we created an MVC app & install the Web API and OData assemblies
2) we created the data Model;
3) we configured the OData Endpoint at the Register() in the WebApiConfig;
4) finally, we created an ODataController and set the "EnableQuery" attribute over the Action Methods.
In this article, we continue the Step #4, coding two updating methods (PUT & PATCH).


WebAPI OData v4.0 RESTful HTTP Service with PUT PATCH support


But before you step in, check that you have the required assemblies. You need to UPDATE the existing Web API references, so open the NuGet Console and type:

Update-Package Microsoft.AspNet.WebApi -Pre


Next, install the OData NuGet package by typing:

Install-Package Microsoft.AspNet.Odata



Also, check that you have the required configuration. Set the OData Endpoint at the WebApiConfig Register() method, as follows:


public static void Register(HttpConfiguration config)
        {
            
            ODataModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Note>("Notes");
            config.MapODataServiceRoute(
                routeName: "ODataRoute",
                routePrefix: "OData",
                model: builder.GetEdmModel());          

        }

The "routePrefix" will state the name of the OData Service Endpoint. Here we use the ODataConventionModelBuilder to create the service's metadata , but you can customize the Model adding keys or properties , using the ODataModelBuilder instead.

Also, add the following code line at the end of the Global.asax Application_Start:
GlobalConfiguration.Configuration.EnsureInitialized(); 



Append to your ODataController the Updating Action Methods : 

Next we will add to the ODataController 2 Action Methods:
1) Put() for handling an HTTP PUT requests
2) Patch() for handling HTTP PATCH requests

But previously i'd like to remark some important points:

Important :  In the following codes is mandatory that you call "key" to the Action method's parameters containing the IDs of the entities to be fetched. This convention is very important and must be respected.

Important:   Notice that the Action method's names are named after the HTTP verbs : as Get handles HTTP GET, then Put handles HTTP PUT, Patch handles HTTP PATCH.....

You will notice that we mark the Action Method as "EnableQuery"? That's because that's the key that enables the OData Service on them:




1) HTTP PUT verb :


We modify the data at the XML Repository, using this method, which is supposed to get a request containing a full entity with all its properties. Write the following code inside your ODataController :



public IHttpActionResult Put([FromODataUri] int key, [FromBody] Note note)
        {
            if (note == null || !ModelState.IsValid)
            {
                string errors = ModelState.Keys
                    .SelectMany(k => this.ModelState[k].Errors)
                    .Select(e => e.ErrorMessage)
                    .Aggregate((i,j) => i + " , " + j);
                return BadRequest(errors);
            }
            Repository.Update(note);
            return StatusCode(HttpStatusCode.Accepted);
        }


At the code above, notice the important points that i remarked with red and green.
We get the ID of the record to update, through the "key" parameter at the ODataURI, and the object through the Note in the request body ("[FromBody]").
If the Model is not valid, we concatenate all the errors in a string and send it inside an 500 Bad Request response status code. If everything is OK, we send an "Accepted" (202) HTTP status code.
Build  the service.
Send an HTTP PUT request according to the OData protocol. We create a PUT request with Fiddler (a short tutorial about using Fiddler can be found here), and send a JSON object in the Request Body. Notice we have to set the "Content-Type" to JSON at the request header.
Notice that we send an OData URI according to OData protocol notation ("/Notes(8)"), and a body containing a JSON object.


 The request is not consistent with the Data Annotations we set, then it will be rejected. The request was rejected , and we got an "Bad Request" response with the error message informing us what was illegal.
This is because we use Data Annotations in our Model:

As you see, in our example both errors were rendered inside the response.
Now we send a legal entity, which will be accepted:



Now, the request is a legal one, then the Model is valid , and a 202 HTTP code ("Accepted")  is rendered:



Important : because the OData protocol is case-sensitive, remember to take care about the text included in the URI, meaning that if for example you write "notes" instead of "Notes", you won't get the required data:





2) HTTP PATCH verb : 

Let's see a PARTIAL update using HTTP PATCH. Now we write a Patch() Action Method as follows:



(COPY-PASTE THIS CODE) : 

 [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
        public IHttpActionResult Patch([FromBody] Delta<Note> note)
        {
            if (note == null)
            {
                return StatusCode(HttpStatusCode.BadRequest);
            }
            Note original = Repository.Get(note.GetEntity().ID);
            note.Patch(original);

            string errors = String.Empty;
            var modelValidator = Configuration.Services.GetBodyModelValidator();
            var metadataProvider = Configuration.Services.GetModelMetadataProvider();
            bool isValid = modelValidator.Validate(original, typeof(Note), 
                metadataProvider, this.ActionContext, String.Empty);
            if (!isValid)
            {
                errors = this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors)
                        .Select(error => error.ErrorMessage).Aggregate((i, j) => i + ", " + j);
            }
            else
            {
                Repository.Update(original);
            }
            return isValid ? StatusCode(HttpStatusCode.OK) as IHttpActionResult :
                  BadRequest(errors);

        }



First we get the entity to update from database. Notice we don't need the ID of the entity as the "key" parameter: that is to show you that is not necessary, since we got it inside the Delta, but only if we get it as one of the Delta properties. If we don't , there will be no ID, and an error will be raised.
Then we call the Patch() method, which takes care automatically of doing the update of the modified properties:


Then, there is a validation code which usually is automatically held by MVC, and is resumed as the "IsValid" boolean property of the ModelState class.
Why do we validate the entity inside the code?
Because the ModelState IsValid validation method is not working!!! Why it does not validate the Model?
Because we got a "Delta" object, that means, a PARTIAL object, which by definition is not a valid
Model (is partial!!! (so it has for example required fields missing)):
We use the BodyModelValidator's Validate() method with a ModelMetadataProvider, to check whether the Delta is valid, and compalins with our Data Annotations. If it 's not, we compose a string with the concatenated errors found. Then we return a BadRequest status together with the errors:


To test our ODataController, we create an HTTP PATCH request with Fiddler , and send a JSON entity in the Request Body. Of course we set the "Content-Type" to JSON at the request's header:




We get a response containing an OK status, because our Patch() renders an IHttpActionResult using the OK (200) StatusCode().
But if we send a PATCH request which is not consistent with the Data Annotations that we stated, it will be rejected:



 The request was rejected , and we got an 400 status code "Bad Request" response with the error message informing us of what was illegal:







That's all
In this post we've seen how to create an OData v4.0  RESTful HTTP Service with support for HTTP PATCH & HTTP PUT verbs for CRUD  operations using a Web API Asp.Net MVC application and an ODataController. 

Enjoy programming.....
    By Carmel Shvartzman
כתב: כרמל שוורצמן


No comments:

Post a Comment