JavaScript, WCF and Entity Framework Data Model for SharePoint 2013 Apps – Part 3
The following article is the third in a three part series on data model patterns for SharePoint 2013 apps.
In Part One of this series we reviewed the basic parameters for a data model pattern that can be used for building SharePoint 2013 apps and defined the process for creating a base data layer using the ADO.NET Entity Framework. In Part Two we explored the use of WCF to create a middle-tier service layer. In this post we will discuss the final piece of the puzzle – binding data to presentation objects in the user interface using JavaScript, JQuery and AJAX.
Overview
For more than a decade server-side programming languages served as the primary means for created web applications. Java and .NET accounted for the bulk of all web programming with client-side scripting languages used mostly as add-ons or enhancements. Occasionally, a creative developer would put together a pure client-side application but these were few and far between and certainly not well represented in any type of business or enterprise application. As client technologies evolved and more attention was given to the creation of libraries to expand their capabilities, these languages, led by JavaScript (or, if you are a die-hard standards conformist, ECMAScript), have taken center stage in what are referred to as "modern", "fluid", or "responsive" applications. These types of applications are defined by their near-complete lack of server-side code and the absence of visible round trips to the server (evidenced primarily by the dreaded screen-wiping postback mechanism). Adoption of client-centric web applications has been spurred in no small part by plugins such as JQuery, which obfuscate many of the tedious tasks associated with programming in a loosely-typed, uncompiled script language and give even raw beginners the ability to quickly create dynamic applications.
This programming model has its obvious limitations as part of an overall pattern that involves structured data access. While an elegant data layer built that interacts with a SQL database is directly accessible in server-side code, client-script running in the web browser has no way of interfacing with it. Furthermore, the lack of strongly-typed objects means that there is no way to directly bind UI elements to objects in the data layer. There needs to be a way for the presentation tier to talk to the data tier in a structured manner using a language that both understand. This is where AJAX and JSON come in. The former provides a mechanism for handling asynchronous calls over HTTP to remote services while the latter defines a data structure for request/response payloads (XML does the exact same thing but is much more cumbersome than JSON, which was designed as a lighter-weight alternative to endless strings of text featuring angle brackets and namespaces). In practical terms, the remote service accepts a request from the client, who specifies that JSON as the message format, and returns a response in the same format.
Once the communication methods and data structure have been identified the client application then needs a way to initiate communication and deal with the result. JQuery makes this easy, as it provides simple methods for executing an asynchronous request and parsing the results. It also provides the mechanism for binding data to control elements or otherwise updating the UI based on the results of the request/response procedure. This is all accomplished in series of JavaScript functions that are invoked by various events, such as page loads, window operations, clicks, keystrokes and the like.
In context of the overall pattern, the presentation tier elements are all related to the parsing of data as defined by the WCF service, which describes the uniform message structure and payload formatting, and the manner in which the service is called. There are no requirements for specific UI components, script behaviors, variable declaration or even function structure. All that is required are a properly formatted request and the ability to parse JSON contained within the response.
Implementation
The presentation tier is comprised entirely of JavaScript methods. Functions handle application events and features of the JQuery library used to request data and update the UI. Each call to the WCF service is made asynchronously with data operations contained within success and failure handlers. The data is then bound to controls or otherwise acted upon using JQuery and base JavaScript functions. The most important factors to consider within the presentation tier implementation are 1) Correct instantiation and parsing of JSON objects, 2) Managing the sequence of asynchronous event chains, and 3) Proper request formatting.
1 |
Create UI Elements
Begin with a set of UI elements that data will be bound to. For purposes of this demonstration, the user will type a numeric value into a text box, click a button and retrieve full contact details from the database. The simplified markup for this scenario is as follows:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="BWAppDataModel._Default" %>
<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
</asp:Content> <asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent"> <div id="userInput" class="inputForm"> <input id="inputUserId" class="inputField" /> <button id="buttonSubmit" class="button" onclick="getUserInfo();return false;">Submit</button> </div> <div id="userDetails" class="detailForm"> <div class="detailHeader">User Information</div> <div class="table"> <div class="row"> <div class="cellLabel">User Name:</div> <div id="fieldUserName" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">Login Name:</div> <div id="fieldLoginName" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">Email Address:</div> <div id="fieldEmail" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">Phone:</div> <div id="fieldPhone" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">Address:</div> <div id="fieldAddress" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">City:</div> <div id="fieldCity" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">State:</div> <div id="fieldState" class="cellText"></div> </div> <div class="row"> <div class="cellLabel">Zip Code:</div> <div id="fieldZip" class="cellText"></div> </div> </div> </div> </asp:Content>
NOTE: An ASPX page was used for this demonstration as one is created by default when the Azure Web Role project is selected in Visual Studio 2012; however, a plain HTML page running on any web server platform works just as well. |
2 |
Call the WCF Service
Setup for the script elements is done when the page initially loads using the $(document).ready(function () {}); method of JQuery. In the following initialization function, a global variable for the WCF service is assigned and some AJAX parameters set to permit cross-domain calls (even though they won’t be used in this context as the service is in the same domain as the web application).
function initializePage() { svcUrl = window.location.href; if (svcUrl.indexOf("?") != -1) svcUrl = svcUrl.substr(0, svcUrl.indexOf("?")); svcUrl = svcUrl.substr(0, svcUrl.lastIndexOf("/")) + ‘/AppDataService.svc/’; $.ajaxSetup({ cache: false, crossDomain: true }); $.support.cors = true; }
The getUserInfo() function assigned to the onClick() event of the "buttonSubmit" button first gets the id value entered by the user then constructs an asynchronous AJAX call to the web service URL, specifying the operation contract, query string arguments, data type (JSON), and request type (GET or POST). Two event handlers are also assigned, one which fires if the request succeeds and the other if it fails.
function getUserInfo() { var userId = $("#inputUserId").val(); $.ajax({ cache: false, url: svcUrl + "GetUser?UserId=" + userId, dataType: "json", type: "GET", success: function (result) {
}, error: function (result) {
} }); }
|
3 |
Parse the Response
The response received from the WCF service will be handled by either the success or error methods. Since the operation contracts in the WCF service (refer to Part Two of this series) use a consistent format, every call can be handled in the same manner. First, the JSON string is parsed using the $.parseJSON method. The first element in the array is always "d" so the object notation for the parsing operation is "result.d" (‘result’ being the payload of the success function). This is assigned to a variable and then checked for the existence of an "Error" value at the first array position. If found, failure logic is invoked (in this case, an alert with the error message as provided for in the Catch block of the operation contract). If no "Error" value is found, the "user" object (which is the variable in the operation contract containing the result of the entity function execution) is assigned to a variable.
function getUserInfo() { var userId = $("#inputUserId").val(); $.ajax({ cache: false, url: svcUrl + "GetUser?UserId=" + userId, dataType: "json", type: "GET", success: function (result) { var objData = $.parseJSON(result.d); if (objData.Error) { var r = $.parseJSON(result.d); alert(r.Error); } else { var data = objData.user } }, error: function (result) { var r = $.parseJSON(result.d); alert(r.Error); } }); }
It is important to note that the request/response process is asynchronous; that is, the thread will continue executing while the operation is taking place. If there are other operations that are dependent upon the success or failure of the AJAX request (such as binding data to a control) then they must be called in either the "success" or "error" event handlers; otherwise, the operations will execute before the response is received from the service call. Asynchronous event chaining can be one of the most difficult aspects of JavaScript programming as there is no way to ever know how long a request will take to complete; the more complex the application, the more chained events, and the harder the overall process is to follow. Visual Studio 2012 includes some enhancements for JavaScript debugging to aid in this task. |
4 |
Bind Data to UI Elements
Once the response has been received and parsed, the data can then be bound to UI elements using JQuery. The values are accessed by calling the variable the object was assigned to in the "success" handler and specifying the desired property (such as FirstName or Email) as a child element.
function getUserInfo() { var userId = $("#inputUserId").val(); $.ajax({ cache: false, url: svcUrl + "GetUser?UserId=" + userId, dataType: "json", type: "GET", success: function (result) { var objData = $.parseJSON(result.d); if (objData.Error) { var r = $.parseJSON(result.d); alert(r.Error); } else { var data = objData.user; $("#fieldUserName").html(data.FirstName + " " + data.LastName); $("#fieldLoginName").html(data.LoginName); $("#fieldEmail").html(data.Email); $("#fieldPhone").html(data.Phone1); $("#fieldAddress").html(data.Address1); $("#fieldCity").html(data.City); $("#fieldState").html(data.State); $("#fieldZip").html(data.Zip); $("#userDetails").show(); } }, error: function (result) { var r = $.parseJSON(result.d); alert(r.Error); } }); }
The final result of the sample application after the data has been processed and bound to the UI elements:
|
4 |
POST Operations
Sending data to the WCF service using a complex object in a POST operation requires slightly different formatting but the process is essentially the same. First, the object must be created as an array that can be serialized into a JSON string. The first element in the array must be the name of the variable specified in the operation contract parameter; in this case, the contract is UpsertUser(DataContracts.UserInfoObject User) so the array must start with "User:". A secondary child array is then created to hold each of the object properties. These must have the same name and appear in the same order as the DataMember values specified in the DataContract for the specified object.
function updateUserInfo() { var _object = { User: { UserId: $("#inputUserId").val(), LoginName: $("#fieldLoginName").html(), FirstName: $("#fieldUserName").html().split(" ")[0], LastName: $("#fieldUserName").html().split(" ")[1], Email: $("#fieldEmail").html(), Phone1: $("#inputPhone").html(), Phone2: ”, Address1: $("#fieldAddress").html(), Address2: ”, City: $("#fieldCity").html(), State: $("#fieldState").html(), Zip: $("#fieldZip").html(), Country: ‘US’, Active: ‘True’, IsEngineer: ‘True’, ImageUrl: ” } }; }
The object can then be serialized and sent to the service. The AJAX call syntax is similar to that used in a GET operation; however, the dataType is different (POST) and two additional parameters are required – "data" and "contentType". The "data" parameter specifies the serialized object and the "contentType" informs the server what MIME type to use. When the WCF service receives the object, it will map it to the data contract and, if the name, properties, and order are all correct, the object may then be used to call an entity function or run some other code. Since this particular operation does not return any data, the response is checked for an Error object and the user alerted if the request succeeded or failed.
function updateUserInfo() { var _object = { User: { UserId: $("#inputUserId").val(), LoginName: $("#fieldLoginName").html(), FirstName: $("#fieldUserName").html().split(" ")[0], LastName: $("#fieldUserName").html().split(" ")[1], Email: $("#fieldEmail").html(), Phone1: $("#inputPhone").html(), Phone2: ”, Address1: $("#fieldAddress").html(), Address2: ”, City: $("#fieldCity").html(), State: $("#fieldState").html(), Zip: $("#fieldZip").html(), Country: ‘US’, Active: ‘True’, IsEngineer: ‘True’, ImageUrl: ” } }; $.ajax({ cache: false, url: svcUrl + "UpsertUser", data: JSON.stringify(_object), dataType: "json", type: "POST", contentType: ‘application/json; charset=utf-8’, success: function (result) { var objData = $.parseJSON(result.d); if (objData.Error) { var r = $.parseJSON(result.d); alert(r.Error); } else { alert("Update Succeeded"); } }, error: function (result) { var r = $.parseJSON(result.d); alert(r.Error); } }); } |
Once the presentation tier is in place the pattern is complete. All subsequent data access functions on the client follow the same structure – an AJAX call to the service endpoint using either a GET or POST operation and a response handler to parse the JSON result. As with the WCF operation contracts, the process is as simple as copy > paste > modify. With a consistent request/response mechanism in place the code is easy to test and debug. It uses common JQuery components to eliminate any reliance on platform-specific implementations (such as the ASP.NET Script Manager). Perhaps most importantly, it allows the developer to concentrate on application design rather than the minutiae of SOAP messages and XPATH expressions.
The overall pattern was created to satisfy the following objectives: 1) Provide a consistent methodology for CRUD operations from client-side script to/from a remote data repository, 2) Define a middle-tier service layer with uniform object and method structures, and 3) Minimize the amount of manual coding required to create and manage core data access classes. As implemented, it meets these requirements and reduces the overhead associated with consuming remote data sources from JavaScript. With each operation essentially being a copy and paste procedure, new apps can be deployed in a very short period of time and, if used consistently on multiple projects, greatly decrease time spent on troubleshooting and maintenance. By exposing backend data sources as serialized JSON objects, the data model is compatible with most JQuery plugins and extensions. It also facilitates rapid creation of platform-specific mobile applications and is cross-browser compatible. While the pattern was originally intended for SharePoint and Office365 apps, it works equally well with any HTML + JavaScript application.
Naturally, there are many opportunities for improvement in the pattern. It would be ideal to have a Visual Studio plugin that would automatically generate data and operation contracts based on an entity model. An abstracted JavaScript library that exposed a few simple commands and did all the request setup and response parsing behind the scenes would also be helpful (so the contents of the $.ajax({}) call could be reduced to a single line like "var user = $.callService(GetUser)" or something similar). Perhaps some enterprising members of the community will take on those challenges. If nothing else, my hope is that developers who know SharePoint but don’t know JavaScript (or even the other way around) are able to use the pattern to rapidly create apps with real business value for the marketplace and internal corporate catalogs.
For those who wish to delve into the sample application used in this series, the full Visual Studio 2012 solution can be downloaded here. Any questions regarding the pattern can be posted as comments to this blog or on Twitter using #spappdatamodel or @eshupps.
Additional References
An Introduction to JavaScript Object Notation (JSON) in JavaScript and .NET
Leave a Reply
You must be logged in to post a comment.