How to open a WCF RIA Services application to other type of clients: the JSON endpoint (4/5)

We’ll see here how to open the same application used in the 3 previous articles with a JSON endpoint. This new endpoint will then be used by an HTML5 application thanks to jQuery that will help us calling the remote service and bring life to our page. We will then see how to display all our books in a very dynamic way thanks to the Galleriffic plug-in, by using some glitch of CSS3 and the <canvas> tag. At last, we’ll see as usual how to add or remove an item after being properly authenticated.

This article is the fourth one out of 5: 

1 – Review of the initial application
2 – How to expose the service as an OData stream consumed by Excel and from a web application built with WebMatrix 
3 – How to expose the service as a “standard” WCF Service used from a WPF application and a Windows Phone 7 application
4 – How to expose the service as a JSON stream to be used by a HTML5/jQuery web application (this article) 
5 – Step by step procedure to publish this WCF RIA Services application to Windows Azure & SQL Azure

Enabling the JSON endpoint on a WCF RIA Services DomainService

As we’ve seen for the SOAP endpoint in the previous article, you need to install the WCF RIA Services toolkit to be able to declare the JSON endpoint. Then, as usual, you need to go into the web.config file and under the SOAP endpoint, add this new one:

<add name="JSON" type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory,  
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
" />

Once done, you’ll be able to query your service with this kind of URL:

http://nomofyourserver:port/ClientBin/NameOfTheSolution-Web-NameOfYourDomainService.svc/JSON/GetEntities

For instance, in my case, here is the URL that returns all the categories via a JSON stream:

http://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/JSON/GetCategories

And here is the stream returned:

{"GetCategoriesResult":{"TotalCount":4,"RootResults":[{"CategoryID":2,"CategoryName":"Business"},
{"CategoryID":4,"CategoryName":"General"},{"CategoryID":1,"CategoryName":"Technology"},
{"CategoryID":3,"CategoryName":"Travel"}]}}

Consuming the JSON endpoint of RIA Services from an HTML5 application

The approach we will use here works whatever the web technologies behind: PHP, ASP.NET, JSP, etc. as we’re creating a HTML5 application based on jQuery running exclusively on the client side in the browser.

The idea is to send some requests on the URLs where our entities’ methods live and parse the returned JSON stream.

For that, I’ve used as a based the following 2 very useful articles written by Joseph Connolly:

WCF RIA Services, jQuery, and JSON endpoint – Part 1

WCF RIA Services, jQuery, and JSON endpoint – Part 2

For instance, here is a sample script using jQuery to query my WCF RIA Services layer to return all the categories and display them into the current HTML document:

function LoadCategories() {
    var Params = {};
    Params.type = 'GET';
    Params.url = 'BookShelf-Web-Services-BookClubService.svc/JSON/GetCategories';
    Params.dataType = 'json';
    Params.success = function (data) {
        $('categories > p').remove();
        var returnedEntity;
        for (var i in data.GetCategoriesResult.RootResults) {
            returnedEntity = data.GetCategoriesResult.RootResults[i];
            $(categories).append('<p>' + returnedEntity.CategoryName + '</p>');
        }
    };

    $.ajax(Params);
}

You can test this script by navigating on this webpage: http://bookclub.cloudapp.net/jQueryHome.html and you’ll have this rendering inside IE9:

BookClubScreen027

I’m using some of the new features of CSS3 like the “border radius” (to have rounded corners) or the “box shadow” (to have drop shadows) but the page still renders correctly under IE8 with just some of the effects disabled.

Ok, we now know how to read data from our WCF RIA Services layer. Let’s now try to display all the books’ details by using the latest standard web technologies. I had fun playing with them in order to get the books’ data and displaying them into an interactive window thanks to Galleriffic. With some CSS3 effects and with an animation using the <canvas> tag in background, you’ll have this result in IE9:

BookClubScreen028

You can test this HTML5 application here in your browser: http://bookclub.cloudapp.net/WCFRIA_jQueryHTML5.html

Some quick notes on HTML5…

This application works fine in all recent modern browsers supporting part of the CSS3 specifications (like IE9, Chrome, Firefox, Opera or Safari) as well as the <canvas> tag. However, you can still test this page on older browsers that will just prevent you from seeing the new effects. To my point of view, this is something very important. We can start to use some of the new features that come with HTML5 and the associated technologies (CSS3, SVG) but by taking care of not using too instable or experimental parts. If so, you’re taking the risk of being dependent of a particular implementation/browser version and you will have to review your code frequently, during each new browser update. This is for instance the case when you’re using today most of the prefixed extensions like –webkit, –moz, –ms, –o, etc.  To have a better idea on the Microsoft official position on this topic, you can read this excellent blog post: HTML5, Site-Ready and Experimental on the IE blog.

Indeed, I’d like to remind you that HTML5 is far from being ready to be entirely used in production for the mass market. This is simply because the specifications are not yet finished or properly tested by unit tests to guarantee a common behavior on all browsers. Moreover, after that, we will have to wait that this new class of “HTML5 browsers” takes enough market shares until the developer can use these new features in production without any stress.

At last, I think it’s very important to keep a nice experience for older browsers on your website. To do that, you’ve got 2 options. First option is to use HTML5 to enhance the UI & user experience for the most recent browsers. It’s the option I’m recommending to my customers. The second option is to use HTML5 as the main target and to support older browsers with kind of “simulators”. For instance, there are some JavaScript libraries that simulate the <canvas> tag for versions of IE inferiors to 9 like the canvasexplorer. However, each time I’ve seen usage of this approach under IE6/7/8, I’ve found that the result was catastrophic. As these 3 combined versions of IE still represent a huge part of the browsers market shares, you’re clearly taking the risk to have bad users’ satisfaction if you decide to offer them a poor experience because they want to keep their old good IE. Clignement d'œil That’s why, in this case, I prefer the approach of Modernizr that helps you to put in place a features detection method to switch the behavior of your site based on the level of support of the browser.

In my case, as I prefer the first option, my application is working fine with the Quirks/IE7/IE8 rendering engines. This will give you this less sexy result:

BookClubScreen028b

But it’s still fully functional!

Review of the HTML5 application

This application is mainly based on the Galleriffic plug-in. This plug-in creates and handles the animations between each elements for you (it displays some Flickr pictures in the default shipped samples). It handles also the thumbnails displayed in the upper ribbon, the keyboard navigation and a slideshow mode. At last, it creates the anchor URLs behind the # character to let you reference directly a specific book. For instance, in the previous screenshot, I’m looking at the book named “Data-Driven Services with Silverlight 2” written by John Papa with the ASIN code: 0596523092. The direct URL to this book is then this one: http://bookclub.cloudapp.net/WCFRIA_jQueryHTML5.html#0596523092 

To build this application, I’ve used the “example-5” sample provided on the Galleriffic website as a base and I’m adding dynamically in the DOM this kind of entry:

<li><a class="thumb" name="1419620037" href="/Content/Images/1419620037.jpg"
    title="7-Slide Solution(tm): Telling Your Business Story In 7 Slides or Less">
    <img src="/Content/Images/Thumbs/1419620037.jpg" 
alt="7-Slide Solution(tm): Telling Your Business Story In 7 Slides or Less" /> </a> <div class="caption"> <div class="image-title"> 7-Slide Solution(tm): Telling Your Business Story In 7 Slides or Less</div> <div class="book-title"> Paul J. Kelly</div> <div class="image-desc"> A unique approach to organizing and constructing business presentations that draws on the insights of cognitive psychology and provides an infrastructure to build presentations that resonate with your audience like a good story.</div> </div> </li>

These entries are built based on the JSON stream sent back by the GetBooks() method. Here is the function hosted in the WCFRIA_jQueryHTML5.js file in charge of loading the JSON stream and generating dynamically the proper entry in the DOM:

function LoadBooks() {
    var Params = {};
    Params.type = 'GET';
    Params.url = 'BookShelf-Web-Services-BookClubService.svc/JSON/GetBooks';
    Params.dataType = 'json';
    Params.success = function (data) {
        var returnedEntity;
        var books = new Array();
        var newHtmlGenerated = new StringBuilder();

        for (var i in data.GetBooksResult.RootResults) {
            returnedEntity = data.GetBooksResult.RootResults[i];
            newHtmlGenerated.append('<li><a class="thumb" name="');
            newHtmlGenerated.append(returnedEntity.ASIN);
            newHtmlGenerated.append('" href="');
            newHtmlGenerated.append("/Content/Images/" + returnedEntity.ASIN + ".jpg");
            newHtmlGenerated.append('" title="');
            newHtmlGenerated.append(returnedEntity.Title);
            newHtmlGenerated.append('">');
            newHtmlGenerated.append('<img src="');
            newHtmlGenerated.append("/Content/Images/Thumbs/" + returnedEntity.ASIN + ".jpg");
            newHtmlGenerated.append('" alt="');
            newHtmlGenerated.append(returnedEntity.Title);
            newHtmlGenerated.append('" /></a>');
            newHtmlGenerated.append('<div class="caption">');
            newHtmlGenerated.append('<div class="image-title">');
            newHtmlGenerated.append(returnedEntity.Title);
            newHtmlGenerated.append('</div>');
            newHtmlGenerated.append('<div class="book-title">');
            newHtmlGenerated.append(returnedEntity.Author);
            newHtmlGenerated.append('</div>');
            newHtmlGenerated.append('<div class="image-desc">');
            newHtmlGenerated.append(returnedEntity.Description);
            newHtmlGenerated.append('</div></div></li>');
        }

        $('ul').append(newHtmlGenerated.ToString());

        LoadGalleriffic();
    };

    $.ajax(Params);
}

I’m then loading exactly the same logic provided into the “example-5 sample by calling the LoadGalleriffic() method. Moreover, I’m using these tiny functions to be able to simulate a kind of StringBuilder in order to be more efficient than brutally concatenating strings with the += operator:

function StringBuilder() {
    this.buffer = [];
}

StringBuilder.prototype.append = function append(string) {
    this.buffer.push(string);
    return this;
};

StringBuilder.prototype.ToString = function ToString() {
    return this.buffer.join("");
};

This approach helps to have a better memory footprint and also much higher performance when you’re playing with big strings in JavaScript (and even in other worlds) whatever the browser you’ll use. I’m recommending you reading this article on this topic: 43,439 reasons to use append() correctly

 
For the background animation, I’m using the HTML5 <canvas> tag you’ve probably already seen in many recent demos. This new tag allows us to write in a bitmap area with some JavaScript APIs/primitives. My goal was to have an effect like the animation you’ve got on a PlayStation (the PS3 background for instance). Before coding it from scratch, I’ve done some quick researches on the web and I’ve found something similar here (in French): Tutoriel Canvas : Réaliser une bannière animée en quelques lignes de code . In my case, I was looking for the same animation but rendered in background behind my main display zone where the books are. For that, I’ve used the solution exposed here: Animated website background with HTML5 . At last, to test the <canvas> support of the browser and to avoid scripting errors under IE6/7/8, I’ve used this little piece of script:

function isCanvasSupported(){  
     var elem = document.createElement('canvas');  
     return !!(elem.getContext && elem.getContext('2d'));
}

I’ve found it while reading this thread on Stackoverflow: Best way to detect that HTML5 <canvas> is not supported

Finally, you’ll notice by looking to the source code of the 2 previous pages that I’ve tried to follow an interesting methodology named “Unobtrusive JavaScript”. This helps to clearly dissociate the role of each element inside a webpage: the markup for the page’s content, the CSS to display it and the scripting part to bring life to the whole page. jQuery is indeed particularly well adapted for this methodology thanks to his powerful selector. You can then attach all the logic to the HTML elements in a separate area. We then can use a welcomed pattern already well known by ASP.NET developers where the UI markup is separated from the code-behind. 

Note: You’ll notice on the screenshots that the “Back” & “Forward” buttons are using a different color than the default one. This is because I’m using the new features offered by IE9. You can indeed pin a website to the Windows 7 taskbar:

BookClubScreen031

You’ll find some documentation on this feature here: http://msdn.microsoft.com/library/gg491738(v=VS.85).aspx . I’ve built my icon by using this HTML5 application: http://ie.microsoft.com/testdrive/Browser/IconEditor/Default.html

Then, I’ve simply added this piece of HTML into my page:

<link rel="shortcut icon" href="/favicon.ico"/>

Handling the authentication to be able to do writing operations

As already seen in the previous articles, you need to be authenticated and member of the “Admin” group to be able to manipulate the entities exposed by our WCF RIA Services layer in write mode. With the SOAP endpoint, in the WPF & Windows Phone 7 cases, this has required a bit of work. Indeed, we had to take care of the authentication cookie contained in the HTTP requests to send it back to the methods handling the writing operations (add, update, delete). Having an application running in the browser will help handling the cookie. We will just have to do a call to the Login() method of the DomainService in charge of the authentication logic. Once successfully logged on, the browser will send back the magic cookie for us to the calls done on the 2nd DomainService.

In order to authenticate my users, I’ve included the logic inside this page: http://bookclub.cloudapp.net/jQueryLogin.html and more specifically into this jQueryLogin.js file that contains this code:

function Auth() {
    var _username = $(UserName).val();
    var _password = $(Password).val();
    var loginParameters = JSON.stringify({ 'userName': _username, 'password': _password, 
'isPersistent': 'true', 'customData': '' }); var result; //Create jQuery ajax request var Params = {} Params.type = "POST"; Params.url = 'BookShelf-Web-AuthenticationService.svc/JSON/Login'; Params.dataType = "json"; Params.data = loginParameters; Params.contentType = "application/json" Params.success = function (data) { for (i in data.LoginResult.RootResults) { result = data.LoginResult.RootResults[i]; var ul = $('<p><img src=' + result.PictureURL + ' width=64/> Welcome '
+ result.DisplayName + '</p>'); $(ul).appendTo($('#UserLogged')); } if (result != null) { $("#AccountInfo").fadeOut(500, null); } else { $("#AccountInfo").append("<p><font color='red'>Error while logging. Check username or password.</font></p>"); } } //Make the ajax request $.ajax(Params); }

We’re doing here a call to the Login() method available thanks to the JSON endpoint of the AuthenticationService.svc service available by default on every application based on the “Silverlight Business Application” template. If the login succeeds, we’re hiding the input controls with a fadeOut() effect and we add a welcome message to the user using its profile picture generated with the webcam via the main Silverlight application.

For instance, these screenshots show the results before and after the login process:

BookClubScreen029BookClubScreen030

Once logged in, we can then do some authenticated calls to the writing operations. The WCF RIA Services layer will take care of checking that you’re in the proper role with the special attributes set on the methods.

If you tries to do some calls to one of these protected methods without being in the right role or without being authenticated, here is the response you’ll have from the SubmitChanges() method:

{"ErrorCode":401,
"ErrorMessage":"You must be part of the Administrator role to insert, update or delete a category.",
"IsDomainException":false,
"StackTrace":" at System.ServiceModel.DomainServices.Server.DomainService.
ValidateMethodPermissions(DomainOperationEntry domainOperationEntry, …"
}

The error message set by attribute is then properly propagated on the JSON calls. At last, here is a sample script that shows you how to add a new category using the JSON endpoint and how to display an error message if you don’t have the appropriate rights:

var newCategoryIdInserted;

function InsertNewCategory() {
    var _categoryName = $(CategoryName).val();
    var changeSet = [];

    var entityChange = {};
    //Setting Id for entityChange, not the entity key but how the changeset tracks each change if there is more than one
    entityChange.Id = 0;
    entityChange.Entity = { '__type': "Category:#BookShelf.Web.Models", 'CategoryName': _categoryName };

    //Set the type of Operation to be performed on the Entity
    //2 - insert
    //3 - update
    //4 - delete
    entityChange.Operation = 2;

    changeSet.push(entityChange);

    var changsetPayload = JSON.stringify({ "changeSet": changeSet });

    //Create jQuery ajax request
    var Params = {}
    Params.type = "POST";
    Params.url = 'BookShelf-Web-Services-BookClubService.svc/JSON/SubmitChanges';
    Params.dataType = "json";
    Params.data = changsetPayload;
    Params.contentType = "application/json";
    Params.error = function (httpRequest, textStatus, errorThrown) {
        var error = JSON.parse(httpRequest.responseText, null);
        alert(error.ErrorMessage);
    }
    Params.success = function (data) {
        for (i in data.SubmitChangesResult) {
            result = data.SubmitChangesResult[i];
            // keeping a copy of the returned affected CategoryID
            // for further operations (update/delete)
            newCategoryIdInserted = result.Entity.CategoryID;
            alert("Category " + _categoryName + " added successfully with ID: " + newCategoryIdInserted);
        }
    }

    //Make the ajax request
    $.ajax(Params);
}

Here is the code to use to delete this new category:

function DeleteCategory() {
    var changeSet = [];

    var entityChange = {};
    entityChange.Id = 0;
    entityChange.Entity = { '__type': "Category:#BookShelf.Web.Models", 'CategoryID': newCategoryIdInserted };

    //Delete
    entityChange.Operation = 4;
    changeSet.push(entityChange);

    var changsetPayload = JSON.stringify({ "changeSet": changeSet });

    //Create jQuery ajax request
    var Params = {}
    Params.type = "POST";
    Params.url = 'BookShelf-Web-Services-BookClubService.svc/JSON/SubmitChanges';
    Params.dataType = "json";
    Params.data = changsetPayload;
    Params.contentType = "application/json";
    Params.error = function (httpRequest, textStatus, errorThrown) {
        var error = JSON.parse(httpRequest.responseText, null);
        alert(error.ErrorMessage);
    }
    Params.success = function (data) {
        for (i in data.SubmitChangesResult) {
            result = data.SubmitChangesResult[i];
            alert("CategoryID: " + result.Entity.CategoryID + " deleted successfully.");
        }
    }

    //Make the ajax request
    $.ajax(Params);
}

You’ll find these 2 functions inside the jQueryAdmin.js file used by the jQueryAdmin.html page you can live test here: http://bookclub.cloudapp.net/jQueryAdmin.html . You’ll then have this kind of output. Trying to insert or delete a category without being logged on first:

BookClubScreen032

Insert done after being authenticated as an Admin:

BookClubScreen033BookClubScreen034

Delete:

BookClubScreen035

You can download the source code of the Visual Studio 2010 solution hosting the results of these 4 articles here:

This is concluding this article. I must admit that I had fun playing with the latest standard technologies inside this HTML5 application. Still, I’ve found the productivity level, the tooling and the global quality of the development production chain far inferior to the one we’ve got while developing rich application in Silverlight. Going from a very rich environment based on C# & XAML under Visual Studio & Expression Blend to JavaScript, HTML, CSS under something like Notepad is particularly painful.

Silverlight is indeed able to do today what HTML5 will do (in a couple of years probably) faster, thus cheaper and with a higher quality of code. Silverlight also guarantee the same cross-browsers rendering & performance. Moreover, Silverlight offers a lot of additional features not even considered before HTML6… That goes from basic obvious things like accessing to the Webcam to the support of DRM on the video streams. If you’d like to go deeper on this subject, you can read this interesting article: Top 10 Reasons why HTML 5 is not ready to replace Silverlight . It well covers the differences between each world. And I don’t even talk about the new features we’ve announced during the PDC on Silverlight 5 supporting 3D and P/Invoke calls!

Still, of course, the Silverlight plug-in remains a plug-in. It won’t be able to cover all the scenarios and all the platforms.

HTML5 is then interesting and has an important potential. That’s why, it must be followed by the web developers in the next years. You mustn’t ignore it as a web developer. That’s why also Microsoft is fully committed to HTML5 by working on the specifications inside the W3C, by writing unit tests to valid them and by supporting it in a practical manner inside our next browser version: Internet Explorer 9. However, HTML5 and Silverlight aren’t enemies. They are complementary and target different scenarios.

The best thing you have to do is probably to be able to work with both. You will then have a clear idea of the strengths and weaknesses of each platform to build your future RIA applications. You will then have also a better view and understanding than the people creating the sterile debate like “HTML5 will kill xxx”. Unfortunately, those persons often don’t know anything about any of the 2 platforms and feed these debates without any solid technical knowledge…

Thanks for reading. See you in the next and final article that will be dedicated to the migration of this solution into Windows Azure & SQL Azure!

David

9 thoughts on “How to open a WCF RIA Services application to other type of clients: the JSON endpoint (4/5)

  1. Why the url of the "InsertCategory" method  is BookShelf-Web-Services-BookClubService.svc/JSON/SubmitChanges,

    I can't find the definition of SubmitChanges in the BookClubService.cs

  2. Hi,

    The RIA Services framework handles for you the connection between the SubmitChanges URL and the calls to each CUD methods based on the type of operations you've added in the changeset. This enables you to do several kind of changes on the clients side (create, update, delete) and launch a unique request on the server-side to ask for the complete updates.

    Regards,

    David

  3. Great Article. You are absolutely correct about HTML 5 vs Silverlight 5. Both are meant for unique purpose and certainly deserves their presence in industry. Thanks again for this great article.

Leave a Reply