ASP.NET MVC + Silverlight

I’ve recently been playing around with ASP.NET’s new MVC (Model View Controller) framework and have to say, its pretty cool. You can find MVC downloads, tutorials and other errata here.

However, I ran into abit of a problem with MVC when using Silverlight applications inside MVC Views. Views should simply display the Model data passed to it by the Controller using (as illustrated in diagram 1).

 <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= Html.Encode(ViewData["Message"]) %></h2>
    <p>
        To learn more about ASP.NET MVC visit <a href="https://asp.net/mvc" title="ASP.NET MVC Website">https://asp.net/mvc</a>.
    </p>
</asp:Content>

 

If you want your View to contain a Silverlight app that contains no Model data, then there’s no problem. Simply embed the Silverlight into the ASPX View using either the raw HTML or the ASP.NET Silverlight control and it works.

However, the problem comes when you want the Silverlight app to display/manipulate/use the Model data passed to the View by the Controller. You can of course get the Model data into Silverlight using web service calls or by making HTTP requests from Silverlight to your Controller (as illustrated by Tim Heuer in this post), but this violates the MVC pattern. Ideally your Silverlight app should work in a similar way to the MVC Views, simply displaying the data that is passed to it.

 

So, how do you initialise Silverlight with the data it needs? Well, I found that I could pass all the data my Silverlight app needed to display using the ‘initParams’! – sneaky huh.

Basically, these are the steps involved:

Controller
  • Create your custom Action inside your Controller (in this example, the controller is ‘Home’ and the Action is ‘Search')
  • The Controller invokes the Search function in the appropriate Model
  • The Model returns the search results to the Controller
  • The Controller returns the ActionResult containing the Search Results as the View’s Model data
 public ActionResult Search()
{
    // Call Model to do Search

    // Set results as View's Model data (we're using dummy data)
    ViewData.Model = new SearchResult[] 
    {
        new SearchResult(){Title="Search Result 1", Relevance=100}, 
        new SearchResult(){Title="Search Result 2", Relevance=75}, 
        new SearchResult(){Title="Search Result 3", Relevance=100} 
    }.ToList();

    // Return View ActionResult containing our Model Data
    return View();
}

  
View
  • Next, create your View ASPX page. I’ve made my View strongly typed, so I can reference the collection of type SearchResult (Model data) directly instead of having to cast it
  • On your View page, embed the standard Silverlight HTML markup
  • Inside the containing div I have some inline C# which copies the Model data into a new collection of anonymous type (you could just use the SearchResult collection passed in, but if I was only interested in a subset of the properties in SearchResult, or wanted to add my own or change their names etc, then creating a collection of a new anonymous type is very handy. Just make sure the type you will deserialize the JSON to inside your Silverlight app contains the exact same properties as your new anonymous type!)
  • The new collection is then serialized to JSON using the System.Web.Script.Serialization.JavaScriptSerializer and stored in a StringBuilder
  • The JSON is then URL encoded and concatenated into a string which matches Silverlight’s InitParams definition (e.g. key=value)
  • Finally, some more inline code to output the necessary ‘initParams’ param markup, passing our custom initParams string as the value
 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MvcSilverlight.SearchResult>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
    <title>Silverlight</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Silverlight Results Page</h2>
    
    
    <div id="silverlightControlHost">
    
        <%             
            System.Text.StringBuilder jsonText = new System.Text.StringBuilder();

            var results = ViewData.Model.Select(sr => new {Title=sr.Title, Relevance = sr.Relevance });            
                
            System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();

            serializer.Serialize(results, jsonText);

            string initParams = string.Format("{0}={1}","model", HttpUtility.UrlEncode(jsonText.ToString()));

        %>
        
        <object data="data:application/x-silverlight-2" type="application/x-silverlight-2" width="300px" height="300px">
            <param name="source" value="/ClientBin/SilverlightApplication1.xap" />
            <param name="minRuntimeVersion" value="2.0.31005.0" />
            <% Response.Write(string.Format("<param name=\"initParams\" value=\"{0}\"/>", initParams)); %>
            <param name="windowless" value="true" />
            <param name="Background" value="#00FFFFFF" />
        </object>
    </div>
</asp:Content>

  
Silverlight
  • Modify your App.xaml.cs file in your Silverlight project
  • In the Application_Startup event handler store the InitParams to a static IDictionary<string,string> for reference later
 public static IDictionary<string, string> initParams = new Dictionary<string, string>();

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new Page();

    App.initParams = e.InitParams;
}

 

  • In your Silverlight application’s Page_Loaded event handler in Page.xaml.cs (or wherever you want really), retrieve the Model data from the IDictionary, using the key (“model”)
  • Once you have your Model data as a string, Url decode it, then deserialize using the DataContractJsonSerializer.
  • If you are passing complex types to Silverlight as we are in this example, you must have a class defined in your Silverlight app to cast the Model data to. This class must be attributed with the DataContract attribute, and each of its publicly accessible properties must attributed with the DataMember attribute
  • Finally, cast the deserialized object to your replica type / collection of replica type, and you’re done!
 void Page_Loaded(object sender, RoutedEventArgs e)
{
    // Deserialize Init Params
    List<SearchResult> results = null;

    using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(HttpUtility.UrlDecode(App.initParams["model"].ToString()))))
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<SearchResult>));

        results = (List<SearchResult>)serializer.ReadObject(ms);
    }

    // Set deserialized search results as DataGrid ItemsSource
    this.ResultsGrid.ItemsSource = results.ToList();            
}

 

 

  • Tada! The SearchResult collection is passed from the Model through the Controller, to the View and finally into Silverlight. Without breaking any MVC rules!

2

 

Hope that helps. You can get the source code for this sample here.