Up HTML/JS
DevForce 2010 Resource Center » Code samples » Additional code samples » HTML/JS » Code sample: BigShelf with RIA/JS

Code sample: BigShelf with RIA/JS

Last modified on December 14, 2011 17:36
As of the August 2011 drop of RIA/JS, this walkthrough is now obsolete. jquery.array.js and jquery.datalink.js are no longer included in RIA/JS.

"BigShelf" is an example of an HTML application with a rich, JavaScript client. DevForce on the server provides the data. jQuery and RIA/JS provide the JavaScript framework that handles data binding and client-side access to the DevForce data. This version of "BigShelf" is an adaptation of the "WCF Support for jQuery" sample

This DevForce sample and the original "WCF Support for jQuery" source on which it is based are exploratory projects.  They rely on unsupported Microsoft CTPs and practices that have yet to prove themselves in real world applications. We think it merits your time and interest but we advise against using this code or the included libraries in production applications at this time.


http://download.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=wcf&DownloadId=253353

BigShelf: a RIA/JS client sample

Most DevForce developers build applications in Silverlight, WPF, or Windows Forms client technologies. Some developers want to extend their applications to HTML clients running in browsers or on mobile hardware ... platforms that are not receptive to .NET technologies.  They realize they'll have to develop in HTML and JavaScript. Each wonders

  • can I re-use my DevForce-based assets?
  • can I still build a responsive user experience with substantial client-side execution? 
  • can I write and maintain the code in a familiar entity-oriented, MVVM style with data binding and validation?

"BigShelf" shows that you can. It is far from a definitive answer. It exploits emerging techniques that are unproven ... and many of the dependent technologies have not even been released. But it is suggestive, provocative, and hopeful.

The DevForce version of "BigShelf" described in this topic is an adaptation of the original "BigShelf" sample produced by the Microsoft WCF RIA Services team which is responsible for the RIA/JS libraries. The original is thoroughly explained in the "walkthrough". That "walkthrough is absolutely essential reading and all credit for both the sample and for the RIA/JS libraries belongs to the RIA Services team.

This DevForce version differs from the original only in that it substitutes a DevForce server back-end for the original RIA Services back-end. You'll find a tour of DevForce BigShelf and an overview of the architecture in this video:

Setup

To experience and study the DevForce BigShelf sample

  • Watch the architecture tour video (link above) 
  • Review the original "walkthrough" 
  • Download and unpack the zip file.
  • Review the "ReadMe.txt" file in the unzipped folder to understand the overall structure and how it relates to the actual "WCF Support for jQuery July 2011" download.
  • Build and run

"WCF Support for jQuery" is the original source for the DevForce BigShelf. While the walkthrough is essential reading, its companion source code is less current and less complete than the code at "WCF Support for jQuery".

Prerequisites

Try it

The video shows how a user operates BigShelf at about the 2 minute mark. In brief:

  • Launch
  • Click "Login" link in the upper right.
  • Login with "brado@microsoft.com"; the password is "abc123"
  • The screen with six books appears as shown above
  • Play with the paging and the links at the top
  • Try searching for different titles
  • Click the "Welcome, brado@microsoft.com" message in the upper right
  • The screen displays the Profile editor, loaded with Brad's data
  • Try different names and emails, triggering validation
  • Try adding and removing a friend
  • Click the logo in the upper left to return to the Books screen

If you don't see books on screen (see snapshot above) shortly after logging in, you may have missed a prerequisite. Check the network traffic with the browser tools (F12 in a modern browser) and look for a "500" status code. If you got one, check the response body. If it reports that it can't invoke a method, you are probably missing the WCF RIA Services SP2 preview, a most common omission. 

What to look for in the code

Traditional HTML applications prepare pages on the server and ship them to the browser. JavaScript on the page is mostly decorative, a means to enliven UI controls and adjust visuals to suit the browser.

A new breed of HTML applications give the client more responsibility and more capabilities by expecting JavaScript logic running in the browser to access data asynchronously, manage application state, and respond to user gestures with local resources. The application calls upon the server to get data, not to get a new page.

This approach is familiar to you if you write desktop or Silverlight applications. If you're a DevForce or WCF RIA Sevices Silverlight developer, you are used to querying entities into cache, making them available in a ViewModel, and data binding the ViewModel to the View using markup in XAML. You expect validation logic defined in your server-side entity model to propagate to the client-side entity model without manual coding. You expect the framework to forward user input to those validation rules and, if the input fails validation, you expect to see an error message appear in the UI ... without having to wire these behaviors by hand. With a single "save" command you expect that all modified, validated entities in cache will be sent to the server as a single "change-set" where they will be saved in a single database transaction. 

RIA/JS, in concert with some UI-oriented jQuery plug-ins, strives to replicate these capabilities in JavaScript. It's still a work-in-progress but the fundamentals are evident in this BigShelf sample. 

"Default.aspx" and "MyProfile.aspx" static HTML pages play the role of XAML Views. Markup describes the layout and visual elements, the CSS to use for styles, and the equivalent of data templates for displaying repeated elements such as rows in a grid or listbox.

Corresponding "Default.js" and "MyProfile.js" JavaScript files in the Scripts folder play the role of the ViewModels you'd write in Silverlight or WPF. They call upon helper libraries to access data and bind controls in the HTML to model elements and event handlers. 

The helper libraries, all in the guise of jQuery plug-ins, provide the binding, data access, caching, and validation logic that correspond to the infrastructure you get in the XAML-platform and DevForce or RIA Services. Look for the "DataSource" variables in "Default.js" and "MyProfile.js"; they approximate the role of the custom/generated EntityManager. The "DataContext.js" library defines something akin to the DevForce entity cache. 

Don't take these correspondences literally; use them to find familiar landmarks in the HTML and JavaScript implementation.
 

The Migration to DevForce

This topic concentrates on the distinctive aspects of the DevForce adaptation. The accompanying code is the same as the RIA original except for the entity model and the server-side data services. We didn't touch the client at all, neither the HTML nor the JavaScript.

This topic doesn't explain the client-side application, RIA/JS, or the jQuery libraries. Please consult the walkthrough to learn about these subjects in some detail; you'll get an excellent tutorial on building "BigShelf" almost from scratch.

Here we focus on the few changes necessary to turn a RIA Services sample into a DevForce RIA/JS example. The essence is simple: swap a DevForce Domain Service for the RIA Domain Service.

RIA/JS in its present incarnation can only access data through a WCF RIA Domain Service. It can't directly access a DevForce EntityServer or web service or an OData data source. It can only talk to a Domain Service.

So we give it a RIA Domain Service which happens to be wrapped around a DevForce EntityManager and entity model. It's a transformation in three basic steps: 

  1. Replace the Entity Framework-generated entity classes with DevForce-generated entity classes
  2. Add [Association] attributes to the navigation properties via the metadata "buddy" classes
  3. Revise the application DomainService to derive from DevForceDomainService

#1 - DevForce Entity Model

The sample is contained entirely within a single web project, BigShelf, located in the Scenarios folder.

The original sample ships with its own database (see the App_Data folder)  and an Entity Framework v.4 Entity Data Model (EDM) built "data first". Entity Framework generated the associated entity class file. The DevForce "Entity Framework Designer Extension"]], installed with DevForce, automatically regenerates a DevForce entity class file as soon as you open the EDMX in the designer and save it ... as we did in the DF BigShelf sample. See the results in the Models folder.

We had to take one additional step; we had to add an abstract base class to the model. You'll find the pertinent code in the RiaEntity partial class file in the Models folder. All entities in the model inherit from RiaEntity.

You'd typically add a base class like this in order to define common logic shared by every entity in your model. This sample is too simple to require such logic. We had to add RiaEntity for one reason only: to hide the DevForce entity's EntityAspect property from the RIA Services serialization.

When RIA Services sends entity data over the network, it tries to serialize every scalar property of the entities involved.

A "scalar" property is a property returning a single object. In contrast, a "collection" property returns a collection of objects.

The DevForce EntityAspect property returns an object that cannot be serialized; you'll get an exception if you try to serialize it. We have to tell RIA not to try. We do that by overriding EntityAspect in the RiaEntity base class and decorating the override with the [Exclude] attribute which tells RIA to ignore this property during serialization.

C#
[Exclude]
public override EntityAspect EntityAspect
{
   get { return base.EntityAspect; }
}

#2 - Association Attributes

RIA Services doesn't know about DevForce entity navigation properties. For example, the Book entity has two navigation properties, CategoryName and FlaggedBooks. RIA doesn't know how to return a book's CategoryName entity from the Book.CategoryName property. Nor does it know how to return a list of FlaggedBook instances from the Book.FlaggedBooks property.

These navigation properties are implemented in terms of "associations" between entities. RIA needs to know about these associations in order to return the FlaggedBooks or the CategoryName when some JavaScript code calls "someBook.FlaggedBooks" or "someBook.CategoryName".

RIA can discover these associations in Entity Framework generated classes because it can interpret the EF metadata behind those navigation properties. RIA doesn't understand the DevForce metadata behind the DevForce navigation properties. We have to tell RIA about associations explicitly by adding [Association] attributes to navigation properties in the entity metadata classes (aka, the "buddy classes").

The RIA services wizard generates metadata classes automatically; they're in the BigShelf.metadata file under the Models folder. RIA created a metadata entry for every property of every entity in the model. We've pared away the unused members in the DF BigShelf sample, leaving just the entries for navigation properties and properties with custom validation attributes. Here's the metadata class for Book:

C#
internal sealed class BookMetadata
{
   // Metadata classes are not meant to be instantiated.
   private BookMetadata(){}

    [Association("CategoryName_Book", "CategoryId", "Id", IsForeignKey = true)]
   public CategoryName CategoryName { get; set; }

    [Association("Book_FlaggedBook", "Id", "BookId")]
   public IList<FlaggedBook> FlaggedBooks { get; set; }
}

Three things to notice:

  1. the [Association] attributes, to be described shortly.
  2. no Book properties have custom validations; see ProfileMetadata for examples of validation attributes.
  3. the original return type of FlaggedBooks was changed to IList for clarity. The actual return types of metadata members are irrelevant; only the names matter.

Each [Association] attribute requires four values to describe the relationship between the two entity types involved in a navigation:

  1. the name of the association which can be any arbitrary string that is unique within the model
  2. the name of the property in the Book that anchors the Book end of the relationship.
  3. the name of the property in the related entity that anchors that other entity's end of the relationship.
  4. whether the Book's anchor is a foreign key property (false by default).

The CategoryName "reference navigation" property returns the book's parent CategoryName entity. CategoryName is an entity that represents the book's category, the kind of book it is.

CategoryId is a Book property, the foreign key property that holds a value that matches the integer primary key of the parent CategoryName entity. Accordingly, we had to indicate that CategoryId is a foreign key property by setting IsForeignKey equal to true.

The FlaggedBooks "collection navigation" property returns a list of child FlaggedBook instances denoting the many users who have flagged and rated this book. Each FlaggedBook points back to its parent Book and has a BookId property whose value matches that Book's primary key Id.  BookId is a foreign key in FlaggedBook. But Book's own Id is a primary key, not a foreign key; therefore, the IsForeignKey attribute is unassigned and retains its false default value.

Scroll through the metadata class file to see that we have added [Association] attributes to every navigation property of every entity in the model. That isn't strictly necessary; you only have to decorate navigation properties that you will expose to the JavaScript client.

#3 - BigShelfService Domain Service

The developers of the original application wrote a RIA DomainSevice, called BigShelfService, located in the Services folder. The DevForce sample revises BigShelfService so that it works with a DevForce DomainService.

The core of BigShelfService

The core of BigShelfService defines four "CRUD" methods - a query and three save methods (insert, update, and delete) - for each entity type in the model.

The original BigShelfService inherited from LinqToEntitiesDomainService<T>, a specialized RIA DomainService class that targets the Entity Framework. The type T in this application was BigShelfEntities, the custom Entity Framework ObjectContext for the BigShelf entity model.

The DevForce sample revises BigShelfService so that it inherits from DevForceDomainService<T> instead. Type T is also named BigShelfEntities. But BigShelfEntities was re-defined when we generated the DevForce entity model classes and is now a custom DevForce EntityManager rather than an Entity Framework ObjectContext.

The BigShelfService implementation seems to have changed radically, a development that could be dispiriting if you were converting a large RIA Services model to DevForce. In fact, the new code is fundamentally the same. The service class still consists of four "CRUD" methods for each entity type. We've simply reduced the verbose and repetitive insert, update, and delete implementations to one-liner Insert..., Update..., and Delete... methods that delegate to helpers in the base DevForceDomainService class.

This is a mindless mechanical transformation that results in code such as this:

C#
public partial class BigShelfService : DevForceDomainService<BigShelfEntities>
//public partial class BigShelfService : LinqToEntitiesDomainService<BigShelfEntities>
{
    public IQueryable<Book> GetBooks() { return Manager.Books; }
    public void InsertBook(Book entity) { InsertEntity(entity); }
    public void UpdateBook(Book entity) { UpdateEntity(entity); }
    public void DeleteBook(Book entity) { DeleteEntity(entity); }

    public IQueryable<Category> GetCategories() { return Manager.Categories; }
    // ... and so on for each entity type ...
}

The Manager property returns an instance of BigShelfEntities just as the ObjectContext property did in the original RIA version. There are no other changes.

Moreover, you don't have to write these four methods for every entity type in your DevForce model. You only have to write the subset of methods for the subset of entities you'll actually use in the HTML client. Remember that your regular DevForce application doesn't use this DomainService; the DomainService exists solely to support the HTML client which probably exhibits a fraction of the functionality of your full client application.

BigShelfService.partial

You might omit the core query methods as well; none of them are used in the BigShelf sample. 

When you review the Default.js and MyProfile.js JavaScript files - the "ViewModels" - you may notice that they only call custom query service methods, all of which are defined in the BigShelfService.partial class file that extends the BigShelfService class.

A global search for the "queryName:" parameter of the RIA/JS DataSource component reveals that only four query methods are actually called by the client, all of them defined in BigShelfService.partial.

The first query issued is GetProfileForSearch(), defined as follows:

C#
public IQueryable<Profile> GetProfileForSearch()
{
    var authenticatedProfileId = this.GetUser().Id;
   return this.Manager.Profiles
                       .Include("Friends.FriendProfile")
                       .Include("FlaggedBooks.Book")
                       .Where(p => p.Id == authenticatedProfileId);
}

This is a typical implementation for a service that a RIA/JS client can call. The query method returns an IQueryable; the JavaScript client could supply filter, sort, and paging criteria which would further refine the LINQ query before RIA Services executed it.

Such refinements don't make sense for this particular query which can return at most a single entity (the Profile of the currently logged-in user). Take a look at GetBooksForSearch for a more sophisticated example.

A RIA/JS client can't specify Include clauses that return related entities along with the queried result in a single HTTP response. You can specify the Include clause in the server-side query method as we see here;  GetProfileForSearch ultimately results in the client receiving the selected Profile, the profile's related Friends, their profiles, the profile's related FlaggedBooks and the Books they flag. That's five different kinds of entities in a single HTTP response.

These entities are all poured into the RIA/JS DataContext on the HTML/JavaScript client. You see the benefit of this object-graph retrieval facility on the applications home page: the star ratings and the list of "Just friends" are fed from the entity data in the DataContext cache via the RIA/JS navigation properties on Profile and Book.

The DevForce version of the BigShelfService.partial partial class file is almost the same as the original. Calls to the ObjectContext property have been replaced by calls to the Manager property. A Contains clause in GetBooksForSearch relies on a list of ids instead of an array of ids. There are no other changes.

The sample BigShelfService incorporates timer-delays to simulate network latency. Otherwise, data operations would happen too quickly to see in the demo UI. Obviously you would remove these lines from your real application.

Inside the DevForceDomainService

The DevForce version of the BigShelfService inherits from DevForceDomainService<T>. The original RIA BigShelfService inherited from LinqToEntitiesDomainService<T>.

This final section explains how DevForceDomainService<T> differs from LinqToEntitiesDomainService<T>. You do not have to understand the DevForceDomainService class to develop your own RIA/JS application. You can simply accept it "as is" and proceed as just discussed. If your curiosity gets the better of you, read on.

If you peek inside LinqToEntitiesDomainService<T>, you'll see that it consists mostly of overrides to methods in the RIA Services base DomainServices class. Within most of those overrides are references to an instance of the Entity Framework ObjectContext class. LinqToEntitiesDomainService<T> delegates most of the data access work to an Entity Framework ObjectContext.

We need a DevForce DomainService class for the DevForce adaptation of BigShelf. It's called DevForceDomainService<T> and it's located in the Services folder too. The basic structure and mechanics of DevForceDomainService<T> are just like LinqToEntitiesDomainService<T>. The type T for this application is also called BigShelfEntities but, in this case, it is the generated EntityManager for the BigShelf entity model. 

Our DevForce DomainService delegates to a DevForce EntityManager instead of an Entity Framework ObjectContext. We want all client data requests to route through a server-side EntityManager and forward on to the EntityServer where they run the gauntlet of our server-side business logic if we have any... just as Silverlight or WPF client requests do. 

Feel free to examine and modify DevForceDomainService<T> to suit your needs. The architecture video walks you through it. We think it is mostly self-explanatory ... with the probably exception of the Query method override which looks like this:

C#
public override IEnumerable Query(
  QueryDescription queryDescription,
 out IEnumerable<ValidationResult> validationErrors,
 out int totalCount)
{
     Manager.DefaultQueryStrategy = DefaultQueryStrategy;
     var result = base.Query(queryDescription, out validationErrors, out totalCount);
    // prevent lazy loading after query completed.
    Manager.DefaultQueryStrategy = QueryStrategy.CacheOnly;
    return result;
}

protected virtual QueryStrategy DefaultQueryStrategy
{
   get { return QueryStrategy.Normal; }
}

The RIA Services querying pipeline calls this Query method, passing in something called a QueryDescription. The QueryDescription encapsulates the client's query mashed up with a server-side query method such as the GetProfileForSearch method we saw in BigShelfService.partial.

Somewhere in that QueryDescription is an IQueryable that is ready to be executed. The nested call to base.Query is going to execute the IQueryable in much the way that you would call ToList() on a DevForce EntityQuery<T>. ToList() causes the EntityManager to fetch entities from the database as prescribed by the query.

The manipulation of the QueryStrategy is likely the surprising part. What is going on?

The first step - setting the DefaultQueryStrategy to a default - seems redundant ... and maybe it is. It's a touch of paranoia; we're trying to make sure that the query is applied to the database by default when it arrives at this point in the query pipeline.

You could override the virtual DefaultQueryStrategy property to change the default behavior in a derived DevForceDomainService. And any DevForce query can have its own explicit QueryStrategy which would trump the default. But 99.9% of the time you want the "Normal" QueryStrategy.

Why are we changing the EntityManager.DefaultQueryStrategy to CacheOnly after the EntityManager returns with its query results? The answer has to do with RIA Services' serialization of the results back to the client.

RIA serialization will call every property on every entity in the result set ... including every navigation property. In DevForce, "lazy loading" is turned on by default. If we left it on, every navigation property of every entity would retrieve related entities from the database. And the navigation properties of each of these entities would also be called and they would retrieve their entities from the database. The cascade of database queries could be enormous and all of the retrieved entities would be sent to the client. Circular navigations could produce an infinite loop. 

Setting the EntityManager.DefaultQueryStrategy to CacheOnly turns "lazy loading" off. All navigation properties are satisfied entirely by entities in cache. There will be no additional trips to the database. 

Usually there aren't any related entities in cache when RIA calls those navigation properties. The navigation properties return either the null entity or an empty list. There will be related entities in cache if the query specifies Include clauses ... as we saw in BigShelfService.GetProfileForSearch ... in which case these related entities are included in the HTTP response to the client. A look at network traffic with Fiddler or the browser's debugging tools (F12) reveals the full story.

Created by DevForce on July 27, 2011 12:33

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2012 IdeaBlade