Skip to content

Accessing business data in SharePoint using jQuery and WCF

November 27, 2010

The problem

I recently needed to display a link on the Home Page to HR Dashboard.  The link should be only visible for managers and have additional notification if there are outstanding actions for that manager.

The information about managers and alerts is stored in an HR Oracle database. We have a few architectural decisions to make.   The simplest approach is use a direct connection to the database either through a web part or via BDC (BCS in 2010.)  The main argument against these approaches (and there are many) is that Oracle drivers will have to be installed and configured throughout the SharePoint farm.  In a farm of moderate size that will include not only multiple servers in the Production environment, but also servers in the Integration, QA, Staging, and test load lab.

A more flexible approach removes the Data Access Layer from the SharePoint farm. In the architecture depicted here, a WCF service communicates with the Oracle database, and a SharePoint component (a WebPart) calls the into WCF methods.

image

The Challenge

There are some good reasons to avoid deploying code to SharePoint farms.  You may not have the necessary permissions; deployment cost is too high; bad timing.  Whatever the reason, I will discuss an approach that requires no server code deployment.

Using client-side scripts to access WCF presents a new challenge: cross-site scripting, or more accurately, the safeguard that modern browser have against cross-site scripting.  Of course, the safeguards are in place to protect against malicious software, but it will prevent client code (Javascript) from accessing locations outside of our SharePoint farm!  So, we have to deal with that challenge in our implementation.

Using only client-side code in the SharePoint farm has several advantages.  We can easily implement the solution on multiple farms even one that are hosted by 3rd party vendors.  We can deploy quickly and easily via the browser interface, and maintain and change our code just as easily.  This is the deployment model that I easily prefer when possible.

The Solution

There are various ways to avoid cross-site scripting.  One will be to place the code server-side. Another is to write a light-weight proxy to run server-side in your farm and make all calls via that proxy.   Even the second approach still requires some server side deployment and may not be always available.  If it is available, it’s a good approach as it avoids most of the deliberate coding and configuration challenges presented above.

I want to present a third approach here using JSONP.  JSONP is a way to pad a JSON request/response in script tag.  The browser then allows the exchange because what is returned is HTML.  This technique does not make the data any safer in transport, or any more secure to process, but it makes it possible to have cross-domain scripts.

You need to adjust both the request and the response, but with jQuery and WCF, most of the work is already done for you.

The Service

We implement the WCF service on a machine close to the database.  Ideally, a server with Oracle drivers already in place and IIS running.  (WCF can also be hosted outside of IIS if so desired.)

Microsoft provides a sample of implementing a JSON-P wrapper by simply using an attribute.  Download the sample code from JSON with Padding (AJAX) copy the JSONPxxx.cs files from WCFWFCardSpace\WCF\Extensibility\Ajax\JSONP\CS\Service to your project, and include the Microsoft.Ajax.Samples namespace.

The contract looks something like this

[ServiceContract]
public interface IAlerts {
    [OperationContract]
    [WebGet(RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "Alerts/{EmployeeId}")]
    [JSONPBehavior(callback = "method")]
    bool GetAlerts(string employeeId);

    [OperationContract]
    [JSONPBehavior(callback = "method")]
    [WebGet(ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "WhoAmI")]
    string WhoAmI();
}

Implementation of GetAlerts is trivial and highly specific.  So, we will skip the actual details.  If you host your service in IIS include the ASP.Net compatibility property in the configuration

   <serviceHostingEnvironment aspNetCompatibilityEnabled="true"> </serviceHostingEnvironment>

You can then use the HTTP window identity as follows:

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Web;
namespace Demo
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class Alerts : IAlerts {
        public string WhoAmI()
        {
            return HttpContext.Current.User.Identity.Name;
        }

The configuration file goes something like the following.

 <system.serviceModel> <standardEndpoints /> <bindings> <customBinding> <binding name="jsonpBinding"> <jsonpMessageEncoding /> <httpTransport manualAddressing="true" /> </binding> </customBinding> </bindings> <services> <service name="Alerts.Service"> <endpoint address="" behaviorConfiguration="jsonpBehavior" binding="customBinding"
          bindingConfiguration="jsonpBinding" contract="Demo.IAlerts" /> </service> </services> <behaviors> <endpointBehaviors> <behavior name="jsonpBehavior"> <webHttp /> </behavior> </endpointBehaviors> </behaviors> <extensions> <bindingElementExtensions> <add name="jsonpMessageEncoding" type="Microsoft.Ajax.Samples.JsonpBindingExtension, Demo.Alerts ,Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </bindingElementExtensions> </extensions> </system.serviceModel>

Deploy the service to a server and test it from the browser.  The call to http://localhost/WhoAmI should yield “<Domain>\<username>”  while the jsonp call http://localhost/WhoAmI?method=jsonp123 should yield the wrapped answer jsonp123(“<domain>\<username>”).

The JavaScript Front End

The client code performs the following operations:

  • Call WhoAmI to get current user
  • call UserProfile web service to get Employee ID
  • call GetAlerts to find out if user has alerts
  • display appropriate information in the page

In our case alerts are stored by Employee ID and we added a profile property that stores the employee ID in the profile.  This is not always the most efficient architecture, but it demonstrate a few key technologies.  The HTML displays a picture when the Employee is a manager, and overlays some notification text when the manager has alerts.

$.getJSON(settings.serviceHost + "WhoAmI?method=?", function (userName) {
    getEmployeeId(userName);
});

this JSON call is turned into a JSON-P by adding “?method=?”.  jQuery manages the name of the function and the unwrapping of the results.

The implementation of getEmployeeId calls SharePoint web service (for MOSS compatibility, ) but that can easily be changed to use the new oData interace.

function getEmployeeId(userName) {
    var userData = SharePoint.getEnvelope(SharePoint.Services.getUserProfileByName, userName);
    $.ajax({
        type: "POST",
        url: "/us/_vti_bin/UserProfileService.asmx",
        data: userData,
        datatype: "xml",
        contentType: "text/xml; charset=\"utf-8\"",
        success: processEmployeeId
    });

I chose to implement SharePoint service call as a set of Java Script objects.  you can find the details in the attached code.

function processEmployeeId(results) {
    var employeeId = null;
    var preferredName = "";
    $(results).children(1).find("PropertyData").each(function () {
        var propName = $(this).find("Name").text();
        if (propName == "employeeID") {
            employeeId = $(this).find("Value").text();
            getAlertsForEmployee(employeeId);
            return true;
        }
    });

We parse the properties returned by the web service and extract the data Employee ID.  The the property at hand we make the final call to our service

function getAlertsForEmployee(employeeId) {
    $.getJSON(settings.serviceHost + "Alerts/" + employeeId + "?method=?", function (results) {
        switch (results) {
            case "True":
                showTarget(true);
                settings.alertTarget
                    .text(settings.alertOnText)
                    .fadeIn('slow').show();
                break;
            case "False":
                showTarget(true);
                settings.alertTarget
                    .text("")
                    .hide();
                break;
            default: // null showTarget(false);
                break;
        }
    });
}

We again make a JSON-P call and letting jQuery handle the naming of the function.  Finally the function that shows the picture and link.

var notice = this;
function showTarget(flag) {
    // for production var pmlTarget = notice.closest("[id^='MSOZoneCell_WebPart']");
    if (flag) {
        pmlTarget.show();
    } else {
        pmlTarget.hide();
    }
}

Notice that we are hiding or showing the entire web part where the code is placed.  We place the entire set of functions inside a jQuery plug-in and then evoke it as in the following HTML code.

<script type="text/javascript" language="javascript"> // activate on document load $(document).ready(function () {
        $("#notice").ManagerAlerts({
            alertTarget: $("#employeeData")
        });
    });
</script> <div id="notice" class="notice"> <div id="employeeData" class="alert"> </div> <div class="dashboard-link"> &nbsp; <a href="/Pages/ManagerDashboard.aspx" target="_blank"> Check your Dashboard</a> </div> </div>

You place this HTML inside a standard Content Editor Web Part (or just in a page in 2010) and link in the JavaScript.

Conclusion

There are a lot of moving parts when you plug different pieces to it.  However, once you deal with more complex architecture, you will find that the rest of it becomes very easy.  jQuery handles the JSON-P calls on client-side; WCF attribute manages it on the Service side.  You have to plug the pieces in and manage the deployment, but you are rewarded with cleaner deployment, shorter time-to-market, and separation of concerns in your software, which will allow you to make changes only to these parts of the system that change.

Download Source Code

Advertisements

From → jQuery, JSON, JSONP, SharePoint, WFC

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: