Tuesday, 29 October 2013

Querying ASP.NET Web API OData using Breeze JS

A few days back I blogged about the query options supported by ASP.NET Web API OData. Though we get a number of options to query data from clients through REST-based URLs, building the URLs at runtime is not an easy task. One of the very popular ways to call services from rich JavaScript applications is through jQuery AJAX. While jQuery AJAX abstracts the pain of dealing with browser and configuring parameters, it expects an absolute URL. Writing a set of fixed URLs can be easy, but in larger applications we will have a number of scenarios where we need to build most part of an OData URL based on decisions. We can’t inspect any of the URLs unless a request is sent to them. It would be good to have an abstraction that generates the queries for us.

Breeze JS is the right library in such case. Breeze is built with OData query standards. But it is not limited to querying OData. Breeze can manage complex object graphs, cache data, query cached local objects, save complex objects to server, and perform validations and more. Breeze solves all the problems that one might face while working with data in rich JavaScript applications. In this post, we will see how Breeze simplifies querying OData services. We will explore some other essential features Breeze in future posts. To learn more about Breeze, make sure to check their official documentation and interactive tutorials on Breeze website.

Breeze uses jQuery for AJAX and Q for promises. Breeze needs data.js to talk to OData sources. Getting these scripts in Visual Studio is easy through the following NuGet packages:

Now add references to these libraries on the page.

<script src="~/Scripts/jquery-1.9.1.min.js"></script>
<script src="~/Scripts/datajs-1.1.1.min.js"></script>
<script src="~/Scripts/q.min.js"></script>
<script src="~/Scripts/breeze.min.js"></script>

Breeze heavily uses metadata of the data structure on which it has to work. ASP.NET Web API OData service exposes the metadata through its endpoint. But we need to set an important property to the OData configuration, it is Namespace. Adding the following statement to the Web API OData endpoint configuration:
modelBuilder.Namespace = "WebAPI_EF_OData.Models";

Make sure to check Brian Noyes’ step-by-step tutorial on Consuming ASP.NET Web API OData using Breeze. Brian does a nice job by explaining each step in detail.

Let’s set up Breeze on the client side. Add the following code to the page in which you want to perform breeze operations:

$(function () {
    var baseAddress = "/odata";
    breeze.config.initializeAdapterInstances({ dataService: "OData" });
    var manager = new breeze.EntityManager(baseAddress);

Now we can start using Breeze’s querying capabilities against the OData endpoint. Breeze uses LINQ-like operators to specify conditions on the data source. Following snippet shows a basic Breeze request to the EntitySet Customers and captures the response once it gets from the server:
var query = breeze.EntityQuery.from("Customers");
manager.executeQuery(query, function (data) {
    //Manipulate UI
}, function (err) {
    //Show Error Message

The above query sends a request to the URL http://localhost:/odata/Customers. Following query applies a simple condition on the above query:
var queryWithCondition = breeze.EntityQuery.from("Customers")
                                           .where("ContactTitle", "equals", "Owner");

This query corresponds to the URL http://localhost:/odata/Customers?$filter=ContactTitle eq 'Owner'. As stated earlier, Breeze is capable of expressing any OData URL. It has operators defined for orderby, checking length of strings, substrings, date operators, querying data as pages, expanding navigation properties and many others. Following listing shows a set of OData URLs and their corresponding Breeze queries.
// http://localhost:<port-no>/odata/Customers?$filter=startswith(ContactName,'Ana') eq true
var queryStartsWith = breeze.EntityQuery.from("Customers")
                                        .where("ContactName", "startsWith", "Ana");

// http://localhost:<port-no>/odata/Customers?$filter= not startswith(ContactName,'Ana') eq true
var Predicate = breeze.Predicate;
var predicate = new Predicate("ContactName", "startsWith", "Ana").not();
var queryNotStartsWith = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$filter=substringof('ill',CompanyName) eq true
var queryContainsSubstring = breeze.EntityQuery.from("Customers")
                                               .where("CompanyName", "contains", "ill");

// http://localhost:<port-no>/odata/Customers?$filter=length(ContactName) gt 10 and length(ContactName) lt 20
var queryCheckingLength = breeze.EntityQuery.from("Customers")
                                                           .where("length(ContactName)", "greaterThan", "10")
                                                            .where("length(ContactName)", "lessThan", "20");

// http://localhost:<port-no>/odata/Customers?$orderby=Country
var queryOrderBy = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$top=10
var queryTop10 = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$skip=40&$top=10
var queryTopAndSkip = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$inlinecount=allpages
var queryInlineCount = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$expand=Orders/Employee
var queryExpand = breeze.EntityQuery.from("Customers")

// http://localhost:<port-no>/odata/Customers?$select=CustomerID,ContactName
var querySelect = breeze.EntityQuery.from("Customers")
                                    .select("CustomerID, ContactName");

This is not the end. I created this list as it will serve as a one stop reference for me and hopefully for you as well! Most of the operators look and behave like LINQ operators. Check API documentation on the official site to get details on each of the operators used above.

Happy coding!

1 comment:

Note: only a member of this blog may post a comment.