One of the biggest benefits that you have with ASP.NET Web API is that it gives 100% transparency of the source code because it is open source. You can easily enlist in the code repository and add the symbol path based on instructions at the CodePlex site, then off you go, you can debug the Web API code as if you were the original code author.
Now if you are like me, who like to put a few breakpoints in the source code before hitting F5, you will need to understand the OData stack a little better. If everything goes expected, you might not need those breakpoints. But if anything goes wrong, such as getting a 500 or 400 error, or even a closed connection, understanding the message flow and stopping at the right breakpoints will be very useful to find out the root cause of the issue.
In this blog, I will walk you through those common breakpoints in a very simple scenario. Imagine your client has sent a POST request to your OData service, which was written using those three simple steps that are described in an earlier blog. Your service is self hosted, and your request message looks like the following.
1: POST http://localhost:50231/odata/Customers HTTP/1.1
2: Content-Type: application/json
3: Host: localhost:50231
4: Content-Length: 25
5:
6: {"Id":1,"Name":"Hongmei"}
What exactly happens from the moment this message arrives at the server until the new Customer gets added in the backend? Let us take a closer look.
Stop 1: Parse the incoming URL into OData Path Segments
The very first stop is a custom Route Constraint that parses the incoming request URL into a list of OData Path Segments. Open the file at {git}\src\System.Web.Http.OData\OData\Routing\ODataPathRouteConstraint.cs, and the breakpoint should be placed at the beginning of the Match method. Just like any routing constraint, this one gets executed every time a particular route is evaluated to see if the incoming HTTP request should match.
In this particular case, since the URL is “http://localhost:50231/odata/Customers”, and you have set up your ODataRoute as follows:
1: configuration.Routes.MapODataRoute(routeName:"odataRoute", routePrefix:"odata", model:model);
The incoming request matches the correct base address “http://localhost:50231/” as well as the correct prefix “odata”, the OData route gets picked. And the rest of the path, that is “Customers”, is being processed by the ODataPathHandler, and it matches the “~/entityset” template, where it determines “Customers” is the entity set name. All this information is stored in a data structure called ODataPath, which is a simple collection of OData Path Segments. ODataPath is saved along with the Request.
As part of the Route Constraint, we also figure out the controller name based on a few OData Routing conventions. In this case, we know the {controller} value should be “Customers”, so it is added to the route value collection.
There are many more route templates and routing conventions that are supported out of box, and a future blog will describe each one in great detail.
Stop 2: Select the Controller
After routing, we should take a stop at the Controller selector. Open the file at {git}\src\System.Web.Http\Dispatcher\DefaultHttpControllerSelector.cs, and put a breakpoint at the SelectController method. This controller selector will read the value of the {controller} token out of the route value collection.
In this case, because the controller token is “Customers”, we will search for a type called “CustomersController”. It then instantiates the controller instance, and invokes the controller.ExecuteAsync method to continue executing the rest of the pipeline.
Please note this is vanilla Web API behavior. Web API OData did not need to override the default controller selector at all.
Stop 3: Select the Action
The next stop is to select action. Open the file at {git}\src\System.Web.Http.OData\OData\Routing\ODataActionSelector.cs, and put a breakpoint at the SelectAction method. Just like what happens earlier with controller name, the default OData selector asks OData Routing Conventions to figure out the correct action name to invoke based on the ODataPath.
In this case, because the request is a “POST” message, the convention would try to look for an action called “PostCustomer” first. Since the “PostCustomer” method is not defined in the controller in our simple case, then it falls back to use “Post” as the action name, which was defined as part of EntitySetController.
Stop 4: Deserialize the request message body
Once the action is selected, the next thing is to prepare the inputs for the action before we can invoke the action. That is where formatters are being used.
In this case, since it is an OData service, we will use ODataMediaTypeFormatter. You can open the file at {git}\src\System.Web.Http.OData\OData\Formatter\ODataMediaTypeFormatter.cs, and put a breakpoint at the ReadFromStreamAsync method.
In this case, OData formatter will be able to deserialize the request body into a Customer instance, which becomes the input to the Post method.
Stop 5: Invoke your Action in your Controller
After all this long journey, we have finally arrived at the action, where your code will be executed. Feel free to open the file {git}\src\System.Web.Http.OData\OData\EntitySetController.cs, and put a breakpoint at the “Post” method. Your code which overrides “GetKey” and “CreateEntity” methods will be invoked subsequently.
Stop 6: Serialize the response message body
The last stop, hopefully, is to write out the response. In this case, ODataMediaTypeFormatter is again being selected to actually serialize the content for the response message. Put a breakpoint at the WriteToStreamAsync method, and you will see it writes out the content to the underlying stream. The following is an example of the response.
1: HTTP/1.1 200 OK
2: Content-Length: 108
3: Content-Type: application/json; charset=utf-8
4: Server: Microsoft-HTTPAPI/2.0
5: DataServiceVersion: 3.0
6: Date: Thu, 21 Feb 2013 01:03:45 GMT
7:
8: {
9:"odata.metadata":"http://localhost:50231/odata/$metadata#Customers/@Element","Id":1,"Name":"Hongmei"
10: }
Congratulations, you have just completed the journey of a message flow through various points of a live OData service. There are many other places you can put breakpoints, such as QueryableAttribute in the querying case. If debugging is not an option, you can also turn on tracing for diagnosis. See this for more information on how tracing can be used.
Hopefully, this kind of deep dive will help you understand your OData service better. Happy debugging!