Tuesday, July 10, 2012

Deploying ASP.NET MVC4 Single Page Applications on Windows Azure Websites

An anonymous poster on my fifth post about Single Page Applications (SPA) asked if it was possible to deploy SPAs on Windows Azure. As it turns out the Windows Azure Web Site offering makes this very easy to do if you build your SPA as outlined in that post.

Windows Azure Websites

Windows Azure Web Sites have been recently announced by Microsoft and are at the time of writing released as a preview feature on Windows Azure. 

If you don't have a Windows Azure account already you can sign up for the free 90-day trial. Since the feature is still in preview, you can sign up for free as well on your Windows Azure Account Page. Activation can take a full day.


Windows Azure Account - Preview Features


Once you have received the e-mail message that your account has been activated, you can navigate to the brand new Windows Azure Portal and create a new website. Since I will be deploying my 'DeliveryTracker' Single Page Application, I will also need to create a database.

New - Web Site - Create With Database

Follow the wizard that appears. If you don't have a SQL Database in Azure already allow the wizard to create one for you.

SQL Databases in Windows Azure

SQL databases are part of the Data Management offering of Windows Azure. You can either manage these databases from the portal or via the SQL Server Management Studio (SSMS). Because I already had an existing SQL database in Azure, I chose to migrate my data from my local SQL Server using SSMS.

Deploy Database to SQL Azure
If your firewall allows it (port 1433 needs to be open) you can deploy to SQL Azure directly. If not, you can choose to 'Export the Data-tier application'. This generates a .bacpac file that you can import in the management window of your SQL Azure.

In any case you need to make sure that the firewall of the SQL Azure server accepts the ip address of your local machine to connect. This you can also manage from the SQL Azure portal

Deploying DeliveryTracker

Now that everything is in place, the actual deployment is quite simple:
  • Download the DeliveryTracker application, unzip it and open it with Visual Studio 2010.
  • Open the web.config file and rename the 'DefaultConnection' to 'AppDbContext' which is the name of the actual Entity Framework Code First DbContext that is used.
  • Right-click the DeliveryTracker project and choose 'Publish...'. A wizard opens.
  • In the first screen ('Profile') of the wizard you can import a publishing profile. This publishing profile is available for download in the management console for your new website in the Azure portal
  • In the third screen ('Settings') of the wizard, make sure that the connection string is called 'AppDbContext' and is pointing to the correct database that was created above in SQL Azure.
  • Click Publish
Visual Studio will now start deploying the website to Windows Azure. It doesn't take too long to finish. You can visit the DeliveryTracker site that I deployed while writing this post. 

I probably should add a 'Reset Test Data' button somewhere :)

Friday, June 29, 2012

Building Single Page Apps with ASP.NET MVC4 - Part 5 - using ASP.NET MVC4 RC

This blogpost is part of a series about building Single Page Applications using ASP.NET MVC 4. Parts 1 through 4 use ASP.NET MVC4 beta but this post will focus on building SPA after upgrading to ASP.NET MVC 4 RC

  1. Single Page Applications - Part 1 - Basic Desktop application
  2. Single Page Applications - Part 2 - Advanced Desktop Application
  3. Single Page Applications - Part 3 - Basic Mobile Application
  4. Single Page Applications - Part 4 - Sorting, Filtering and Manipulation data with upshot.js
  5. Single Page Applications - Part 5 - Using ASP.NET MVC4 RC

In the release candidate for ASP.NET MVC 4, Microsoft decided to remove the Single Page Application template and supporting files. This was no surprise as I already concluded in part 4 of this series that SPA was far from being ready.

So how do you build an SPA after installing MVC4 RC? Peter Porfy described one solution on his blog. Essentially he is taking the latest nightly build of ASP.MVC4 and correcting the errors. The downside to his solution is that it looks like a lot of work and this is still very unstable.

The key to my solution lies in the fact that SPA written against ASP.NET MVC4 beta, still run on machines with only the MVC4 RC installed. All the SPA essentials can be found in NuGet packages. As it happens, I had created a new MyGet feed to enable NuGet Package Restore so I didn't have to include the SPA packages every time I released new code on my blog.

I realize that this means you are not really using the MVC4 RC but at least you can continue building SPA after installing it until Microsoft releases a new and supported version.

Starting a new SPA using VS 2010 with ASP.NET MVC 4 RC

Follow these steps to get started:
ASP.NET SPA MVC4 Beta feed

Create a new MVC project and install the SPA NuGet packages from the PM Console:

  • Menu --> File --> New --> Project --> ASP.NET MVC4 Web Application --> Internet Application
  • Find the packages.config file and remove the line that includes the "EntityFramework". This version 5.0.0-rc RC is incompatible with the version 4.3.1 that we need. The line looks like this:
<package id="EntityFramework" version="5.0.0-rc" targetFramework="net40"/>
  • Go to your project references and remove the reference to EntityFramework here as well. Below screenshot shows both action:
  • Menu --> Tools --> Library Package Manager --> Package Manager Console

Make sure that 'Package source:' points to my MyGet feed otherwise you will download the packages from the official not-supported NuGet source

Install-Package SinglePageApplication.CSharp
Install-Package EntityFramework

After closing and re-opening the solution you're now ready start developing a new SPA

Migrating DeliveryTracker to MVC4 RC

I took the source code of DeliveryTracker4 from my SkyDrive and used this as a starting point for a new project that I also conveniently had called 'DeliveryTracker' to minimize namespace problems

  • This is the list of files and folders I copied and included into the new 'DeliveryTracker' project.
    .\DeliveryTracker\Content\Site.mobile.css
    
    .\DeliveryTracker\Controllers\DataServiceController.cs
    .\DeliveryTracker\Controllers\ViewSwitcherController.cs
    
    .\DeliveryTracker\Models\AppDbContext.cs
    .\DeliveryTracker\Models\DomainModel.cs
    
    .\DeliveryTracker\Scripts\App
    
    .\DeliveryTracker\Views\Home
    .\DeliveryTracker\Views\Shared\_Layout.cshtml
    .\DeliveryTracker\Views\Shared\_Layout.mobile.cshtml
    .\DeliveryTracker\Views\Shared\_SpaScripts.cshtml
    .\DeliveryTracker\Views\Shared\_ViewSwitcher.cshtml
    
  • I changed the connectionstring in both the connectionStrings and entityFramework sections of the web.config file
    <connectionStrings>
      <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=DeliveryTracker;Integrated Security=SSPI" />
    </connectionStrings>
    
    <entityFramework>
      <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
        <parameters>
          <parameter value="Data Source=.; Integrated Security=True; MultipleActiveResultSets=True" />
        </parameters>
      </defaultConnectionFactory>
    </entityFramework>
    

There were some additional steps to take before I could get my DeliveryTracker sample to function correctly.

  • Update the global.asax.cs file to add my database initializer to the Application_Start function
    #if DEBUG
    System.Data.Entity.Database.SetInitializer
      (new DeliveryTracker.Models.AppDbContextSeedInitializer());
    #endif
    
  • Update the site.css file to include the two classes that were added to highlighted changes on screen
    .delivered
    {
        text-decoration: line-through;
        color: #008000;
    }
    
    .updated
    {
        background-color: #FFFF00;
    }
    
  • Update the RouteConfig.cs file to make the DataService controller reachable to accept POST'ed updates.
    routes.MapHttpRoute(
      "DataService", // Route name
      "api/DataService/{action}", // URL with parameters
      new { controller = "DataService" } // Parameter defaults
    );
    

Download the source code

You can download the source code from my SkyDrive (Visual Studio 2010 project)

Tuesday, April 3, 2012

Building Single Page Apps with ASP.NET MVC4 - Part 4 - Sorting, Filtering and Manipulating data with upshot.js

This blogpost is part of a series about building Single Page Applications using ASP.NET MVC 4 beta. When writing this post I assumed you have read the previous ones. The code snippets are just the incremental changes that are needed since the third. post I'd advise to just download the completed source code directly

  1. Single Page Applications - Part 1 - Basic Desktop application
  2. Single Page Applications - Part 2 - Advanced Desktop Application
  3. Single Page Applications - Part 3 - Basic Mobile Application
  4. Single Page Applications - Part 4 - Sorting, Filtering and Manipulation data with upshot.js
  5. Single Page Applications - Part 5 - With MVC4 RC

This post was more or less triggered by this comment on my first post in this series. James wanted to avoid creating GET functions for each single case. I already replied that he could take advantage of the OData features of WebApi but I think a dedicated post is needed since this is a topic where upshot is showing its limitations.

Updating to the latest packages

First thing I did -starting from the code from my previous post- was to upgrade all packages to the latest version. I had to rename the existing upshot.knockout.extensions.js file before updating, since I had modified it and the package manager would skip it instead of updating it. Since I'm working with C#, I used this command in the package manager console
Install-Package SinglePageApplication.CSharp

This will upgrade some dependencies and add new files such as T4 templates to the project. You will need to update some of the links to javascript files because for example knockout-2.0.0.js is upgraded and renamed to simply knockout.js. I also created a namespace 'deliveryTracker' and put my DeliveriesViewModel in it.

After the package update the UpshotContext HtmlHelper function is now available. So in my desktop and mobile views I can now replace the Javascript call to upshot.metadata with following code:

@(Html.UpshotContext(bufferChanges: true)
          .DataSource<DeliveryTracker.Controllers.DataServiceController>
                  (x => x.GetDeliveriesForToday())
          .ClientMapping<DeliveryTracker.Models.Customer>
                  ("deliveryTracker.Customer")
          .ClientMapping<DeliveryTracker.Models.Delivery>
                  ("deliveryTracker.Delivery")
)

<script type="text/javascript">
    $(function () {
        var model = new deliveryTracker.DeliveriesViewModel();
        ko.applyBindings(model);
    });
</script>

This will generate javascript that calls upshot.metadata and also sets up a remote datasource called 'DeliveriesForToday. This means I can now remove the code that creates a new RemoteDataSource in the constructor of my DeliveryViewModel and get it from upshot.dataSources.DeliveriesForToday

/// <reference path="_references.js" />
(function (window, undefined) {
    var deliveryTracker = window["deliveryTracker"] = {};

    deliveryTracker.DeliveriesViewModel = function () {

        // ... snip

        self.dataSource = upshot.dataSources.DeliveriesForToday;

        // ... snip
    };

    deliveryTracker.MobileDeliveriesViewModel = function () {
        //inherit from DeliveriesViewModel
        var self = this;
        deliveryTracker.DeliveriesViewModel.call(self);

        // ... snip
    };

    // ... snip ...

    window["deliveryTracker"] = deliveryTracker;
})(window);

Note the calls to the ClientMapping functions which are used to indicate that I want to map the incoming data to the Customer and Delivery objects that I have created in javascript

Sorting and Filtering when fetching data

        // Initialize remote and local datasource
        self.dataSource = upshot.dataSources.DeliveriesForToday;
        self.dataSource.setFilter({ property: "IsDelivered", 
                                    operator: "==", value: false } ); //,
        self.dataSource.setSort({ property: "Customer/Name", descending: false });
        self.dataSource.refresh();

The remoteDatasource is built on top of an OData service. The above request will generate this HTTP GET

 
GET http://localhost:61907/api/DataService/GetDeliveriesForToday?%24filter=IsDelivered+eq+false&%24orderby=Customer%2FName HTTP/1.1
X-Requested-With: XMLHttpRequest
Accept: application/json, text/javascript, */*; q=0.01
Referer: http://localhost:61907/
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Host: localhost:61907
Connection: Keep-Alive

Note how you can sort and filter on a property of an associated entity by using a forward slash (...property: "Customer/Name"...) because this is supported by OData. The data also got filtered so the browser only received those deliveries where the IsDelivered property was set to false. Unfortunately upshot does not expose the full power of OData to the client application.

Sorting and Filtering the local data

Once data is loaded into the browser it can still be manipulated. In order to see the effect of a manipulation while data is being edited, I decided to set the 'allowRefreshWithEdits: true' property on the local datasource. This lets me sort or group the data when edits are still pending (not uploaded back to the server).
self.localDataSource = upshot.LocalDataSource({ source: self.dataSource, 
                        autoRefresh: true, allowRefreshWithEdits: true });

self.deliveries = self.localDataSource.getEntities();
self.deliveriesForCustomer = self.deliveries.groupBy("Customer");

In order to demonstrate sorting and grouping on the local data, I thought it would be fun to include the geolocation capabilities of the current browsers. If a user allows the application to read his current location, the application will sort the data by 'distance from user'.

I added latitude and longitude data to the my server-side Customer domain entity. This entity and the Delivery entity are both mapped to their javascript counterparts, like so:

deliveryTracker.Customer = function (data) {
 var self = this;

 self.CustomerId = ko.observable(data.CustomerId);
 self.Name = ko.observable(data.Name);
 self.Address = ko.observable(data.Address);
 self.Latitude = ko.observable(data.Latitude);
 self.Longitude = ko.observable(data.Longitude);

 //only exists client-side
 self.DistanceFromMe = ko.observable('unknown');

 upshot.addEntityProperties(self, "Customer:#DeliveryTracker.Models");
};

deliveryTracker.Delivery = function (data) {
 var self = this;

 self.DeliveryId = ko.observable(data.DeliveryId);
 self.CustomerId = ko.observable(data.CustomerId);
 self.Customer = ko.observable(data.Customer ? 
             new deliveryTracker.Customer(data.Customer) : null);
 self.Description = ko.observable(data.Description);
 self.IsDelivered = ko.observable(data.IsDelivered);

 upshot.addEntityProperties(self, "Delivery:#DeliveryTracker.Models");
};

It's important to note that upshot will map the Delivery object automatically but not the Customer object even when specifying it in the ClientMapping function on the UpshotContext. So whenever upshot tries to map the Delivery object, I create an observable Customer object myself. See the highlighted lines above

Now add this to the constructor of the DeliveriesViewModel:

var self = this;
var self.position
// Initialize geolocation
if (navigator.geolocation) {
 self.deliveries.subscribe(CalculateDistanceFromMe); //data is refreshed
 self.position.subscribe(CalculateDistanceFromMe);   //position is refreshed

 navigator.geolocation.getCurrentPosition(function (position) {
  self.position(position);
 });
}

// Recalculate distance from deliverer when data or geolocation changes
function CalculateDistanceFromMe() {

 if (self.position() && self.deliveriesForCustomer 
            && self.deliveriesForCustomer().length > 0) {

  //calculate and update distances
  ko.utils.arrayForEach(self.deliveriesForCustomer(), 
                                           function (keyValuePair) {
   var customer = keyValuePair.key;
   var dist = geolocationUtils.calculateDistance(
                                           self.position().coords.latitude, 
                                           self.position().coords.longitude, 
                                           customer.Latitude(), 
                                           customer.Longitude());
   customer.DistanceFromMe(dist.toFixed(2));
  });

  //sort local datasource on DistanceFromMe ascending
  self.localDataSource.setSort(function (delivery1, delivery2) {
   var value1 = delivery1.Customer().DistanceFromMe();
   var value2 = delivery2.Customer().DistanceFromMe();
   return (value1 - value2);
  });
                self.localDataSource.refresh(); //trigger refresh with sorting

  //sort data grouped by customer
  self.deliveriesForCustomer().sort(function (left, right) {
   var value1 = left.key.DistanceFromMe();
   var value2 = right.key.DistanceFromMe();
   return (value1 - value2);
  });
 };
};

So what's happening:

  • self.position is a knockout (ko) observable that holds the current location.
  • CalculateDistanceFromMe is triggered when self.position or self.deliveries gets updated
  • calculateDistance is a function I found on HTML5 Rocks and wrapped in the 'geolocationUtils' namespace. It is called for each customer when the data gets refreshed
  • The calculated distance is stored in the client-side observable customer.DistanceFromMe property
  • Sorting on the localDataSource passes a sorting function to the setSort method on the dataSource.
  • Sorting on the deliveriesForCustomer passes a sorting function to the sort method off the knockout observable array (wrapper around javascript arrays)

Conclusions

Sorting is a tricky subject when mixing technologies like Microsoft is doing in this beta release of Single Page Applications. Different objects allow different features depending on which part of the abstraction leaks through

The remoteDataSource object is leaking OData conventions so when sorting or filtering you need to comply with the OData Orderby and Filter uri-conventions. For example you can easily sort on the Customer/Name property of an entityset of Delivery objects.

The localDataSource object however has its own implementation of sorting, filtering and grouping. Besides passing a sorting function, it also allows passing in a property name. So you can sort on the CustomerId of an entityset (of Delivery objects) but you cannot sort on Customer/Name

The GetEntities method on a localDataSource returns a Knockout observableArray. This object exposes a wrapper around the built-in javascript arrays and exposes the same functionality which again is different from the remote or local datasources.

What annoys me is that knockout and javascript both have a syntax for navigating associated entities, being the dot operator. If a remoteDataSource allows to sort by "Customer/Name", at least the localDataSource should expose the same interface. Also when binding data into the UI, you can easily do the following:


So navigating the associated entities using the dot operator works fine when data-binding but not when sorting, filtering, grouping, ...

As the Single Page Application are still in an early phase I suppose this is to be expected. In my opinion it's still way too soon to use upshot in its current form in any serious development.

Download the source code

You can download the code (Visual Studio 2010 project) from my Skydrive

Monday, March 26, 2012

Pinning a website - Part 1 - Internet Explorer 9

While attending a Webcamp around HTML 5, the instructor pointed out the "Pin to Taskbar" feature that is available as from Internet Explorer 9. Pinning a site to the taskbar makes your website behave more like a desktop application, allowing users to select from fixed tasks and/or a dynamic jumplist, receive notifications, see thumbnail previews... all available from a prominent location on screen showing your website's logo.

My blog pinned to my Taskbar

You can find all information and guidance on the 'Build my Pinned Site' website. Obviously I had to try it out for myself so if you are on Internet Explorer 9 or higher, you should be able to pin my blog to your taskbar. The result of doing this should look like this at the moment:

  • My brand new logo always present on the desktop
  • The right-click menu containing:
    • A link to my homepage
    • A fixed 'Tasks' section showing a link to my LinkedIn profile
    • A dynamic jumplist with my 10 most recent posts
As long as you don't pin this blog to your taskbar you will see a teaser bar at the bottom of my page informing you that this site can take advantage of the 'Pin to taskbar' feature

Generating the script

Microsoft provides a quick way of generating the script that is needed to enable the feature. When you start their wizard you'll need to provide some basic information, a list of fixed tasks, upload a 64x64 icon file, indicate if you want a teaser bar and give the URL to your RSS or Atom feed to generate the dynamic jump list. This generates a single JavaScript file that is hosted on the 'BuildMyPinnedSite' data servers, along with the icon you uploaded. So all you need to do is insert a reference to this script file on your page and you are good to go.

Unfortunately when I first inserted that script in my blog there were some issues:

  • The teaser bar was inserted on a fixed position on the top of the page where it collided with the Google navigation bar.
  • The URLs in my dynamic jumplist pointed to the 'comments' pages instead of the posts themselves.
  • I linked to the JavaScript file from within my blogspot template file, meaning that I would lose it if I were to choose a different template.

Making it work on Blogspot

Blogspot allows inserting custom HTML and JavaScript via their 'HTML/JavaScript' gadget. You can find it when you put your blog in design mode, choose layout and click 'Add Gadget'. I choose a wide gadget at the bottom of my screen because the generated teaser bar is quite wide.

Since the script has to do everything by itself, the entire construction of the teaser bar is done via script. I don't need that kind of dynamic behavior on this blog so I extracted the teaser bar <div id="___ie9sitepinning__bar_container"> from JavaScript and converted it to HTML. I also don't want the teaser bar to decide its own position. It should display in the area that is reserved for the gadget as defined by the template and chosen by the user.

These are the changes I made to the inline style of the root div:

  • Changed the style so it gets positioned inside the gadget's area
    • from "position: absolute; top: 0; left: 20px;width: 95%;"
    • into "position: relative; top: 0; left: 0px;width: 100%;"
  • Add the "display:none;" style because I don't want this div to appear by default
The HTML that I put in 'HTML/JavaScript' gadget looks like this:

<div id="___ie9sitepinning__bar_container" style="display:none; position: relative; top: 0; left: 0px;width: 100%; margin: 0; padding: 0; border: 0 none; border-bottom: 1px solid #707070;color: #1c1f26; background: transparent none no-repeat scroll 0 0; font-family: 'segoe ui', Arial, tahoma, sans-serif;line-height: 18px; box-shadow: 0 1px 5px rgba(140,140,140,0.7);">
 <div style="border: 1px solid #E1E1E1; padding: 5px 9px 2px 9px; background: #fff url(http://www.buildmypinnedsite.com/PinImages/Bar/bar-background.png) repeat-x scroll 0 100%;">
  <table cellspacing="0" cellpadding="0" style="width: 100%; border: 0 none; border-collapse: collapse;">
   <tbody>
    <tr>
     <td>
      <div style="background: transparent url(http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.png) no-repeat scroll 0 3px;background-size: 20px 20px; width: 260px; padding-left: 26px; min-height: 30px;font-weight: bold; font-size: 14px;">
       Experience my Software Cookbook as a Pinned Site
      </div>
     </td>
     <td>
      <div style="display: inline-block; padding-right: 40px; position: relative; text-align: right;">
       <strong style="font-weight: bold; font-size: 18px;">Drag this icon to your taskbar
        <img src="http://www.buildmypinnedsite.com/PinImages/Bar/arrow-icon.png" alt="Arrow" />
       </strong>
       <div style="color: #797c85; font-size: 12px;">
        or, <a href="#" onclick="window.external.msAddSiteMode(); return false" style="color: #6CABBA;text-decoration: underline;">click here</a> to add this site to your start menu
       </div>
       <div style="position: absolute; right: -120px; top: -13px; width: 164px; height: 143px;background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/drag-icon-placeholder.png) no-repeat scroll 0 0;">
        <img class="msPinSite" style="position: absolute; top: 17px; left: 16px; cursor: move;width: 32px; height: 32px;" src="http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.png" alt="Drag this icon to pin this site" />
       </div>
      </div>
     </td>
     <td>
      <div style="position: relative; float: right; width: 80px; min-height: 30px; padding-left: 23px;padding-right: 30px; background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/info-icon.png) no-repeat scroll 0 6px;">
       <a style="font-size: 12px; color: #6CABBA; text-decoration: underline;" href="http://windows.microsoft.com/en-US/internet-explorer/products/ie-9/features/pinned-sites" target="_blank">Learn about Site Pinning</a>
       <div onclick="document.getElementById('___ie9sitepinning__bar_container').style.display='none';window.sessionStorage.setItem('hideie9sitepinningbar', '1')"
        style="background: transparent url(http://www.buildmypinnedsite.com/PinImages/Bar/close-button.png) no-repeat scroll 0 0;position: absolute; top: 0; right: 0; display: block; width: 18px; height: 18px;cursor: pointer; float: right;">
       </div>
      </div>
     </td>
    </tr>
   </tbody>
  </table>
 </div>
</div>

Notice that the image on the highlighted line 20 has a class attribute set to "msPinSite". This class is recognized by Internet Explorer 9 and higher and enables the 'Drag this icon to your taskbar' functionality

The rest of the JavaScript needs to be encapsulated in a CDATA block and appended below the above div. Notice the two changes (highlighted) I had to make:

Line 87-99

The original code for generating the jumplist downloads the list of links per post and then just takes the first available link. This happens to be the link to the comments page of each blog. According to the information I found on the structure of an Atom feed (as used by blogspot) the perma-link to a post is called the 'alternate' link. So I execute a 'filter' function to the list of links to find the alternate link. If it can't find it, I take the first link just like the original code did

Line 171-172

Instead of dynamically generating the div here, I just find it back using its 'id' and I set the 'display' style to 'block'

<script type='text/javascript'>
//<![CDATA[
    var ____prototype_ae_IE9JumpList = ____prototype_ae_IE9JumpList || {};

    (function (jumplist) {
        if (!navigator.userAgent.toLowerCase().match(/msie (9|10)(\.?[0-9]*)*/)) {
            return;
        }

        var options = {

            // Basic site information 
            siteName: 'Bart\'s Software Cookbook', // Site Name
            applicationName: 'Bart\'s Software Cookbook', // Site Name 
            startURL: 'http://bartjolling.blogspot.com/', // Homepage URL 
            shortcutIcon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Main-c:/users/bart.jolling/pictures/bscb-logo.ico', // Main Site Icon
            tooltip: '',

            // Dynamic jumplist tasks & notifications
            rssFeedURL: 'http://www.buildmypinnedsite.com/RSSFeed?feed=http%3a%2f%2fbartjolling.blogspot.com%2ffeeds%2fposts%2fdefault',
            categoryTitle: 'Posts', // Task group name
            defaultTaskIcon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/GenericTask-c:/users/bart.jolling/pictures/feed.ico', // Generic task icon

            navButtonColor: false,

            // Jumplist tasks { name: Task Label, action: Task URL, icon: Task Icon }
            staticTasks: [{ name: 'Linked-In Profile', action: 'http://www.linkedin.com/in/bartjolling', icon: 'http://ie9pinning.blob.core.windows.net/files/JumpList/634679315872229742/Task0-c:/users/bart.jolling/pictures/linkedin.ico', target: 'tab'}],

            // Drag and drop site pinning bar  
            prompt: true, // Add a site pinning bar on top of my site pages
            barSiteName: 'Bart\'s Software Cookbook' // Site name as it should appear on the pinning bar
        };

        var lib = {
            dom: {
                meta: function (name, content) {
                    var meta = document.createElement('meta');
                    meta.setAttribute('name', name);
                    meta.setAttribute('content', content);
                    return meta;
                },
                link: function (rel, href) {
                    var link = document.createElement('link');
                    link.setAttribute('rel', rel);
                    link.setAttribute('href', href);
                    return link;
                },
                div: function () {
                    return document.createElement('div');
                }
            },
            net: {
                getJSONP: function (URL) {
                    var script = document.createElement('script');
                    script.type = 'text/javascript';
                    script.src = URL + (URL.indexOf('?') != -1 ? '&' : '?') + Date.now();
                    var head = document.getElementsByTagName('head')[0];
                    head.insertBefore(script, head.firstChild);
                }
            }
        };

        jumplist.parseRSSFeed = function parseRSSFeed(news) {
            try {
                if (window.external.msIsSiteMode()) {
                    window.external.msSiteModeClearJumpList();
                    window.external.msSiteModeCreateJumpList(options.categoryTitle);

                    try {
                        // RSS feeds
                        if (news.rss && news.rss.channel && news.rss.channel.item) {
                            for (var items = news.rss.channel.item.slice(0, 10), numItems = items.length, i = numItems - 1, task, pubDate, taskTitle = ''; i >= 0; i--) {
                                task = items[i];
                                pubDate = Date.parse(task.pubDate);
                                taskTitle = task.title ? (typeof task.title == 'string' ? task.title : task.title['#cdata-section'] || '') : '';
                                window.external.msSiteModeAddJumpListItem(taskTitle, task.link, options.defaultTaskIcon);
                            }
                        } else if (news.feed && news.feed.entry) { // Atom feeds
                            for (var items = news.feed.entry.slice(0, 10), numItems = items.length, i = numItems - 1, task, pubDate, taskTitle = '', link = {}; i >= 0; i--) {
                                task = items[i];
                                pubDate = Date.parse(task.published);
                                taskTitle = task.title ? (typeof task.title == 'string' ? task.title : (task.title['#cdata-section'] ? task.title['#cdata-section'] : task.title['#text'] || '')) : '';

                                if (task.link) {
                                    if (typeof task.link == 'string') {
                                        link['@href'] = task.link || '#';
                                    } else if (Object.prototype.toString.call(task.link) === '[object Array]') {
                                         //filter to get the perma link called 'alternate'
                                        links = task.link.filter( function (item) {
                                            return(item['@rel'] == 'alternate');
                                        });
                                        if(links[0]) {
                                          link = links[0];
                                        } else {
                                          link = task.link[0];
                                        }
                                    } else {
                                        link = task.link;
                                    }
                                }

                                window.external.msSiteModeAddJumpListItem(taskTitle, link['@href'] || '#', options.defaultTaskIcon);
                            }
                        }
                    } catch (ex) {
                    }

                    window.external.msSiteModeShowJumpList();
                } else {
                }
            }
            catch (ex) {
            }
        }

        // Init code
        document.addEventListener('DOMContentLoaded', function () {

            try {
                document.getElementsByTagName('body')[0].onfocus = function () {
                    window.external.msSiteModeClearIconOverlay();
                };
            } catch (err) {
            }

            var head = document.getElementsByTagName('head');

            if (!head) {
                return;
            }

            head = head[0];

            var links = document.getElementsByTagName('link'), remove = [];

            for (var i = 0, rel; i < links.length; i++) {
                rel = links[i].getAttribute('rel');
                if (!rel) {
                    continue;
                }
                rel = rel.toLowerCase().replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
                if (rel == 'icon' || rel == 'shortcut icon') {
                    remove.push(links[i]);
                }
            }

            for (i = 0; i < remove.length; i++) {
                head.removeChild(remove[i]);
            }

            if (options.shortcutIcon) {
                head.appendChild(lib.dom.link('shortcut icon', options.shortcutIcon));
            }

            head.appendChild(lib.dom.meta('application-name', options.applicationName));
            head.appendChild(lib.dom.meta('msapplication-tooltip', options.tooltip));

            if (options.navButtonColor) {
                head.appendChild(lib.dom.meta('msapplication-navbutton-color', options.navButtonColor));
            }

            if (options.startURL) {
                head.appendChild(lib.dom.meta('msapplication-starturl', options.startURL));
            }

            for (var i = 0, task; i < options.staticTasks.length; i++) {
                task = options.staticTasks[i];
                head.appendChild(lib.dom.meta('msapplication-task', 'name=' + task.name + ';action-uri=' + task.action + ';icon-uri=' + task.icon + ';window-type=' + task.target));
            }
            if (options.prompt && !window.external.msIsSiteMode() && sessionStorage.getItem('hideIE9SitePinningBar') != '1') {
                var bar = document.getElementById('___ie9sitepinning__bar_container');
                bar.style.display='block';
            }

            jumplist.poll = function () {
                lib.net.getJSONP(options.rssFeedURL, jumplist.parseRSSFeed);
            };

            window.setTimeout(jumplist.poll, 30);
        });
    })(____prototype_ae_IE9JumpList);
//]]>
</script>

Pinning my blog site seems a bit over the top but I can easily imagine business cases for this feature. Web applications are starting to behave more and more like desktop applications and this is one of the things that makes the end-user experience richer and provides the content owner with a means to grab the users' attention and get them to visit their site more frequently.

Thursday, March 15, 2012

Building Single Page Apps with ASP.NET MVC4 - Part 3 - Basic Mobile Application

This blog is part of a series about building Single Page Applications using ASP.NET MVC 4 beta. When writing this post I assumed you have read the first and second posts. The code snippets are just the incremental changes that are needed since the second post

  1. Single Page Applications - Part 1 - Basic Desktop application
  2. Single Page Applications - Part 2 - Advanced Desktop Application
  3. Single Page Applications - Part 3 - Basic Mobile Application
  4. Single Page Applications - Part 4 - Sorting, Filtering and Manipulation data with upshot.js
  5. Single Page Applications - Part 5 - With MVC4 RC

The mobile viewmodel

In this post I'm adding a mobile component to the DeliveryTracker application I have been describing in my previous posts. I want to have an overview page per delivery, an overview page per customer and a detail view for the currently selected delivery. In order to do that, I'll create a new MobileDeliveriesViewModel in the DeliveriesViewModel.js file. This new class inherits from the existing DeliveriesViewModel class to inherit the existing features but adds some of its own:

  • self.currentDelivery is an observable (knockout.js) variable to keep track of the currently selected delivery which we need in order to navigate from the overview pages to the detail page
  • self.nav keeps the navigation history (nav.js) for our single page. It gives the impression to the user of navigating between different pages as usual. But it enables back button and allows creation of bookmarks within the single page application.
    • The name of the view will be stored in the view parameter. The default page will be the deliveries view.
    • A deliveryId paramater will be included in the querystring and intialized to null
    • On navigation we extract the deliveryId from the querystring and use it to filter the datasource
  • self.showsomething functions to invoke the navigation functionality. The showDelivery function receives the delivery object that was clicked so it can extract the deliveryId from it and attach it to the querystring so the delivery view can read it.
function MobileDeliveriesViewModel() {
  //inherit from DeliveriesViewModel
  var self = this;
  DeliveriesViewModel.call(self);

  // Data
  self.currentDelivery = ko.observable();

  self.nav = new NavHistory({
      params: { view: 'deliveries', deliveryId: null },
      onNavigate: function (navEntry) {
          var requestedDeliveryId = navEntry.params.deliveryId;            
          self.dataSource.findById(requestedDeliveryId, self.currentDelivery);
      }
  });

  //Operations
  self.showDeliveries = function () 
       { self.nav.navigate({ view: 'deliveries' }) };
  self.showCustomers = function () 
       { self.nav.navigate({ view: 'customers' }) };

  self.showDelivery = function (delivery) {
      self.nav.navigate({ view: 'delivery', 
                    deliveryId: delivery.DeliveryId() })
  };

  self.nav.initialize({ linkToUrl: true });
}

Note that the onNavigate function uses the findById method on the upshot datasource that doesn't exist in the original beta release of ASP.NET MVC 4. Add the following javascript code to the upshot.knockout.extensions.js file. The highlighted line below is already present in the file so don't add it again.

(function (ko, upshot, undefined) {
    upshot.RemoteDataSource.prototype.findById = function (id, updateTarget) {
        function search() {
            var self = this;
            var foundEntity = 
                $.grep(ko.utils.unwrapObservable(self.getEntities()), 
                function (entity) {
                   return self.getEntityId(entity) === updateTarget._id;
                })[0];
            updateTarget(foundEntity);
        }

        if (!('_id' in updateTarget)) {
            // TODO: What is the provision to 'unbind' here?
            this.bind("refreshSuccess", search);
        }
        updateTarget._id = id;
        search.call(this);
    };

Also note that in the parent DeliveriesViewModel class, bufferChanges is put back to false in order to have immediate upshot synchronization with the service back-end

function DeliveriesViewModel() {
    // Private
    var self = this;
    var dataSourceOptions = {
        providerParameters: { url: "/api/DataService",
            operationName: "GetDeliveriesForToday"
        },
        entityType: "Delivery:#DeliveryTracker.Models",
        bufferChanges: false,
        mapping: Delivery
    };

    // Snip the rest of the code
    // ...
    // ...
}

The mobile layout

ASP.NET MVC can support mobile browsers by letting you create specific mobile pages using the ".mobile.cshtml" extension. In order to test these pages I installed this User-Agent Switcher for Chrome.

In the \Views\Shared folder, create two new files:

  • _SpaScripts.cshtml: partial view that groups the different javascript includes
  • _Layout.mobile.cshtml: layout page for mobile browsers

The _SpaScripts.cshtml: is quite simply

<script src="~/Scripts/jquery-1.6.4.min.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.js"
        type="text/javascript"></script>
<script src="~/Scripts/arrayUtils.js"
        type="text/javascript"></script>
<script src="~/Scripts/upshot.min.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>

The _Layout.mobile.cshtml: includes the _SpaScripts partial view and the navigation bar. The two list items in the navigation bar use data-binding to attach their functionality in a declarative way.

  • Their click events are linked to their respective show functions in the MobileDeliveriesViewModel
  • Their CSS style is adapted so they appear as active when their view is showing. You can find the Site.Mobile.css file in the code download at the end of this post.
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <link href="~/Content/Site.Mobile.css" rel="Stylesheet" type="text/css" />       
    <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")">
    </script>
    <meta name="viewport" content="width=device-width" />

    @Html.Partial("_SpaScripts")

    <script src="~/Scripts/App/DeliveriesViewModel.js"
            type="text/javascript"></script>

  </head>
  <body>
    <nav class="bar">
        <ul>
          <li data-bind="click: showDeliveries, 
              css: { active: nav.params().view == 'deliveries' }">Deliveries</li>
          <li data-bind="click: showCustomers, 
              css: { active: nav.params().view == 'customers' }">Customers</li>
        </ul>
    </nav>

    @RenderBody()
  </body>
</html>

The mobile views

Now add a Index.mobile.cshtml to the \Views\Home folder. It's quite simple. When the DOM is ready a javascript function initializes the upshot library and initializes the knockout bindings to the MobileDeliveriesViewModel. The actual views are defined as partial views that are shown whenever their views are active.

Notice that the _DeliveryDetails view in Steven Sandersons example used the with: data-bind parameter. I couldn't get it to work so I encapsulated it within a parent div that checks the active view

@{
    ViewBag.Title = "Deliveries";
}

<script type="text/javascript">
    $(function () {
        upshot.metadata(@(Html.Metadata
                 <DeliveryTracker.Controllers.DataServiceController>()));
        ko.applyBindings( new MobileDeliveriesViewModel( ));
    });
</script>

<div data-bind="if: nav.params().view == 'deliveries'">
    @Html.Partial("_DeliveriesList")
</div>

<div data-bind="if: nav.params().view == 'customers'">
    @Html.Partial("_CustomersList")
</div>

<div data-bind="if: nav.params().view == 'delivery'">
    <div data-bind="with: currentDelivery">
        @Html.Partial("_DeliveryDetails")
</div>

The final items left to do are defining the partial views themselves. The only new functionality here is the $root pseudo-variable used by knockouts for-each data-binding which is used to bind the showDelivery method of the MobileDeliveriesViewModel to each item in the list

_CustomersList.cshtml

<div class="content">
    <label><input type="checkbox" data-bind="checked: excludeDelivered" />
              Only show undelivered items</label>

    <div data-bind="foreach: deliveriesForCustomer">
        <h4 data-bind="text: key.Name"></h4>
        <ol class="deliveries" data-bind="foreach: values">
            <li data-bind="text: Description, 
                           css: { updated: IsUpdated, delivered: IsDelivered }, 
                           click: $root.showDelivery"></li>
        </ol>
    </div>
</div>

_DeliveriesList.cshtml

<div class="content">
    <label><input data-bind="checked: excludeDelivered" type="checkbox" />
              Only show undelivered items</label>

    <ol class="deliveries" data-bind="foreach: deliveries">

        <li data-bind="css: { updated: IsUpdated, delivered: IsDelivered },
                                        click: $root.showDelivery">
            <span data-bind="text: Description"></span>
            <span class="details" data-bind="text: Customer().Name"></span>
        </li>

    </ol>
</div>

_DeliveryDetails.cshtml

<div class="title bar">
    <button data-bind="click: History.back">&lt; Back</button>
    <h1 data-bind="text: Description"></h1>
</div>

<div class="content">
    <p>Delivery #<strong data-bind="text: DeliveryId"></strong></p>

    <div class="callout" data-bind="with: Customer">
        <h1>Customer</h1>

        <label>Name</label>
        <strong data-bind="text: Name"></strong>
        
        <label>Address</label>
        <strong data-bind="text: Address"></strong>
    </div>
    
    <label class="checkbox" data-bind="css: { delivered: IsDelivered }">
        <input type="checkbox" data-bind="checked: IsDelivered" /> Delivered
    </label>
</div>

Following navigation map shows the mobile application in action

  • The 'deliveries' screen with the list of deliveries. Clicking a delivery navigates to the 'delivery' screen
  • The 'customers' screen with the list of deliveries grouped by customer. Clicking a delivery navigates to the 'delivery' screen. In the URL you can see the 'view=customer' parameter.
  • The 'delivery' screen containing the delivery details per item. In the URL you can see the 'view' and the 'deliveryId' parameters. Clicking 'Back' navigates back to the previous page.
  • All of these views exist on the same webpage but by changing the URL, the back button and bookmarking functions as you would expect

Download the source code

You can download the source code (Visual Studio 2010 solution) from my Skydrive.

Wednesday, March 7, 2012

Building Single Page Apps with ASP.NET MVC4 - Part 2 - Advanced Desktop Application

This is the second article in a series about building single page applications using ASP.NET MVC4. I assume that you have read the first part.
  1. Single Page Applications - Part 1 - Basic Desktop application
  2. Single Page Applications - Part 2 - Advanced Desktop Application
  3. Single Page Applications - Part 3 - Basic Mobile Application
  4. Single Page Applications - Part 4 - Sorting, Filtering and Manipulation data with upshot.js
  5. Single Page Applications - Part 5 - With MVC4 RC

In part 1 I recreate the first part of a demo by Steven Sanderson that shows a list of deliveries on screen, allows a user to mark an item as delivered and have the results immediately sent back to the server. In this second part the application will also show the deliveries grouped by customer and uses "Save All" and "Revert All" buttons so that the user can decide when to update the back-end.

Group by Customer

In order to group deliveries by customer, you'll need to a bit of javascript code that can go over the observable 'deliveries' array and group them together based on the referenced customer. This grouped data needs to be observable too. Steven has released the following bit of javascript called arrayUtils.js in order to achieve this:



/// <reference path="knockout.debug.js" />

(function () {
    function firstWhere(array, condition) {
        for (var i = 0; i < array.length; i++)
            if (condition(array[i]))
                return array[i];
    }

    ko.observableArray.fn.groupBy = function (keySelector) {
        if (typeof keySelector === "string") {
            var key = keySelector;
            keySelector = function (x) 
                          { return ko.utils.unwrapObservable(x[key]) };
        }
        var observableArray = this;

        return ko.computed(function () {
            var groups = [], array = observableArray();
            for (var i = 0; i < array.length; i++) {
                var item = array[i],
                existingGroup = firstWhere(groups, function (g) 
                                { return g.key === keySelector(item) });
                if (existingGroup)
                    existingGroup.values.push(item);
                else
                    groups.push({ key: keySelector(item), values: [item] });
            }
            return groups;
        })
    }
})();
You can now add a new datasource to the DeliveriesViewModel.js like this:
    self.deliveries = self.dataSource.getEntities();
    self.deliveriesForCustomer = self.deliveries.groupBy("Customer");
To show this new information on screen, add following code to index.cshtml. The groupBy operator has put every customer object on the "key" parameter and all his deliveries on the "values" parameter.
    <h3>Customers</h3>
    <ul data-bind="foreach: deliveriesForCustomer">
        <li>
            <div>Name: <strong data-bind="text: key.Name"></strong></div>
            <ul data-bind="foreach: values">
                <li data-bind="text: Description, 
                                css: { highlight: IsDelivered }">                
                </li>
            </ul>
        </li>    
    </ul>

If you would run the application now you will notice that knockout.js is keeping the values is the deliveries list and the customers list in sync.

Save, Revert, Exclude if delivered

Upshot can be configured to NOT synchronize the changes to the backend, meaning the user will have to click a "Save" button when he wants to submit. There is also the possibility to undo the accumulated changes or to put a filter on the datasource. In order to enable above functionality, make the following changes to the DeliveriesViewModel.js
  • Set bufferChanges to true so that the upshot datasource will keep the changes in memory and not sync them with the back-end
  • Create a self.localDataSource that we can use for client-side operations like filtering the data
  • Create a self.excludeDelivered variable to keep track of the value of the checkbox saying the user wants to exclude already delivered items from the list
  • Create the self.saveAll and self.revertAll function to commit or revert the changes on the upshot datasource
  • Subscribe a function to the self.excludeDelivered observable that gets called everytime the value changes. Inside this function a new filter rule is created which is then applied to the local data source
/// <reference path="_references.js" />

function DeliveriesViewModel() {
    // Private
    var self = this;
    var dataSourceOptions = {
        providerParameters: { url: "/api/DataService",
                    operationName: "GetDeliveriesForToday" },
        entityType: "Delivery:#DeliveryTracker.Models",
        bufferChanges: true,
        mapping : Delivery
    };

    // Public Properties
    self.dataSource = new upshot.RemoteDataSource(dataSourceOptions).refresh();
    self.localDataSource = upshot.LocalDataSource({ source: self.dataSource,
                                               autoRefresh: true });

    self.deliveries = self.localDataSource.getEntities();
    self.deliveriesForCustomer = self.deliveries.groupBy("Customer");
    self.excludeDelivered = ko.observable(false);

    // Operations
    self.saveAll = function () { self.dataSource.commitChanges() }
    self.revertAll = function () { self.dataSource.revertChanges() }

    // Delegates
    self.excludeDelivered.subscribe(function (shouldExcludeDelivered) {
        var filterRule = shouldExcludeDelivered 
            ? { property: "IsDelivered", operation: "==", value: false }
            : null;
        self.localDataSource.setFilter(filterRule);
        self.localDataSource.refresh();
    });
}

Include the new arrayUtils.js in the Index.cshtml file

<script src="~/Scripts/arrayUtils.js"
        type="text/javascript"></script>
<script src="~/Scripts/App/DeliveriesViewModel.js"
        type="text/javascript"></script>
To take advantage of the functionalities you can bind the view to the functions that have been put in place in the DeliveriesViewModel
  • Added two buttons of which knockout binds the click event to the appropriate functions in the DeliveriesViewModel
  • The "Exclude Delivered items" checkbox bound to the 'excludeDelivered' property in the viewmodel
  • The 'delivered' CSS class is bound to the existing 'IsDelivered' property which was foreseen on the view model. The 'updated' CSS class however is bound to the 'IsUpdated' property that comes built-in with the observable datasource
<div>
    <h3>Deliveries</h3>

    <button data-bind="click: saveAll">Save All</button>
    <button data-bind="click: revertAll">Revert All</button>

    <label>
        <input data-bind="checked: excludeDelivered" type="checkbox" />
         Exclude delivered items</label>

    <ol data-bind="foreach: deliveries">
        <li data-bind="css: { delivered: IsDelivered, 
                                        updated: IsUpdated}">
            <strong data-bind="text: Description"></strong>
            is for <em data-bind="text: Customer().Name"></em>
            <label><input data-bind="checked: IsDelivered" 
                  type="checkbox"/>Delivered</label>
        </li>
    </ol>

    <h3>Customers</h3>
    <ul data-bind="foreach: deliveriesForCustomer">
        <li>
            <div>Name: <strong data-bind="text: key.Name"></strong>
            </div>
            <ul data-bind="foreach: values">
                <li data-bind="text: Description, 
                    css: { delivered: IsDelivered,
                             updated: IsUpdated}">                
                </li>
            </ul>
        </li>    
    </ul>
</div>
I've defined these new styles in Site.css
.delivered
{
    text-decoration: line-through;
    color: #008000;
}

.updated
{
    background-color: #FFFF00;
}

If you now mark items as delivered, they will get the 'delivered' AND 'updated' styles in both lists. These are nicely kept in sync by upshot. When the user clicks the "SaveAll" button, all changes will be submitted to the server and the 'updated' style will disappear. The "RevertAll" button undoes all the changes. Selecting "Exclude Delivered Items" will remove the delivered items from the shared datasource so they won't show up in either list.

Download the source code

You can download the code (Visual Studio 2010 project file) from my SkyDrive

Monday, February 20, 2012

Building Single Page Apps with ASP.NET MVC4 - Part 1 - Basic Desktop Application

This blog entry is mainly based on the presentation given by Steven Sanderson on TechDays 2012 called “Building Single Page Apps for desktop, mobile and tablet with ASP.NET MVC 4”. Steven’s talk consisted of a large demo that immediately got me interested and made me want to try it out.
There was one big disappointment however. With the latest beta version of ASP.NET MVC 4 and information from the official pages, it was not possible to recreate Steven’s demo. In the following post, I’ll show what I had to do to get things to work. This is the first post of a series
  1. Single Page Applications - Part 1 - Basic Desktop application
  2. Single Page Applications - Part 2 - Advanced Desktop Application
  3. Single Page Applications - Part 3 - Basic Mobile Application
  4. Single Page Applications - Part 4 - Sorting, Filtering and Manipulation data with upshot.js
  5. Single Page Applications - Part 5 - With MVC4 RC

New Project Template

The ASP.NET MVC 4 beta adds a new project template in Visual Studio 2010 called “The “Single Page Application" (SPA). When creating a new project and starting it up, you’ll get some guidance about completing a first demo application for adding ToDoItems using a new TasksController. I will not do this but instead I will follow the same sequence as Steve did in his presentation and create a new SPA application called “DeliveryTracker”

Server side Domain Model

Since this demo application is a tool to track deliveries we will be using the following datamodel. The Customer class shows how to define a one-to-many relationship.
namespace DeliveryTracker.Models
{
    public class Customer
    {
        public int CustomerId { get; set; }

        public string Name { get; set; }
        public string Address { get; set; }
    }

    public class Delivery
    {
        public int DeliveryId { get; set; }
        public virtual int CustomerId { get; set; }
        public virtual Customer Customer { get; set; }

        public string Description { get; set; }
        public bool IsDelivered { get; set; }
    }
}
In order to store delivery information in the database, we are using the Entity Framework Code First approach
namespace DeliveryTracker.Models
{
    public class AppDbContext : DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Delivery> Deliveries { get; set; }
    }
}

Web API Data Service

The data for the application is loaded from a DbDataController which was introduced with the new Web API feature. In the solution explorer, right-click the controller folder and add a new controller called “DataServiceController” based on the “Empty Controller” template. The purpose here is to show what is happening behind the scenes and not rely on scaffolding too much. Replace the parent Controller class by DbDataController using the AppDbContext as generic parameter.
namespace DeliveryTracker.Controllers
{
    public class DataServiceController : DbDataController<AppDbContext>
    {

        public IQueryable<Delivery> GetDeliveriesForToday() 
        {
            return DbContext.Deliveries.Include("Customer")
                            .OrderBy(x => x.DeliveryId);
        }

        public void InsertDelivery(Delivery delivery) 
                  { InsertEntity(delivery); }
        public void UpdateDelivery(Delivery delivery) 
                  { UpdateEntity(delivery); }
        public void DeleteDelivery(Delivery delivery) 
                  { DeleteEntity(delivery); }
    }
}
The GetDeliveriesForToday will be used by the page to load the data. The IQueryable type allows sorting, paging, filtering and passing in custom parameters. The other methods are necessary for inserting, updating and deleting entities. If you build and run now, you could run OData queries against the service on http://localhost/api/DataService/GetDeliveriesForToday

Client Side ViewModel -  upshot.js & knockout.js

In order to use this data, the page will use the upshot.js javascript library that knows how to talk to a DbDataController. It will want to map your server side C# model in to a client-side javascript model. Steven has hidden this step behind his mysterious and undocumented Html.UpshotContext class.

Add a new “App” subfolder inside the “Scripts” folder and create a new script there called “DeliveryViewModel.js”. In the constructor of the DeliveriesViewModel, we are using upshot to make a connection to the GetDeliveriesForToday method in the DbDataController class. The results from the query are stored in the self.deliveries property.

Notice that the properties of the Customer and Delivery classes are knockout observables. Knockout keeps the data on your views and viewmodel synchronized. Upshot synchronizes with the data service. The bufferChanges option is set to false, meaning that any data that is changed on the web page will also be submitted to the server and saved directly in the database.
/// <reference path="_references.js" />

function DeliveriesViewModel() {
    // Private
    var self = this;
    var dataSourceOptions = {
        providerParameters: { 
          url: "/api/DataService", 
          operationName: "GetDeliveriesForToday"
        },
        entityType: "Delivery:#DeliveryTracker.Models",
        bufferChanges: false,
        mapping : Delivery
    };

    // Public Properties
    self.dataSource = new upshot.RemoteDataSource(dataSourceOptions)
                                .refresh();
    self.deliveries = self.dataSource.getEntities();   
}

function Customer (data) {
    var self = this;

    self.CustomerId = ko.observable(data.CustomerId);
    self.Name = ko.observable(data.Name);
    self.Address = ko.observable(data.Address);
    upshot.addEntityProperties(self, "Customer:#DeliveryTracker.Models");
}

function Delivery (data) {
    var self = this;

    self.DeliveryId = ko.observable(data.DeliveryId);
    self.CustomerId = ko.observable(data.CustomerId);
    self.Customer = ko.observable(data.Customer);
    self.Description = ko.observable(data.Description);
    self.IsDelivered = ko.observable(data.IsDelivered);
    upshot.addEntityProperties(self, "Delivery:#DeliveryTracker.Models");
}

Index View

In order to trigger the upshot call to the service when the index.cshtml page is loaded, add this code below the title section. All the rest of the code is not needed and can be removed. The Html.MetaData function provides the structure of the types to upshot. Knockout applies the bindings to the DeliveriesViewModel
<script src="~/Scripts/upshot.js" 
          type="text/javascript"></script>
<script src="~/Scripts/knockout-2.0.0.js" 
          type="text/javascript"></script>
<script src="~/Scripts/nav.js" 
          type="text/javascript"></script>
<script src="~/Scripts/native.history.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/App/DeliveriesViewModel.js" 
          type="text/javascript"></script>

<script type="text/javascript">
    $(function () {
        upshot.metadata(@(Html.Metadata
            <DeliveryTracker.Controllers.DataServiceController>()));

        ko.applyBindings( new DeliveriesViewModel( ));
    });
</script>
We can now use the data that is loaded and bound to the DeliveriesViewModel in the HTML. Add this code below the javascript call
<section>
<div>
    <h3>Deliveries</h3>
    <ol data-bind="foreach: deliveries">
        <li data-bind="css: { highlight: IsDelivered}">
            <strong data-bind="text: Description"></strong>
            is for <em data-bind="text: Customer().Name"></em>
            <label>
               <input data-bind="checked: IsDelivered" 
                           type="checkbox"/>
               Delivered</label>
        </li>
    </ol>
</div>
</section>

Routing

If you run this code, you will noticed that loading data from the database and displaying it on page works fine but submitting changes to the services fails. Investigating with Fiddler shows that the service responds to the submit request with the error “The service does not support the POST verb”

What Steven Sanderson also failed to mention is that you need to correct the 'MapHttpRoute' route in the routing table in global.asax.cs like this:
            routes.MapHttpRoute(
                "DataService", // Route name
                "api/DataService/{action}", // URL with parameters
                new { controller = "DataService" } // Parameter defaults
            );
So with the code above you can list the deliveries in the database and update the "IsDelivered" properties in real-time from a single page. All communication consists of AJAX calls handled by the upshot framework.

Download the source code

You can download the code (Visual Studio 2010 project) from my Skydrive

Wednesday, August 4, 2010

Create a bug progress report from a Sharepoint 2003 bug list

Sharepoint 2003 includes a list template for bug tracking. It provides very basic functionality like keeping bug statuses, description, comments, dates, assignments etc. For every update that is made to a bug report, Sharepoint just adds a new item to the list. It doesn't however include a bug progress report.

In order to create such a report, I will create a new view on the bug report list in Sharepoint in order to expose the necessary information. In MS Access I will created a linked table that allows us to perform queries on the data to put it in the correct format for use in a pivot table. This pivot table and pivot chart will contain the bug progress report.

Sharepoint bug reports: progress details

On the sharepoint bug list, create a new view containing the following fields:

  • Id
  • Issue Id
  • Status
  • Modified
  • Current
This will give you the entire history for every Issue but from this information you can't yet determine when a bug changed from one status to another.

MS Access linked tables

In order to create a bug progress report, we need to build a list of status changes for each of the issues. I have created four queries for the different situations that can occur.

The "StatusSequenceHistory" query determines the sequence of Ids of bug reports for each Issue Id. For each change it collects what Id was changed (FromId) and which new Id was created (ToId)

SELECT fr.[Issue Id] AS IssueId,
       fr.Id AS FromId,
       fr.Status AS FromStatus,
       min(to.Id) AS ToId
FROM [Bug Reports: Progress Details] AS fr
LEFT JOIN [Bug Reports: Progress Details] AS [to]
ON fr.[Issue Id]=to.[Issue Id]
WHERE fr.Id < to.Id
  AND to.Current=False
  AND fr.Current=False
GROUP BY fr.[Issue Id], fr.Id, fr.Status
ORDER BY fr.[Issue Id], fr.id;

Not all status will have a ToId and a FromId. The "StatusSequenceNew" query selects bug reports that have just been created but do not have a history yet.

SELECT [Issue Id] AS IssueId, min(Id) AS ToId
FROM [Bug Reports: Progress Details]
GROUP BY [Issue Id]
HAVING Count(Id) = 1;

The "StatusSequenceFirst" query selects the very first bug report that was created.

SELECT [Issue Id] AS IssueId, min(Id) AS ToId
FROM [Bug Reports: Progress Details]
WHERE Current=False
GROUP BY [Issue Id];

The "StatusSequenceLast" query selects the final bug report that was created which in fact is the current version of the bug report

SELECT fr.[Issue Id] AS IssueId,
       max(fr.Id) AS FromId,
       to.Status AS ToStatus,
       to.Id AS ToId
FROM [Bug Reports: Progress Details] AS fr
LEFT JOIN [Bug Reports: Progress Details] AS [to]
ON fr.[Issue Id]=to.[Issue Id]
WHERE fr.Id > to.Id
AND to.Current=True
AND fr.Current=False
GROUP BY fr.[Issue Id], to.id, to.Status
ORDER BY fr.[Issue Id], to.id;

The StatusSequence query

This query joins the complete history together and adds the status for each change

(
SELECT IssueId,FromId, FromStatus, ToId, det.Status as ToStatus
FROM StatusSequenceHistory old
LEFT JOIN [Bug Reports: Progress Details] det
ON old.ToId = det.Id
)
UNION (
SELECT IssueId,FromId, det.Status as FromStatus, ToId, ToStatus
FROM StatusSequenceLast cur
LEFT JOIN [Bug Reports: Progress Details] det
ON cur.FromId = det.Id
)
UNION (
SELECT IssueId, NULL as FromId, NULL as FromStatus, ToId, det.Status as ToStatus
FROM StatusSequenceFirst fir
LEFT JOIN [Bug Reports: Progress Details] det
ON fir.ToId =det.Id
)
UNION (
SELECT IssueId, NULL as FromId, NULL as FromStatus, ToId, det.Status as ToStatus
FROM StatusSequenceNew new
LEFT JOIN [Bug Reports: Progress Details] det
ON new.ToId =det.Id
);

The ProgressReport query

This query adds the number of status changes for each change and for each day

(
SELECT Int(d.Modified) AS ModifiedDate, 
       s.FromStatus AS Status, 
       -1 * COUNT(s.FromStatus) AS Diff
FROM StatusSequence s
RIGHT JOIN [Bug Reports: Progress Details] d
ON s.ToId = d.Id
WHERE s.FromStatus <> ''
GROUP BY Int(d.Modified), s.FromStatus
)
UNION (
SELECT Int(d.Modified) AS ModifiedDate, 
       s.ToStatus AS Status, 
       1 * COUNT(s.ToStatus) AS Diff
FROM StatusSequence s
LEFT JOIN [Bug Reports: Progress Details] d
ON s.ToId = d.Id
GROUP BY Int(d.Modified), s.ToStatus
);

Microsoft Excel: Bug progress report

In Excel you can now create a link to the MS Access database as an external source. Choose the PivotTable/PivotChart option when connecting. Put the Modified Date in the left column of the PivotTable, the Status in the upper row and the Issue Id as in the data fields. Configure the Ids in order to show as a running total

Saturday, January 16, 2010

Windows Activation: No records found for given DNS query.


I recently installed Windows 7 Enterprise Edition on one of my PC. Eager to get my PC up and running with all software I daily use, I skipped activation. When I tried to activate a few days later, I was greeted by this error message: No records found for given DNS querySearching for this message, you might end up on this Microsoft support page telling you to check you DNS settings. In my case this didn't apply at all.

In the end it turns out that I ot this message because I hadn't entered my product key yet because I skipped activation during setup. To solve the issue, I:

  • went to "Control Panel\System and Security\System"
  • clicked "change product key"
  • entered a valid product key

Windows automatically activated.

Saturday, July 11, 2009

Injecting a version number into WiX via TeamBuild

I have found several ways of injecting a version number into Wix via TeamBuild, but most of them require way too much work. After a few days of experimenting I ended up with this solution. My TFSBuild.proj file contains the Major and Minor versions and a target "GenerateBuildNumber" to generate build and revision numbers. I use these to create a 'Versionnumber' property and add that to the <custompropertiesforbuild> property.
<propertygroup>
  <assemblymajorversion>2</assemblymajorversion>
  <assemblyminorversion>7</assemblyminorversion>
  <custompropertiesforbuild>
    SignAssembly=true;DelaySign=false;AssemblyOriginatorKeyFile=$(PrivateKeyLocation)
  </custompropertiesforbuild>
</propertygroup>

<target name="BuildNumberOverrideTarget" dependsontargets="GenerateBuildNumber">
  <createproperty value="$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(GeneratedBuildNumber).$(GeneratedRevisionNumber)">
    <output taskparameter="Value" propertyname="Versionnumber" />
  </createproperty>

  <!-- Add the dynamically generated Versionnumber to the custom build properties for use in the WIX project -->
  <createproperty value="$(CustomPropertiesForBuild);Versionnumber=$(Versionnumber)">
    <output taskparameter="Value" propertyname="CustomPropertiesForBuild" />
  </createproperty>
</target>
For setting the version number in a WiX project, you need to get the value in the Product.wxs file. My predecessor had defined PRODUCTVERSION to contain a placeholder value "_versionnumber_". Note: The official way would have been to use the <defineconstants> in the WiX project file but in my case, other builds were already depending on the placeholder so I didn't want to change this.
<?define PRODUCTVERSION="_versionnumber_"?>
In my WiX project file I have overriden the "BeforeBuild" target to replace the "_versionnumber_" placeholder with the actual value of $(Versionnumber). I'm using the 'File.Replace' task from the SDC Tasks library.
<Target name="BeforeBuild">
     <microsoft.sdc.tasks.file.replace
       Path="Product.wxs"
       OldValue="%22_versionnumber_%22"
       NewValue="%22$(Versionnumber)%22"
       Force="true"
       IgnoreCase="true">
   </Target>
As a final step, I have overriden the "AfterBuild" target of the WiX project to rename the output msi in order to contain the versionnumber in the filename as well.
<Target name="AfterBuild">  
 <Createproperty value="$(TargetDir)%(CultureGroup.OutputFolder)">  
   <Output taskparameter="Value" propertyname="TargetDir">  
 </CreateProperty>  
 
 <Copy sourcefiles="$(TargetDir)$(TargetName)$(TargetExt)"  
       destinationfiles="$(TargetDir)$(TargetName).$(Versionnumber)$(TargetExt)">  
   
 <Delete files="$(TargetDir)$(TargetName)$(TargetExt)">  
</Target>

Monday, November 24, 2008

Making MFC controls available for WinForms through MFC subclassing

When trying to figure out how to use some of my MFC controls in a WinForms application, I came across this article by Rama Krishna Vavilala. As his article was targetting the .NET 1.1 framework, I decided to rework it for .NET 2.0. The main difference is the switch from using Managed Extension for C++ to using C++/CLI.

Static Win32 library for C3DMeterCtrl

As is the case in Rama's article, I will also use Mark C. Malburg's Analog Meter Control. So first, create a Win32 static library project with support for MFC and precompiled headers called "ControlS" (S stands for static). This will contain the MFC code for the existing 3DMeterCtrl control. Place the files 3DMeterCtrl.cpp, 3DMeterCtrl.h and MemDC.h in the "ControlS" project. Modify the 3DMeterCtrl.cpp file to remove the line #include "MeterTestForm.h".

The .NET designer and runtime will call functions that try to talk to your MFC control even before its window handle is created. In case of the C3DMeterCtrl, I needed to add this function call at the beginning of the "UpdateNeedle" and "ReconstructControl" functions:

if (!GetSafeHwnd())
  return;

MFC library for the managed 'ThreeDMeter' control

To bridge the gap between MFC and .NET I'm going to use C++/CLI. This allows me to create a managed wrapper object around the MFC control.

Add an "MFC DLL" project, called "control". Go to the project properties and enable the common language runtime support (/clr). Using the "Add Class" wizard add a new control and call it "ThreeDMeter". Make these changes to the ThreeDMeter.h file:

  • #include the header file of the MFC control "..\ControlS\3DMeterCtrl.h"
  • Change the inheritance of the control to public System::Windows::Forms::Control
  • Add a private instance of C3DMeterCtrl to the class. Create it in the constructor and delete it in the finalizer. In "OnHandleCreated", call its "SubclassWindow" method using the .NET controls window handle.
The file "ThreeDMeter.h" should now look like this:
#pragma once

using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;

#include "..\ControlS\3DMeterCtrl.h"

namespace Control {

public ref class ThreeDMeter : public System::Windows::Forms::Control
{
public:
ThreeDMeter(void)
{
  InitializeComponent();
  m_pCtrl = new C3DMeterCtrl();
}

protected:
//Finalizer
!ThreeDMeter()
{
  if (m_pCtrl != NULL)
  {
    delete m_pCtrl;
    m_pCtrl = NULL;
  }
}

//Destructor
~ThreeDMeter()
{
  if (components)
  {
    delete components;
  }

  //call finalizer to release unmanaged resources.
  this->!ThreeDMeter();
}

virtual void OnHandleCreated(EventArgs^ e) override
{
  System::Diagnostics::Debug::Assert(m_pCtrl->GetSafeHwnd() == NULL);

  m_pCtrl->SubclassWindow((HWND)Handle.ToPointer());

  Control::OnHandleCreated(e);
}

private:
System::ComponentModel::Container ^components;
C3DMeterCtrl* m_pCtrl;
};
In order to expose the properties of the MFC control in .NET, you need to implement them yourself. Add following code in the public section of the ThreeDMeter wrapper class.
event EventHandler^ OnValueChanged;

[property: System::ComponentModel::CategoryAttribute("Meter")]
property Color NeedleColor
{
  Color get()
  {
    if( m_pCtrl == NULL )
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    return System::Drawing::ColorTranslator::FromWin32(m_pCtrl->m_colorNeedle);
  }
  
  void set(Color clr)
  {
    if (!m_pCtrl)
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
  
    m_pCtrl->SetNeedleColor(ColorTranslator::ToWin32(clr));
  }
}

[property: System::ComponentModel::CategoryAttribute("Meter")]
property String^ Units
{
  void set(String^ units)
  {
    if (!m_pCtrl)
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
  
    CString strUnits(units);
  
    m_pCtrl->SetUnits(strUnits);
  }
  
  String^ get()
  {
    if (!m_pCtrl)
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    LPCTSTR szUnits = (m_pCtrl->m_strUnits);
  
    return gcnew String(szUnits);
  }
}

[property: System::ComponentModel::CategoryAttribute("Meter")]
property double Value
{
  double get()
  {
    if (!m_pCtrl)
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    return m_pCtrl->m_dCurrentValue;
  }
  
  void set(double d)
  {
    if (!m_pCtrl)
      throw gcnew ObjectDisposedException(ThreeDMeter::GetType()->ToString());
  
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
  
    m_pCtrl->UpdateNeedle(d);
  
    OnValueChanged(this, EventArgs::Empty);
  }
}

.NET Test application

In order to test the managed ThreeDMeter control, add a .NET "Windows application" project to the solution in your favorite language. Put the Three3Meter control on the form and add a timer control
Public Class Form1
  Private Sub Timer1_Tick(ByVal sender As System.Object, 
                          ByVal e As system.EventArgs) Handles Timer1.Tick

    If Me.ThreeDMeter1.Value <= 4 Then
      Me.ThreeDMeter1.Value += (Me.Timer1.Interval / 1000)
    Else
      Me.ThreeDMeter1.Value = -5
    End If
  End Sub
End Class

This will make the meter move from left to right.

Wednesday, November 19, 2008

Deleting absent, updating existing and inserting new rows in a table

Synchronizing a table based on a flat files is one of the more common tasks when dealing with databases. It should be a straight forward task to delete removed records, update changed records and insert new records but I often encounter horrible ways of achieving this goal.

I recently came across an implementation using a DTS package with a data-driven task in SQL Server 2000. For each line in the file it would launch several queries to detect if it was a new, updated or unchanged record (all glued together with VBScript) and then launch the appropriate query to update the database.

Searching a bit on the internet I came across this simple solution. The best way of doing this fast and reliably is by using the JOIN and LEFT JOIN clauses in your UPDATE, INSERT and DELETE commands to determine the status of the record. I would like to show my implementation of slightly more complicated requirements.

Preparation

First start by bulk inserting your data file into a staging table that has no constraints or indexes. This is the fastest way to get your data into the database. For the synchronisation queries (see below), you might want to consider using transactions if there is a risk of data corruption. You could also gain some speed by disabling the indexes while running the insert query.

In the following examples I will try to synchronize the accounts table using the tmp_accounts table as the staging table.

Deleting removed rows

DELETE accounts 
FROM accounts acc LEFT JOIN tmp_accounts tmp
ON  acc.category = tmp.category
AND acc.code     = tmp.code
WHERE acc.code IS NULL
   OR acc.category IS NULL

Fields code and category together uniquely define an account.
Using the LEFT JOIN with the FROM clause will ensure that all records from the accounts table are selected, even if they don't exist in tmp_accounts. The records that don't exist in tmp_accounts will have a NULL value for every field. So all records that exist in the accounts table but have NULL values in their tmp_accounts fields, need to be deleted.

Updating changed rows

UPDATE accounts SET
  description = tmp.description,
  type        = tmp.type,
  section     = tmp.section,
  audit_date  = tmp.audit_date,
  audit_user  = tmp.audit_user
FROM accounts     acc
JOIN tmp_accounts tmp
ON  acc.category = tmp.category
AND acc.code     = tmp.code
WHERE acc.description <> tmp.description
   OR acc.type        <> tmp.type
   OR acc.section     <> tmp.section

By using the JOIN clause, we are sure to work only on records that exist in both the accounts table and the tmp_accounts table. Accounts need to be update in case their description, type or section has changed.

Audit_user and audit_date are automatically filled in the tmp_accounts table using a DEFAULT and are always kept up-to-date in the accounts table.

Inserting new rows

INSERT INTO accounts
( category,
  code,
  description, 
  type,
  section,
  title_only, 
  division,
  override,
  allow_manual_entry, 
  audit_date, 
  audit_user
)
SELECT
  tmp.category,
  tmp.code, 
  tmp.description,
  tmp.type,
  tmp.section,
  tmp.title_only, 
  tmp.division,
  tmp.override,
  tmp.allow_manual_entry, 
  tmp.audit_date,
  tmp.audit_user
FROM tmp_accounts  tmp
LEFT JOIN accounts acc
ON  tmp.category = acc.category
AND tmp.code     = acc.code
WHERE acc.code IS NULL
   OR acc.category IS NULL

Using a LEFT JOIN makes sure we are working with all records in the tmp_accounts table, even if they don't exist in the accounts table. Records that don't exist in the accounts table will have NULL values for all of their fields. So all records that exist in tmp_accounts and have NULL values for the accounts fields, have to be inserted.

Audit_user and audit_date are automatically filled in the tmp_accounts table using a DEFAULT and are always kept up-to-date in the accounts table