HTML5 Zone is brought to you in partnership with:

Kristof Degrave is a Microsoft Certified Technology Specialist ASP.NET and has a broad interest in web development. He works as a software engineer for RealDolmen, one of Belgium's biggest ICT companies, where he takes on the role of squadleader for the web squad, a group of web-enthousiasts within the Microsoft Division. He mainly focuses on HTML5 and related technologies, such as CSS3, jQuery, ... He is currently working on a library on top of the IndexedDB API called Linq2indexedDB. The goal of this library is an easy-to-use query interface with extended filter, sort and select capabilities. The library can be found on http://linq2indexeddb.codeplex.com Kristof is a DZone MVB and is not an employee of DZone and has posted 14 posts at DZone. You can read more from them at their website. View Full User Profile

Upshot.js & Knockout.js: The HTML5 Client for WCF RIA Services

04.02.2012
| 23579 views |
  • submit to reddit

As Microsoft is leaving the Silverlight track and more and more focusing on HTML5 for cross browser support, solutions are coming for several existing frameworks. One of the things I’ve been looking at is the possibility to reuse your existing RIA services ,who you currently use for your Silverlight application, and port them to HTML5 enabled website.

The HTML client for ria services started as an jQuery plugin called Ria.js. Currently this project is hosted into the ASP.NET Single Page Application. This is a new project template for building single page applications. The advantage of these kind of application is that they run completely in the browser, this way it’s pretty easy to enable your application to be used offline.

The JS client framework used for communicating with the server is Upshot.js. This framework provides a rich context for the object used on the client side, this includes change tracking, paging, sorting, … More information of this subject can be found on the Denver Developers blog. If you prefer watching a live demo, Steve Sanderson gave one at TechDays Netherlands.

While searching on the net for some more information, I noticed that the most examples focus on the use of Web API for communication with the server. Next to the standard Web API provider for upshot, there is also support for RIA and OData. Since my post is handling RIA services, I‘ll be handling the riaDataProvider.

Building a live demo

Perquisite:

So lets get started by creating a new MVC4 project:

Create SPAWithRiaServices project

If we press the OK button, we can choose which MVC4 project template we want to use. In our case we choose for the Single Page Application.

Create SPAWithRiaServices project step 2

This template includes already some models, controls and views, but when we open up the scripts folder, we will see the script references for upshot.js, knockout.js, … Everything we need for building an HTML5 application with a rich context. But these frameworks evolve fast and thank god we have a thing called Nuget so we can easily update to the latest version. So this will be the next step in my tutorial, updating to the latest version of the single page application. Search for SinglePageAppliction in your Nuget packet management explorer or type the following in your package manager explorer:

    Install-Package SinglePageApplication.CSharp

Once this is done, we need to add the reference to the ServiceModel so we can use the domainService class of the RIA services. The following references need to be added (note that these references come from the latest version of the WCF Ria Services SDK V1,  C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server):

System.ServiceModel.DomainServices.Hosting
System.ServiceModel.DomainServices.Server

And one reference from the SP2 of the WCF Ria Services SDK V1, C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Toolkit\Libraries\Server\SP2\

Microsoft.ServiceModel.DomainServices.Hosting

After adding the references, we need to configure our DomainServices. First thing we need to do is adding a new configuration section for the DomainService

<configSections>
  <sectionGroup name="system.serviceModel">
    <section name="domainServices" type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, 
      System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
      Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
      allowDefinition="MachineToApplication" requirePermission="false" />
  </sectionGroup>
</configSections>

Next we add a new httpModule for the DomainService to the system.web section

<system.web>
  <httpModules>
    <add name="DomainServiceModule" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
      System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
      Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </httpModules>
</system.web>

In the system.webServer section, we add a new module for the DomainService

<system.webServer>
  <validation validateIntegratedModeConfiguration="false" />
  <modules runAllManagedModulesForAllRequests="true">
    <add name="DomainServiceModule" preCondition="managedHandler" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
       System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
       Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </modules>
</system.webServer> 

As last we add the following system.serviceModel config section. Most important thing here is that we add a JSON endpoint, and on this endpoint we add the attribute transmitMetadata=”true” so the metadata is send to our HTML client.

<system.serviceModel>
  <domainServices>
    <endpoints>
      <add name="JSON" type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory, 
       Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
       Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       transmitMetadata="true" />
    </endpoints>
  </domainServices>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
</system.serviceModel>

Once all the references and configuration sections are added, we can start to code. The first thing we do is adding a new class and let it derive from DomainService.

using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using SPAWithRiaServices.Models;
 
namespace SPAWithRiaServices.Services
{
    /// <summary>
    /// Domain Service responsible for todo items.
    /// </summary>
    [EnableClientAccess]
    public class TodoItemDomainService : DomainService
    {
        [Query(IsDefault = true)]
        public IQueryable<TodoItem> GetTodoItems()
        {
            IList<TodoItem> todoItems = new List<TodoItem>();
 
            todoItems.Add(new TodoItem() 
                { TodoItemId = 1, Title = "Todo item 1", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 2, Title = "Todo item 2", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 3, Title = "Todo item 3", IsDone = false });
            todoItems.Add(new TodoItem() 
                { TodoItemId = 4, Title = "Todo item 4", IsDone = false });
 
            return todoItems.AsQueryable<TodoItem>();
        }
    }
}

The TodoItem Class looks as follows:

using System.ComponentModel.DataAnnotations;

namespace SPAWithRiaServices.Models
{
    public class TodoItem
    {
        [Key]
        public int TodoItemId { get; set; }
        [Required]
        public string Title { get; set; }
        public bool IsDone { get; set; }
    }
}

Now we can go to edit the views. The first view we need to change is the _Layout.cshtml in the shared folder. In here we delete the following line:

<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>

And add the following references instead:

<script src="~/Scripts/jquery-1.6.2.js" 
        type="text/javascript"></script>
<script src="~/Scripts/modernizr-2.0.6-development-only.js" 
        type="text/javascript"></script>
<script src="~/Scripts/knockout-2.0.0.debug.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.compat.knockout.js" 
        type="text/javascript"></script>
<script src="~/Scripts/upshot.knockout.extensions.js" 
        type="text/javascript"></script>
<script src="~/Scripts/native.history.js" 
        type="text/javascript"></script>
<script src="~/Scripts/nav.js" 
        type="text/javascript"></script>

In the index.cshtml in the home folder, we add the following:

The configuration for using upshot
<script type='text/javascript'>
    upshot.dataSources = upshot.dataSources || {};
    
    upshot.dataSources.GetTodoItems = upshot.RemoteDataSource({
        providerParameters: { 
            url: "/SPAWithRiaServices-Services-TodoItemDomainService.svc", 
            operationName: "GetTodoItems" 
        },
        provider: upshot.riaDataProvider,
        bufferChanges: true,
        dataContext: undefined,
        mapping: {}
    });
</script>

The knockout view

<ol data-bind="foreach: todoItems">
    <li><strong data-bind="text: Title"></strong>
        <label>
            <input data-bind="checked: IsDone" type="checkbox" />
            Done
        </label>
    </li>
</ol>

And as last the ViewModel definition

<script type="text/javascript">
    function TodoItemViewModel() {
        // Data
        var self = this;
        self.dataSource = upshot.dataSources.GetTodoItems.refresh();
        self.localDataSource = upshot.LocalDataSource({ 
                                    source: self.dataSource
                                  , autoRefresh: true });
        self.todoItems = self.localDataSource.getEntities();
    }
 
    $(function () {
        ko.applyBindings(new TodoItemViewModel());
    });
</script>

When you run the application now, you should see the todo items appear on your screen with a little delay.

Conclusion

In your HTML applications you can take advantage of your existing RIA services with a minimum of effort. For now the possibilities are limited, but I think in the future we will be able to use all features as they are present in SL. By adding the transmitMetadata attribute to our JSON endpoint, it isn’t necessary to define the model again on the client side.
Published at DZone with permission of Kristof Degrave, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Daniel Blendea replied on Tue, 2012/04/10 - 2:46pm

Can this be used without VS 2011 and MVC 4?

Kristof Degrave replied on Sat, 2012/05/05 - 9:15am

Yes you can, on codeplex I've added 2 solutions. One with SPA and an other one with just upshot, knock-out and ria services. http://ria4htmldemo.codeplex.com/

 greetings,

 Kristof

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.