Поделиться через


Preloading Content for Your Silverlight Applications and Web Parts

I’ve been studying up on Sharepoint 2010 recently and one of the great things about it is the extensive use of AJAX and Silverlight in the new version. Combine that with the huge improvements in the development environment on Sharepoint 2010 (F5 development, finally!) and you’ve got the potential for some very nice web 2.0ish portals. This is great news for everyone who works on Sharepoint as well as people who work on Silverlight.

With this in mind I started out writing a few Silverlight web parts to familiarize myself with the new Sharepoint client object model. Everything worked great and pretty soon I had a Sharepoint page built entirely of Silverlight applications (hosted in Sharepoint 2010’s built in Silverlight web part) that could talk to each other. Great! Except when the page downloaded all I saw was a bunch of “Loading” messages. Not so great. So what’s so wrong with having a bunch of silverlight apps that pull data down from the server you say? Well nothing, except when people visit my portal I don’t want them wondering whether it’s Silverlight that’s slowing things down for them.

So here’s what we do. We make sure that when the page comes down, it comes down with enough data to let the Silverlight applications display whatever it is they want to display. But how do we get this data into the page? And how do we then pick it up from there and get it into the Silverlight applications? One way would be to use initparams attribute on the Silverlight’s object tag. It works, but it’s messy (all that data in one attribute) and we end up with a long string that we’ll have to interpret on the other (Silverlight) end.

Instead, we’ll use the very useful Silverlight HTML bridge features. First, we write out the data to the page. Here’s a little JsonSerializationHelper class I wrote:

    1: public class JsonSerializationHelper
    2:     {
    3:         public static void SerializeToPage(Page page, List<MenuItem> items)
    4:         {
    5:             DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<MenuItem>));
    6:             string json = null;
    7:  
    8:             using (MemoryStream stream = new MemoryStream())
    9:             {
   10:                 serializer.WriteObject(stream, items);
   11:                 json = Encoding.Default.GetString(stream.ToArray());
   12:             }
   13:  
   14:             if (page.ClientScript.IsClientScriptBlockRegistered(page.GetType(), "GetMenuItems") == false)
   15:             {
   16:                 StringBuilder sb = new StringBuilder();
   17:                 sb.Append("function GetMenuItems()");
   18:                 sb.Append(Environment.NewLine);
   19:                 sb.Append("{");
   20:                 sb.Append(Environment.NewLine);
   21:                 sb.Append("return ");
   22:                 sb.Append(json);
   23:                 sb.Append(";");
   24:                 sb.Append(Environment.NewLine);
   25:                 sb.Append("}");
   26:  
   27:                 page.ClientScript.RegisterClientScriptBlock(page.GetType(), "GetMenuItems", sb.ToString(), true);
   28:             }
   29:         }
   30:     }

What the JsonSerializationHelper does is take a collection of objects of type MenuItem and spit them out to the ASPX page as a Javascript function. The function in turn returns the collection as a JSON array. The MenuItem class has a couple of members, DisplayText (string) and a Value (string). This of course is a very simple data structure you’ll rarely encounter or use in real life. However, JSON has a very rich syntax that can be used to express complex hierarchical data structures very succinctly. Why use JSON instead of XML or something else? Well there’s nothing stopping us using either, but we do want to keep the size of the page down. Adding a whole bunch of extra angle brackets isn’t going to help. If you want to, go ahead and use the XmlSerializer here.

A quick look at the MenuItem class:

    1:  public class MenuItem
    2:      {
    3:          public string DisplayText { get; set; }
    4:          public string Value { get; set; }
    5:      }

Simple right? Now we go ahead and add some code into the Page_Load method of our ASPX page (the one that hosts the Silverlight object tag) to actually do the serialization. Here’s what our Page_Load method looks like now:

    1:  protected void Page_Load(object sender, EventArgs e)
    2:          {
    3:              var items = GetDummyList();
    4:              JsonSerializationHelper.SerializeToPage(Page, items);
    5:          }

The GetDummyList method just goes ahead and generates a dummy list (surprise surprise!) of menu items that we can feed to our serialization helper. We also pass in the current Page object so that the helper knows where to serialize the Javascript.

So what we’ve got now is an ASPX page with a Javascript function called GetMenuItems. The function returns a JSON array of MenuItem objects that contain the data that we need to display in our Silverlight application. Almost there. For your reference, here’s what the generated Javascript method looks like:

    1:  function GetMenuItems()
    2:  {
    3:  return [
    4:  {"DisplayText":"Menu Item A","Value":"Menu Item A Value"},
    5:  {"DisplayText":"Menu Item B","Value":"Menu Item B Value"},
    6:  {"DisplayText":"Menu Item C","Value":"Menu Item C Value"},
    7:  {"DisplayText":"Menu Item D","Value":"Menu Item D Value"},
    8:  {"DisplayText":"Menu Item E","Value":"Menu Item E Value"}];
    9:  }

The next step is working out a way to pick up data from the GetMenuItems function and deserialize it. Time for another JsonSerializationHelper! Except this one lives in the Silverlight project. Here’s what it looks like:

    1: public class JsonSerializationHelper
    2:     {
    3:         const string JavascriptMethod = "GetMenuItems";
    4:  
    5:         /// <summary>
    6:         /// Deserialize menu items to be shown in the wheel from the current HTML page
    7:         /// </summary>
    8:         /// <returns>A list of wheel menu items</returns>
    9:         public static ObservableCollection<MenuItem> DeserializeFromPage()
   10:         {
   11:             object json = HtmlPage.Window.Invoke(JavascriptMethod, null);
   12:             var list = ((ScriptObject)json).ConvertTo<ObservableCollection<MenuItem>>();
   13:             return list;
   14:         }
   15:     }

Perhaps we should have called it the JsonDeserializationHelper :)

Anywho, the class has just one method called DeserializeFromPage. This method uses the Silverlight HtmlPage class to invoke the GetMenuItems method. Of course, we could make the method even more generic by passing in the name of the function to call but lets leave that for another day. We cast the object returned by the Invoke method to a ScriptObject (because we know that’s what it is) and call the very useful ConvertTo method of the ScriptObject class.

At first, what ConvertTo does might seem like magic, but all it’s really doing is trying to interpret the ScriptObject based on the template parameters we’ve passed in. In this case, we’re telling ConvertTo that we think the JSON object is a collection (hence the ObservableCollection) of MenuItem objects. The ConvertTo method figures out the mapping by looking at the JSON above and gives us a collection of MenuItem objects.

Now you might be wondering how the MenuItem class ended up in the Silverlight project. It is true that we can’t share assemblies between the regular CLR and Silverlight but there’s nothing stopping us sharing the source files. That’s exactly what I did, I right-clicked the Silverlight project and did an Add…Existing Item. Then I went and found the MenuItem class in the web project and just added it as a “link”. That means that while both projects reference it, there is really just one source file. And since the MenuItem class doesn’t use anything that isn’t in the Silverlight class library everything builds just fine. Neat, isn’t it?

image

 

Okay, now we get to the final step, displaying the data we got. For the purpose of this sample, I just put an ItemsControl in my MainPage.xaml. Here’s what it looks like:

    1: <ItemsControl x:Name="lstItems" Background="AntiqueWhite" Width="350">
    2:             <ItemsControl.ItemTemplate>
    3:                 <DataTemplate>
    4:                     <Border BorderThickness="2" BorderBrush="Gray" Margin="5">
    5:                         <Grid Margin="5">
    6:                             <Grid.ColumnDefinitions>
    7:                                 <ColumnDefinition Width="3*" />
    8:                                 <ColumnDefinition Width="2*" />
    9:                             </Grid.ColumnDefinitions>
   10:                             <TextBlock Text="{Binding DisplayText}" FontWeight="Bold" Foreground="Gray" FontSize="25" Grid.Column="0" />
   11:                             <TextBlock Text="{Binding Value}" Grid.Column="1" VerticalAlignment="Bottom" />
   12:                         </Grid>
   13:                     </Border>
   14:                 </DataTemplate>
   15:             </ItemsControl.ItemTemplate>
   16:         </ItemsControl>

It simply displays the DisplayText and Value properties in a grid. Next, I added a Loaded event handler for MainPage and put the code in to load the data:

    1: private void UserControl_Loaded(object sender, RoutedEventArgs e)
    2:         {
    3:             lstItems.ItemsSource = JsonSerializationHelper.DeserializeFromPage();
    4:         }

And that’s it. We’re done! The code for this sample is here:

Go ahead and give it a whirl. Do let me know what you think and if you run into any problems.