Silverlight Frankenstein

Frank2 For the last 6 months I have been working on a massive call center application that uses JavaScript heavily to provide a rich client experience. While working on this application a feature request was made that threw me for a loop. The requirement was that should the users browser die or lock up their session state would need to be recoverable, meaning when the browser was restarted  we would need to recover state. With a little head scratching and soul searching I came up with this idea:

“Why not user Silverlight, Jquery, JSON and .NET Isolated Storage to persist client information on the browser. It’s Alive!!!!”

I then went to work to create this monster.

Constructing a Monster

1. Create a new Silverlight Application with the test site included.  Initially this application will have a visual representation. You will be changing this application to a  “headless” version later.

2. Add a new class to your application. Lets  call that class Frank.

3. Add a reference (and appropriate using statement) to System.Windows.Browser to your Silverlight Application. System.Windows.Browser is the assembly used to integrate Silverlight with its host application.

4. Now add a function called wakeUp to your Frank class. Decorate wakeUp with the [ScriptableMember] attribute.

[ScriptableMember]
public string wakeUp(string input)
{
return input + System.Guid.NewGuid().ToString();
}

5. The next step is to expose your Frank class to your browser by registering it it with the Silverlight plugin. Registering classes is done in the Page.xaml.cs file.

6. Open the Page.xaml.cs file and add a reference (and appropriate using statement) to the System.Windows.Browser assembly.

7. Modify the constructor of the Page class adding the following code:

public Page()
{
InitializeComponent();
HtmlPage.RegisterScriptableObject("frank",new Frank());
}

 8. Now your Silverlight Frankenstein is exposed to your browser’s scripting code. Now lets go to the browser and wake up the monster.

Frank1

Waking Up the Monster

 

 

Our preferred method of working with the DOM and JavaScript is to use jQuery. jQuery found at www.jquery.com is a very useful library that takes the pain away from working with client side applications.

Along with jQuery we use the JSON library to serialize and de-serialize JavaScript objects. the JSON library can be found at http://www.JSON.org/json2.js

Now lets go to work and call our monster from our JavaScript application.

Important Step:

Before you begin this exercise modify the <object> tag in the HTML page created when you started the silverlight application. You need to add an ID attribute to that tag. Your Silverlight tag should look like:

<object id="frank" data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/SilverlightFrankenstein.xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="background" value="blue" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
</a>
</object>

 Now you can proceed to add the code to talk to your Silverlight application.

1. Add the latest and greatest version of jQuery to your web application.

2. Add the latest and greatest version of JSON to your web application.

3. Add a new Jscript file to your application. call it Frank.main.js. This file will contain your custom JavaScript code.

4. In the .html page generated for your Silverlight application add script references to jQuery, JSON and your Frank.main.js files.

5. Add a script tag to your html page to launch your jQuery application. Your code should look like the following snippet:

<script src="Scripts/jquery-1.2.6.js" type="text/javascript"></script>
<script src="Scripts/json2.js" type="text/javascript"></script>
<script src="Scripts/Frank.Main.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(main);
</script>

5. Add a function called main to the Frank.main.js file.

6. Now you can add code to call your Frank class. The first step is to create a function that will return a reference to your referenced frank class. The following JavaScript function does just that:

function getFrank() {
var returnValue = document.getElementById("frank").content.frank;
return returnValue;
}

7. You need to carefully examine that function. It does the following:

  a) Grabs a handle to the Silverlight control via document.getElementByID(“frank”)

  b) Bores into the Silverlight control’s content property and grabs a reference to  

     the frank variable you referenced in the first exercise.

8. Now you can call your wakeUp() method as follows:

alert(getFrank().wakeUp("hello"));

 

Now For Something Useful

Now that you have a basic understanding of how to call C# code from your web pages you can open up your horizons and do something useful.

 s_spider

Using the Visual Studio Debugger

One of the more interesting things I came up with was integrating the Visual Studio debugger into my JavaScript applications. Rather than debugging variables via the good old alert() function why not spit information to the debugger?

 

The following code blocks show how you can spit data from your JavaScript application into the Visual Studio debugger.

[ScriptableMember]
public void writeline(string output)
{
System.Diagnostics.Debug.WriteLine(output);
}

 

function debuggerTest() {
for (i=0; i<100; i++) {
writeline(i);
}
}

 Frank3

Calculating Ages

Lets face it JavaScript date support,especially when it comes to date calculations sucks.Date support in the .NET framework is much better. Especially when you use the “wrapped up” code found in the Microsoft.VisualBasic namespace.

The following code calculates a person’s age (in years) using the DateDiff() function found in the Microsoft.VisualBasic assembly. To use this code add a reference to the Microsoft.VisualBasic assembly to your Silverlight Application.

The following code snippets show how to calculate an age from a given date:

[ScriptableMember]
public long getAge(string inputDate)
{
long returnValue = -1;
try
{
var dateValue = System.Convert.ToDateTime(inputDate);
returnValue = Microsoft.VisualBasic.DateAndTime.DateDiff(Microsoft.VisualBasic.DateInterval.Year,
dateValue,
System.DateTime.Now,
Microsoft.VisualBasic.FirstDayOfWeek.System,
Microsoft.VisualBasic.FirstWeekOfYear.System);
}
catch (Exception)
{
returnValue = -2;
}
return returnValue;

}

 

alert(getFrank().getAge("01/01/1981"));c

 

51XJ2eVrTaL__SL500

Final Frankenstein

The original purpose of this exercise is to be able to save and restore form state from a client browser session. This is where a good plan comes together. Saving state involves a number of moving parts.

0. Create a simple HTML form with some fields.

1. Create an array of the objects representing the data from a set of web input fields.

2. Serialize the array into a string using JSON (JavaScript Object Notation)

3. Pass that string to Silverlight and save it to disk using Silverlight’s Isolated storage mechanism.

4. Retrieve the JSON data from Isolated Storage and de-serialize it back to an array.

5. Re-populate the controls on the form from the array.

 

Simple HTML

This code is about the simplest HTML form you could create. Two text fields and two buttons. The two buttons will be used to save and restore state (we save and restore state via a timer).

<label>Last Name</label><input type="text" id="txtLastname"/><br />
<label>First Name</label><input type="text" id="txtFirstName"/><br />

<input id="cmdSaveState" type="button" value="Save State" />
<input id="cmdRestoreState" type="button" value="Restore State" />

The following code wires up the buttons to functions to save and restore state:

$("#cmdRestoreState").click(restoreState);
$("#cmdSaveState").click(saveState);

Gathering and Restoring Data

The next set of code creates an array of objects representing page content. It also serializes the content to a JSON string using the JSON library added to your project earlier.

function getContentAsJSON() {
var inputControls = $(":input").not(":button").not(".ignorestate");
var controlData = Array();
for (i = 0; i < inputControls.length; i++) {
controlData.push({ ID: inputControls[i].id, Value: inputControls[i].value, Type: inputControls[i].type });
}
var jsondata = JSON.stringify(controlData);
return jsondata;
}

 Note: this code gathers up data for all controls except for buttons and ones that have the .ignorestate style attached to them.

The next set of code is the converse of the prior. It takes a JSON string and de-serializes it back to an array and repopulates control values based on its content.

function restoreControlState(controlDataString) {
if (controlDataString.length > 0) {
var controlData = JSON.parse(controlDataString);
for (i = 0; i < controlData.length; i++) {
$("#" + controlData[i].ID).val(controlData[i].Value);
}
}
}

 

Saving and Restoring State

Now you need a mechanism for storing and retrieving the data to client workstation. To do this, you will use the Isolated Storage mechanisms contained in the Silverlight framework. Isolated storage is a data storage mechanism available in both standard .NET and Silverlight applications. Isolated storage is a set of APIs used to read and write files to a virtual file system managed by the .NET framework. Silverlight applications have a default 1MB (yes 1MB) of storage available to them for reading and writing files.  To use the following code examples import System.IO and System.IO.IsolatedStorage to your applications:

 The first is used to preserve state. It  does the following:

1.  Opens the “virtual file system” presented by IsolatedStorage

2. Checks to see if the “sessioncontents.txt”  file exists  . If so it deletes it.

3. Creates a file stream to write content to.

4. Uses a standard StreamWriter to push content to the file.

5. Flushes the buffer and closes the file handle

[ScriptableMember]
public void saveState(string stateString)
{
//yes this is a MAGIC string for a demo
var contentFileName = "sessionContents.txt";
var userStore = IsolatedStorageFile.GetUserStoreForApplication();

//whack the file if it exists
if (userStore.FileExists(contentFileName))
{
userStore.DeleteFile(contentFileName);
}
var file = userStore.CreateFile(contentFileName);
var writer = new StreamWriter(file);
writer.WriteLine(stateString);
writer.Flush();
writer.Close();
file.Close();
}

The next step is to return the data from IsolatedStorage. It does the converse:

1.  Opens the “virtual file system” presented by IsolatedStorage

2. Checks to see if the “sessioncontents.txt”  file exists  . If so it opens it.

3. Creates a file stream to read content from.

4. Uses a standard StreamReader to read content from the file.

5. Closes the file handle

6 Returns the string to the caller.

[ScriptableMember]
public string getState()
{
var returnValue = "";
//yes this is a MAGIC string for a demo
var contentFileName = "sessionContents.txt";
var userStore = IsolatedStorageFile.GetUserStoreForApplication();

//whack the file if it exists
if (userStore.FileExists(contentFileName))
{
var file = userStore.OpenFile(contentFileName,FileMode.Open,FileAccess.Read );
var reader = new StreamReader(file);
returnValue = reader.ReadToEnd();
reader.Close();
}

return returnValue;
}

 

Now all you need is the small glue code to put it all together:

function saveState() {
getFrank().saveState(getContentAsJSON());
}

function restoreState() {
restoreControlState(getFrank().getState());
}

 

That’s all you need to save and restore state from a HTML form

 

Removing the Head

FRANKENSTEIN_JR_HEADSHOT

 Finally you can go “remove the head” from your Silverlight application. this is rather simple. To remove the head from your Silverlight application you simply go to the Page.xaml file and change the size attributes to zero. Now you have a “headless” frankenstein for your fun and enjoyment.

<UserControl x:Class="SilverlightFrankenstein.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="0" Height="0">
<Grid x:Name="LayoutRoot" Background="White">

</Grid>
</UserControl>

 

 I hope you have enjoyed this post. You can find this code at www.dashpoint.com/downloads/silverlightfrankenstein.zip

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

9 Responses to Silverlight Frankenstein

  1. Thanks for sharing this post. This maybe used by the philippine call center in the future.

  2. Ronald says:

    Dumas: I did not look at Google Gears. I don’t know if this is good option as users need to install Gears, but I will definitely have a look at it. Thanks for the suggestion.

    Hope Silverlight will support client side database in the future! Would make everything a bit more robust.

  3. Dave T. says:

    I use flash to do something similar. Unlike silverlight everyone seems to have it an I rarely run across the case where they have shut local storage off.

  4. Ronald: Cool app.

    Dumas: I need to look at that library.

  5. Art says:

    Interesting concept. Just wondering if you looked at Google Gears (JavaScript) to save/restore browser application state.

  6. Ronald says:

    For another possible use of isolated storage check this webapp http://www.ToolToMeet.com.

  7. Frank N. Stein says:

    I’m getting really sick of you guys tarnishing my name.

    Oh, nice article…

  8. Gary Sherman says:

    Cool stuff Rod!
    I definitely have many ideas on how this could be useful in our web apps.

  9. Nordes says:

    Brilliant article. I will maybe use that in the future.

Leave a Reply