How to build a Simple OData-based AJAX application with Visual Studio 2010

16 September 2012

JSLQ, OData, Ajax, featured post

By completing this tutorial you will be able to consume an OData service from JavaScript with the elegance and simplicity of the JavaScript Language Query, without ever writing down an $.ajax statement or manually building OData query strings or payloads.

 

Notes:

  • This howto is built with the Northwind sample database, wrapped in an EntityFramework 4.0 EDMX and published with a WCF Data Service. You can grab this example projectfrom CodePlex.
  • This walkthrough intentionally lacks the use of any kind of view or template engines to keep the material focused on its main topic: accessing and manipulating data with JayData. For an example on how to use knockoutjs with JayData check this blog or this video or download the knockoutJS integration example from CodePlex.

 

Why use JayData?

 

With JayData you can work with online data in JavaScript on a way that was previously only possible for .NET and Silverlight developers. Instead of approaching the OData (or other storage) endpoint from a protocol level with handcrafted query strings and HTTP requests fired off from the middle of an event handler, JayData provides a typed abstraction layer: the EntityContext with typed EntitySets and Entities, and the native syntax of JavaScript for querying and modifying data.

 

Before we begin, see it in action below:

 

One possible way

The JayData way
JavaScript Language Query (JSLQ)

 

Get ALL Categories!

 
$.ajax({
   url: '/northwind.svc'+ '/Categories',
   dataType: 'JSON',
   success: function (result) {
 
  var data;
   if ((typeof result === 'object')
           && (('d' in result)
           && ('results' in result.d))) {
    data = result.d.results;
   } else {
   data = result.d || result;
   }
 
   if (Array.isArray(data)) {
    for (var i = 0; i < data.length; i++) {
      var item = data[i]; $('#queryResult')
           .append('<li>'
            + item["Category_Name"] 
            + '</li>');
    }
   } else { 
     $('#queryResult').append(data); 
   }
}});
 
 
 
 
 
 
  
 
 
 
  northwindContext
    .Categories
    .forEach(function (category) {
        $('#categories').append('<li>' + 
        category.Category_Name + '</li>'); });
 
 
 
 
 
 
 
 

 

 

Get ONLY the product names from the list of products that are either Beverages (Category_Id = 1) or contain tofu in the product name regardless of the case,

sorted by the product name.

var queryString = ("/Northwind.svc/Products?" 
    + "$filter=((Category/Category_ID eq 1)or" 
    + "substringof('tofu',tolower" 
    +"(Product_Name)))&" 
    + "$orderby=Category/Category_Name&" 
    + "$select=Product_Name");

$.ajax({ 
    url: queryString, 
    dataType: 'JSON', 
    success: function (result) { 
        var data; 
        
        if ((typeof result === 'object')
   && (('d' in result)
    && ('results' in result.d))) {
data = result.d.results;
        } else { 
            data = result.d || result; 
        } 
        
        if (Array.isArray(data)) { 
         for (var i = 0; i < data.length; i++) {
             var item = data[i][“Product_Name”];
             ...
          }  } else { 
   $('#products').append(data); 
  }
}
});
 
 
 
 
 
 
 
 
 northwindContext
  .Products
  .filter(function (product) { return
      product.Category.Category_ID == 1 ||
      product.Product_Name
.toLowerCase().contains('tofu') }) .orderBy(“it.Category.Category_Name”)
  .map(function(p) { return p.Product_Name } )
  .forEach(function (productName) { ... } );
 
 
 
  
 
 
 
 
 
 
 
 
 

 

 

 

A typed OData context provides intellisense on the Entity and EntitySet level.

You can generate such a context with the JaySvcUtil.exe, we will talk this about later.

image_thumb[17]

 

CategoryId? CategoryID? or Category_ID?

Intellisense list of entity fields comes in handy in a number of times.

image_thumb[16]

-

 

The fluent API provides for simple, readable code

...that is easy to maintain even in the long run

image

 

 

 

How to setup the Northwind OData demo environemnt?

The Northwind OData demo environment can be downloaded from the http://examples.jaydata.org/download page or from this link at CodePlex.

 

1) Open Visual Studio 2010 and create a new Empty Web Application, name it NorthwindDemo.

 

image

 

 

2) Add ASP.NET folder App_Data

3) Drag the Northwind.sdf sample database from

C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Samples into the App_Data folder.

 

image

 

4) Add a new ADO.NET Entity Data Model and name it Northwind

 

image

 

5) Choose Generate from database

 

image

 

6) Accept the default selection of the data connection

If you can not see Northwind.sdf like in the picture then make sure you have SQL Server CE 4.0 support installed. Also you might need to open the Northwind.sdf database with a double click to have it appear in the list.image

 

7) Include all the tables and press Finish to get an EDMX (that is not so nicely arranged then mine)

 

image

 

image

 

8) Add a new WCF Data Service and name it Northwind.svc

image

 

9) Open the Northwind.svc file and edit its contents following the comments in the code:

 

namespace NorthwindDemo
{
    public class Northwind : DataService<NorthwindEntities>
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

 

10) Right click on the Northwind.svc and select “View in Browser”. Congratulation, you have and operational OData service.

 

image

 

Getting JayData and preparing our HTML5 application

 

1) Open Tools > Library Package Manager > Package Manager Console, and type

 

2) Install-Package JayData

 

image

 

3) The  package contains the JayData JavaScript library (and its dependencies) and the JaySvcUtil.exe command line tool.

 

image

 

4) Use the JaySvcUtil to generate Northwind.js from the service metadata uri: /Northwind.svc/$metadata

C:\JayDataDemo\NorthwindDemo\JaySvcUtil -m http://yoursite:yourport/Northwind.svc/$metadata 
-o Northwind.js

The syntax for JaysvcUtil is blogged about here.

image

 

Northwind.js is generated in you web project folder. You may have to switch “show all files” in the solution tree to be able to access Northwind.js from Visual Studio.

 

For those with deep passion in their heart toward JavaScript, here is what gets generated. Others may freely skip.


//////////////////////////////////////////////////////////////////////////////////////
////// Autogenerated by JaySvcUtil 1.0.2, copyright http://JayData.org/licensing
//////////////////////////////////////////////////////////////////////////////////////

(function(global, $data, undefined) {




function registerEdmTypes() {

function Edm_Boolean() { };
$data.Container.registerType('Edm.Boolean', Edm_Boolean);
$data.Container.mapType(Edm_Boolean, $data.Boolean);

function Edm_Binary() { };
$data.Container.registerType('Edm.Binary', Edm_Binary);
$data.Container.mapType(Edm_Binary, $data.Blob);

function Edm_DateTime() { };
$data.Container.registerType('Edm.DateTime', Edm_DateTime);
$data.Container.mapType(Edm_DateTime, $data.Date);

function Edm_DateTimeOffset() { };
$data.Container.registerType('Edm.DateTimeOffset', Edm_DateTimeOffset);
$data.Container.mapType(Edm_DateTimeOffset, $data.Integer);

function Edm_Time() { };
$data.Container.registerType('Edm.Time', Edm_Time);
$data.Container.mapType(Edm_Time, $data.Integer);

function Edm_Decimal() { };
$data.Container.registerType('Edm.Decimal', Edm_Decimal);
$data.Container.mapType(Edm_Decimal, $data.Number);

function Edm_Single() { };
$data.Container.registerType('Edm.Single', Edm_Single);
$data.Container.mapType(Edm_Single, $data.Number);

function Edm_Double() { };
$data.Container.registerType('Edm.Double', Edm_Double);
$data.Container.mapType(Edm_Double, $data.Number);

function Edm_Guid() { };
$data.Container.registerType('Edm.Guid', Edm_Guid);
$data.Container.mapType(Edm_Guid, $data.String);

function Edm_Int16() { };
$data.Container.registerType('Edm.Int16', Edm_Int16);
$data.Container.mapType(Edm_Int16, $data.Integer);

function Edm_Int32() { };
$data.Container.registerType('Edm.Int32', Edm_Int32);
$data.Container.mapType(Edm_Int32, $data.Integer);

function Edm_Int64() { };
$data.Container.registerType('Edm.Int64', Edm_Int64);
$data.Container.mapType(Edm_Int64, $data.Integer);

function Edm_Byte() { };
$data.Container.registerType('Edm.Byte', Edm_Byte);
$data.Container.mapType(Edm_Byte, $data.Integer);

function Edm_String() { };
$data.Container.registerType('Edm.String', Edm_String);
$data.Container.mapType(Edm_String, $data.String);

};
registerEdmTypes();
$data.Entity.extend('NorthwindModel.Category', {
'Category_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true },
'Category_Name': { type:'Edm.String',nullable:false,required:true,maxLength:15 },
'Description': { type:'Edm.String',nullable:true,maxLength:Number.POSITIVE_INFINITY },
'Picture': { type:'Edm.Binary',nullable:true,maxLength:Number.POSITIVE_INFINITY },
'Products': { type:'Array',elementType:'NorthwindModel.Product',inverseProperty:'Category' }
});
$data.Entity.extend('NorthwindModel.Customer', {
'Customer_ID': { key:true,type:'Edm.String',nullable:false,required:true,maxLength:5 },
'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 },
'Contact_Name': { type:'Edm.String',nullable:true,maxLength:30 },
'Contact_Title': { type:'Edm.String',nullable:true,maxLength:30 },
'Address': { type:'Edm.String',nullable:true,maxLength:60 },
'City': { type:'Edm.String',nullable:true,maxLength:15 },
'Region': { type:'Edm.String',nullable:true,maxLength:15 },
'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 },
'Country': { type:'Edm.String',nullable:true,maxLength:15 },
'Phone': { type:'Edm.String',nullable:true,maxLength:24 },
'Fax': { type:'Edm.String',nullable:true,maxLength:24 },
'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Customer' }
});
$data.Entity.extend('NorthwindModel.Employee', {
'Employee_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true },
'Last_Name': { type:'Edm.String',nullable:false,required:true,maxLength:20 },
'First_Name': { type:'Edm.String',nullable:false,required:true,maxLength:10 },
'Title': { type:'Edm.String',nullable:true,maxLength:30 },
'Birth_Date': { type:'Edm.DateTime',nullable:true },
'Hire_Date': { type:'Edm.DateTime',nullable:true },
'Address': { type:'Edm.String',nullable:true,maxLength:60 },
'City': { type:'Edm.String',nullable:true,maxLength:15 },
'Region': { type:'Edm.String',nullable:true,maxLength:15 },
'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 },
'Country': { type:'Edm.String',nullable:true,maxLength:15 },
'Home_Phone': { type:'Edm.String',nullable:true,maxLength:24 },
'Extension': { type:'Edm.String',nullable:true,maxLength:4 },
'Photo': { type:'Edm.Binary',nullable:true,maxLength:Number.POSITIVE_INFINITY },
'Notes': { type:'Edm.String',nullable:true,maxLength:Number.POSITIVE_INFINITY },
'Reports_To': { type:'Edm.Int32',nullable:true },
'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Employee' }
});
$data.Entity.extend('NorthwindModel.Order_Detail', {
'Order_ID': { key:true,type:'Edm.Int32',nullable:false,required:true },
'Product_ID': { key:true,type:'Edm.Int32',nullable:false,required:true },
'Unit_Price': { type:'Edm.Decimal',nullable:false,required:true },
'Quantity': { type:'Edm.Int16',nullable:false,required:true },
'Discount': { type:'Edm.Single',nullable:false,required:true },
'Product': { type:'NorthwindModel.Product',required:true,inverseProperty:'Order_Details' },
'Order': { type:'NorthwindModel.Order',required:true,inverseProperty:'Order_Details' }
});
$data.Entity.extend('NorthwindModel.Order', {
'Order_ID': { key:true,type:'Edm.Int32',nullable:false,required:true },
'Customer_ID': { type:'Edm.String',nullable:false,required:true,maxLength:5 },
'Employee_ID': { type:'Edm.Int32',nullable:true },
'Ship_Name': { type:'Edm.String',nullable:true,maxLength:40 },
'Ship_Address': { type:'Edm.String',nullable:true,maxLength:60 },
'Ship_City': { type:'Edm.String',nullable:true,maxLength:15 },
'Ship_Region': { type:'Edm.String',nullable:true,maxLength:15 },
'Ship_Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 },
'Ship_Country': { type:'Edm.String',nullable:true,maxLength:15 },
'Ship_Via': { type:'Edm.Int32',nullable:true },
'Order_Date': { type:'Edm.DateTime',nullable:true },
'Required_Date': { type:'Edm.DateTime',nullable:true },
'Shipped_Date': { type:'Edm.DateTime',nullable:true },
'Freight': { type:'Edm.Decimal',nullable:true },
'Customer': { type:'NorthwindModel.Customer',required:true,inverseProperty:'Orders' },
'Employee': { type:'NorthwindModel.Employee',inverseProperty:'Orders' },
'Order_Details': { type:'Array',elementType:'NorthwindModel.Order_Detail',inverseProperty:'Order' },
'Shipper': { type:'NorthwindModel.Shipper',inverseProperty:'Orders' }
});
$data.Entity.extend('NorthwindModel.Product', {
'Product_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true },
'Supplier_ID': { type:'Edm.Int32',nullable:true },
'Category_ID': { type:'Edm.Int32',nullable:true },
'Product_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 },
'English_Name': { type:'Edm.String',nullable:true,maxLength:40 },
'Quantity_Per_Unit': { type:'Edm.String',nullable:true,maxLength:20 },
'Unit_Price': { type:'Edm.Decimal',nullable:true },
'Units_In_Stock': { type:'Edm.Int16',nullable:true },
'Units_On_Order': { type:'Edm.Int16',nullable:true },
'Reorder_Level': { type:'Edm.Int16',nullable:true },
'Discontinued': { type:'Edm.Boolean',nullable:false,required:true },
'Category': { type:'NorthwindModel.Category',inverseProperty:'Products' },
'Order_Details': { type:'Array',elementType:'NorthwindModel.Order_Detail',inverseProperty:'Product' },
'Supplier': { type:'NorthwindModel.Supplier',inverseProperty:'Products' }
});
$data.Entity.extend('NorthwindModel.Shipper', {
'Shipper_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true },
'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 },
'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Shipper' }
});
$data.Entity.extend('NorthwindModel.Supplier', {
'Supplier_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true },
'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 },
'Contact_Name': { type:'Edm.String',nullable:true,maxLength:30 },
'Contact_Title': { type:'Edm.String',nullable:true,maxLength:30 },
'Address': { type:'Edm.String',nullable:true,maxLength:60 },
'City': { type:'Edm.String',nullable:true,maxLength:15 },
'Region': { type:'Edm.String',nullable:true,maxLength:15 },
'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 },
'Country': { type:'Edm.String',nullable:true,maxLength:15 },
'Phone': { type:'Edm.String',nullable:true,maxLength:24 },
'Fax': { type:'Edm.String',nullable:true,maxLength:24 },
'Products': { type:'Array',elementType:'NorthwindModel.Product',inverseProperty:'Supplier' }
});
$data.EntityContext.extend('NorthwindDemo.NorthwindEntities', {
Categories: { type: $data.EntitySet, elementType: NorthwindModel.Category },
Customers: { type: $data.EntitySet, elementType: NorthwindModel.Customer },
Employees: { type: $data.EntitySet, elementType: NorthwindModel.Employee },
Order_Details: { type: $data.EntitySet, elementType: NorthwindModel.Order_Detail },
Orders: { type: $data.EntitySet, elementType: NorthwindModel.Order },
Products: { type: $data.EntitySet, elementType: NorthwindModel.Product },
Shippers: { type: $data.EntitySet, elementType: NorthwindModel.Shipper },
Suppliers: { type: $data.EntitySet, elementType: NorthwindModel.Supplier }
});


})(window, $data);

 

 

5) Include Northwind.js in your project and place into the Scripts folder

6) Add an empty html page and name it index.html

Make sure that the doctype is HTML5 that is: <!DOCTYPE html>

7) Place JayData.js and its dependencies in the index.html file

 

image

 

UPDATE:

As of JayData 1.1 and above you need to also include jaydataproviders/odataprovider.js in your page for the following examples to work without modification.

 

How to create a simple product creator application?

 

List categories

 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <script src="Scripts/datajs-1.0.3.js" type="text/javascript"></script>
    <script src="Scripts/jquery-1.6.4.js" type="text/javascript"></script>
    <script src="Scripts/JayData-rc1-r1.js" type="text/javascript"></script>
    <script src="Scripts/Northwind.js" type="text/javascript"></script>
    <script>

        //copy this line from Northwind.js
        northwind = new NorthwindDemo.NorthwindEntities({  name: 'oData', oDataServiceHost: '/Northwind.svc' });
        
        $(document).ready(function () {

            northwind.Categories.forEach(function (category) {
                var item = "<li class=@cls data-id=@id><a href=#>@name</a></li>"
                                    .replace("@cls", 'category')
                                    .replace("@id", category.Category_ID)
                                    .replace("@name", category.Category_Name);
                $('#categories').append(item);
            });
        });


    </script>
    <title></title>
</head>
<body>
    <h1>Categories</h1>
    <ul id="categories"></ul>
    <h1>Products</h1>
    <ul id="products"></ul>
</body>
</html>

 

 

List products for a category

Extended the script block with the following lines. Notice the match in the jQuery delegate for the category class (class=category).

        ...
        var selectedCategoryID;

        $(document).delegate('.category', 'click', function () {
            selectedCategoryID = $(this).data('id');

            northwind.Products
                .filter(function (product) { return product.Category_ID == selectedCategoryID })
                .forEach(function (product) {
                    $('#products').append($('<li>').text(product.Product_Name))
                });
        });
    </script>

 

Create a new product from code

 

        function addNewProduct() {
            var product = new NorthwindModel.Product();
            product.Product_Name = "Tea for two #" + Math.random().toString().substr(1,3);
            product.English_Name = "JayData tea";
            //product.Category = new NorthwindModel.Category({ Category_ID: 1 });
            product.Category_ID = 1;
            product.Supplier_ID = 1;
            northwind.add(product);
            northwind.saveChanges(function () {
                alert('New produt created with ID: ' + product.Product_ID);
            });
        }

To invoke this function place a button somewhere in the html.

<button onclick="addNewProduct()">Press to create a product</button>

 

and that’s all folks…

Share this

categories: JayData examples, JSLQ, OData, HTML5, WCF

Comments

  • Missing user avatar
    zbrong

    I followed your steps with Jaydata 1.0.4 , but It dosnt work . may you have a try?

    in jaydata.js
    行: 7578
    错误: 对象不支持此操作

    in northwind.js
    行: 71
    错误: 'Entity' 为空或不是对象

    following is my page code:
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title></title>
    <script src="Scripts/datajs-1.0.3.js" type="text/javascript"></script>
    <script src="Scripts/jquery-1.7.2.js" type="text/javascript"></script>
    <script src="Scripts/JayData.js" type="text/javascript"></script>
    <script src="Northwind.js" type="text/javascript"></script>
    </head>
    <body>
    </body>
    </html>

  • I completed the steps and it worked for me (almost) fine. Uploaded the project as a compressed zip folder here http://jaydata.org/examples/jaydata104nwind.zip

    BUT. I had some issues with the datacontext namespace and variable name, as copypasting the code from the blog for the html page did not match the contextname generate by jaysvcutil. Copying it from the northwind.js mismatched the variable name :) so I had to name the context instance variable "northwind" to get the page working.

    northwind = new WebApplication5.NorthwindEntities({ name: 'oData', oDataServiceHost: 'http://localhost:46885/WcfDataService1.svc' });


    From your code I see you experienced problems before writing any client code. Could you email your projectfolder to me at helpme@jaydata.org?
    Do you have the northwind.js file matching the code found in the blog?

  • Missing user avatar
    jazzman

    Hi Peter

    Just got Jaydata from the odata.org. Looks pretty slick in your videos and demos.

    However I wasn't able to get it work against an ODATA service I put up in the cloud at all.

    I am able to successfully query the service with a normal browser and with my C# client, but the jaydata client seems to be doing something weird.

    When the Jaydata library makes a call to a section in the ODATA service, it seems to be making an HTTP OPTIONS call, instead of a straight GET. I got this information from Fiddler, and I can provide you both the Fiddler information and the project itself.

    Would appreciate a hand, as I will be using this library pretty heavily if I can make it work for me. It seems to be much nicer to use than plain ajax :)

    Thanks much

    J

Leave a comment

comments powered by Disqus