Migrating a Silverlight application to HTML5 - part II

Here is the first part of this article

There are certain factors why we want to migrate Silverlight applications to HTML: end of support, the technology is stuck since 2011, and most important, The NPAPI support in most browsers will, at some point, be discarded (Chrome plans to drop out support by the end of 2014).

Luckily, practically we can use to date technologies with HTML and Javascript to migrate Silverlight technologies, minimizing the effect of dependence:

  • We can translate XAML files to HTML files and use the same MVVM pattern with javascript libraries. In my case, Knockout.js.

  • Instead of call WCF services we can call WebMethods that act as a bridge to the WCF services. The WebMethod can reference the WCF and call their methods.

  • Despite the excellent UI components in Silverlight, we can use javascript libraries to replace UI behaviour.

Taking into account a team that has worked on Silverlight during a long time, and that they know the system, how can you minimize the effect of the new migration, in terms of easiness of the code, without affecting the modification? Certainly a reengineering process may be the best alternative, but the time and cost is another big problem. In our case, the best cost-effective solution is to look the XAML and their associated ViewModel and try to translate it directly to HTML+Javascript.

Migration technique

Our first attempt to migrate the code is try to translate it without too much complexity (the perfect migration is one in which we just translate code to Javascript, without changing the logic or the way things get done). Take, for example, the following case. It’s basically an UI for CRUD operations over an entity

XAML

Using the yeoman’s generator-ko we have an base SPA architecture in which we can play. In my case, I separated the UI, View and Services (for WCF calls) in three folders:

  • Views: contains the HTML files.

  • ViewModels: contains the javascript modules in which the HTML is bound to.

  • Services: contains service implementations (more on this later).

The HTML is very straightforward to migrate, as far as it does not contains special UI elements (in case you have special UI elements you need to find the corresponding HTML/javascript component and need to add a bindingHandler with knockout. We’ll see this later in another article).

Let’s see how we can migrate the .cs Viewmodels with their corresponding .js Viewmodel:

    
    public class MyViewModel
    {
        // Interface that's injected through ServiceLocator in the Load() method.
        private IParameterService _parameterService;

        public string Code { // getters and setters that calls private variables }
        public string Address { // getters and setters that calls private variables }
        public ObservableCollection MyCollection { // getters and setters that calls private variables }

        // Event handlers for the XAML UI. It's the first called event, trigered once
        // the UI is loaded
        public ICommand LoadCommand 
        {
            get { return new DelegateCommand((o) => Load(), (o) => true); }
        }
        public ICommand SaveCommand 
        {
            get { return new DelegateCommand((o) => Save(), (o) => true); }
        }

        // Delegates for the event handlers
        private void Load() {
            _parameterService = ServiceLocator.GetService("ParameterService");

            Code = _parameterService.GetValue("Code");
            Address = _parameterService.GetValue("Address");
            MyCollection = _parameterService.GetValue>("Collection");
        }
    }
    

In this case we have a simple class that has some properties bounded with the XAML (Code, Address and MyCollection), some ICommand elements that gets fired when the user make some action on the UI (like, for example, pressing the “Save” button) and their corresponding delegate method that executes the action.

Also, we have a _parameterService object that’s injected with the ServiceLocator in the Load() method. The _parameterService object then set the values for some properties in the ViewModel. Consider the _parameterService object as a simple hash or Dictionary.

We can create the same object with knockout:

    
define([
    'knockout',
    'text!../Views/MyViewModel.html', 
    'parameterService',
    'knockout-es5',
    ], function(
        ko, 
        templateMarkup, 
        _parameterService) {

    function MyViewModel(params)  {
        this.Code = null;
        this.Address = null;
        this.MyCollection = [];

        this.Load = function() {
            Code = _parameterService.GetValue("Code");
            Address = _parameterService.GetValue("Address");
            MyCollection = _parameterService.GetValue("Collection");
        }.bind(this);

        ko.track(this);

        // Here you execute the Load method. It's the same implementation than with XAML in which
        // you execute the element once the UI is loaded.
        this.Load();
    }

    return { viewModel: MyViewModel, template: templateMarkup };

});     
    

The migration from C# to Javascript is pretty simple. In fact, most of the object is migrated, and all of the logic is practically the same. How we can achieve this?

Knockout observables

First of all, knockout has an excellent compatibility performance with most of the browsers. Knockout starts being compatible with IE6+, which is great. To achieve this, knockout use the ko.observable() and ko.observableArray() functions which handle the binding between elements. That’s because IE6-IE8 does not handle Ecmascript 5. They implement Ecmascript 3 as far as I know; that means that IE6-IE8 does not know how to handle getters and setters in javascript.

The big problem with ko.observable() is that you need to declare all your elements as observables, and be very careful in the UI with things like this:

	<!-- ko if: MyProperty().MyChildElements().length -->
	... do something
	<!-- /ko -->

In this case, because MyProperty is an observable and MyChildElements is an observableArray, you need to call your properties as functions. The problem is… if you don’t remember if your property is an observable or a simple element, you will have a lot of errors with “MyProperty() is not a function”. It’s very unpleasant.

My first step in the migration was to create the elements as observables or observableArrays. But, once I started to send messages between elements (and specially to migrate elements from/to the server) I started to had a lot of troubles with observables and simple properties. Practically I have to know which element is what… it was a nightmare, in fact I was willing to drop out knockout and start with another framework.

The big news is that knockout has a plugin that implements Ecmascript-5. With this, you just define your properties as simple elements, and knockout handles them as observables/observableArrays. It’s a pleasant to use the library. Unfortunately you lost compatibility with IE6-IE8, but you improve a lot your codes and the development time.

The library is very easy to use. You just need to execute ko.track(this) once the object is created and then you have a simple ViewModel with all the functionality of the knockout library:

    
        /* 
            ko.track is part of the knockout-es5 library. It takes the 
            properties of the object and assign their corresponding
            observable/observableArray
        */
        ko.track(this); 
    }
});     
    

Please check this link to read about the plugin.

One of the problems with the library is that it does not work with AMD (Asynchronous module definition). So, I saw a simple patch from a fork and applied it on my own fork*. You can see the fork with the patch [here](https://github.com/jparaya/knockout-es5). If you add it to your project you can have Ecmascript-5 modules with AMD (require.js).

* The reason to use my own fork is to keep control of the changes in my project, with the bower package manager.

Services

In the C# code we have an interface that’s injected with ServiceLocator. It’s a very basic injection.

In Javascript we define our module, with their dependencies:

    
define([
    ...,
    'parameterService', --> here is my dependency for the service
    ], function(
        ...
        _parameterService) { --> here is the variable in which I can use the service

    function MyViewModel(params)  {
        ...

        this.Load = function() {
            Code = _parameterService.GetValue("Code"); --> here I get values from the service
        }.bind(this);
    }
});     
    

It’s very easy to define that dependency. As I wrote before, the _parameterService is nothing but a simple Dictionary:

    
define([], function () {
    function ParameterService() {
        var _hash = {};

        this.GetValue = function(key, defaultvAlue)  {
            defaultvAlue = typeof defaultvAlue !== 'undefined' ? defaultvAlue : "";

            if(_hash[key])
                return _hash[key];
            else 
                _hash[key] = defaultvAlue;
        }.bind(this);

        this.SetValue = function(key, value) {
            _hash[key] = value;
        }.bind(this);
    }

    return new ParameterService(); // Here is the magic!
}); 

The ParameterService module has a simple private _hash object with their public functions GetValue(key, defaultValue) and SetValue(key, value). With this, is very easy to use a hash to transfer objects between ViewModels through the application (the only difference with the C# implementation is that Javascript it’s not a strongly typed language; instead of an explicit cast, you just use JSON objects).

The trick is to return an unique object:

return new ParameterService();

If you return that way you can have a singleton element through the entire lifecycle of the application. Otherwise, if you return this way:

return ParameterService;

Every time you need the module, require.js will create a new object.

So, we have a simple implementation of services. The ServiceLocator is not needed here because we use require.js to get the object required for the implementation (require.js in fact act as a ServiceLocator) . Because we returned a new ParameterService() the object is a singleton and we can use it to send messages through different ViewModels.

Services with delegate functions

In the last case, the ParameterService is a simple dictionary. You can have different services anyway, it depends on your necessities. Suppose you need to call a WCF service and execute something with the return, like in this case (C#):


_companyService.GetContext += new EventHandler>(OnCompanyService_GetContextCompleted);
_companyService.GetContextAsync(parameter);

void OnCompanyService_GetContextCompleted(object sender, ClientProxy.ServiceEventArgs e)
{
    // ContextData is a DataContract object (it's the DTO between Silverlight and WCF).
    // This method is executed once the call to GetContext finish
    if (!e.HasError) {
        ... do something
    }
}

Supose you’re calling a WCF Service called CompanyService that has a method, GetContext. Silverlight calls the WCF service in an async call, and execute OnCompanyService_GetContextCompleted once the service finish. Our implementation with Javascript could be:


// ViewModel code
_companyService.GetContext(parameter, function(e) { 
    // We are here once the service finish and returns the result
    if(!e.HasError) {
        ... do something
    }
});

// CompanyService code
this.GetContext = function(myParameter, fn) {
    $.ajax(
    {
      type: 'POST',
      url: 'CompanyService.aspx/GetContext',
      data: JSON.stringify({ parameter: myParameter }),
      contentType: 'application/json; charset=utf-8',
      dataType: 'json',
      varProcessData: true,
      success: function(data) { fn(data) },
      error: function(data) {
        var error = (data.responseJSON ? data.responseJSON.Message : data.responseText);
        var e = {
            HasError: true,
            Error : { ErrorMessage: error }
        }
        fn(e);
      },
    });
};

We have practically the same implementation:

  • C# calls WCF and execute a method once the service finish.

  • Javascript calls a service that makes an Ajax call. The service receives fn as a parameter and execute fn(data). That means, executes fn and send the result. Then we have an inline function that process the data.

It’s not mandatory to have an inline function. We can call functions from the ViewModel, in the same way as the C# implementation.

Migrating partial Silverlight

My first idea on the migration plan was a complete rewrite of the application in a new project. Once created the migration, the idea was to switch off Silverlight and connect the new HTML interface. However, it’s very possible that while we are working on the migration, the application can have their own changes (in the Silverlight world), losing integrity in the migration if it’s not treated carefully. More important, it’s necessary to receive feedback as early as possible, so the switch-once-finished is inefficient; we need to split the task in little pieces in order to migrate early and put the change in production so we can start to correct the codes, if there is any failure.

The solution in our case was to migrate certain parts of the application, and once finished we can add a Web page in front of a section of the Silverlight, simulating that the content is inside the Silverlight application. Let’s see how.

Consider this application. It’s one XAML that loads a menu bar and has a container for other XAML files. Depending on the user selection you load in the container the desired View:

XAML

We cannot add an HTML section in the Menu, because all the UI is Silverlight. However, we can add a div element that can be over the container. In our case, when the user clicks on Configuration we simply tell the Web page to show the HTML element that is in a fixed position, in front of the container:

XAML

In order to make this, we just have to add an iframe in the Web page that loads the Silverlight application, add a few Javascript functions that display/hide the element and configure the application to have a transparent background and to accept the rendering from the browser:

<!-- Here we put the new HTML UI --> 
<iframe id="htmlMigration"></iframe>
<!-- 
CSS, in the Head or the CSS file. You need to adjust the position
based on your application's needs.
-->
#htmlMigration
{
  display: none;
  position: absolute;
  left: 0;
  top: 60px;
  width: 100%;
  height: 100%;
  border: 0px;
}

<script type="text/javascript">
	function activateMigration() {
	  document.getElementById("htmlMigration").style.display = "inline";
	}

	function deactivateMigration() {
	  document.getElementById("htmlMigration").style.display = "none";
	}

	function defineURLMigration(url) {
	  document.getElementById("htmlMigration").src = url;
	}
</script>
<object id="silverlightControl" data="data:application/x-silverlight-2," type="application/x-silverlight-2"
      width="100%" height="100%">
      ...
	<param name="background" value="transparent" /> <!-- Here we allow the HTML to be seen -->
	<param name="windowless" value="true" /> <!-- Here we allow the browser to render, instead of Silverlight -->
      ...
</object>

Now the trick is to render the HTML when the user clicks on Configuration. How can we do this? Easy, we can use the HtmlPage object that can call Javascript functions in the Class constructor, which is the moment when Silverlight creates the object to render the view, once the user clicked on the Configuration tab:

    
    // Configuration.xaml.cs
    public Configuration()
    {
      InitializeComponent();
      // Here we put the new UI
      System.Windows.Browser.HtmlPage.Window.Invoke("activateMigration", null);
      System.Windows.Browser.HtmlPage.Window.Invoke("defineURLMigration", new string[] { "Configuration.html" });
    }

    private void LayoutRoot_Unloaded(object sender, System.Windows.RoutedEventArgs e)
    {
      _messengerService.Unregister(this);

      // Here we hide the new UI and clear the iframe
      System.Windows.Browser.HtmlPage.Window.Invoke("deactivateMigration", null);
      System.Windows.Browser.HtmlPage.Window.Invoke("defineURLMigration", new string[] { "" });
    }
    

The last thing to do is to clean the View for the Configuration. If not, you will have a mix of HTML and Silverlight in the same space.

So, in summary:

  • You need to create an iframe in the Web page that loads the Silverlight application.

  • When the user clicks on the migrated section, the application calls the corresponding HTML.

  • You can evolve the application, mixing more HTML content inside the Silverlight. At some point, all the Silverlight application will be migrated. It’s like a StranglerApplication, instead of a full migration.

Well, I hope you can find useful information here. The migration is new to me, especially if we have different technologies and platforms. I hope to write a new article with the advance, and some tips for replacement of Silverlight UI elements.

Greetings!