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!
Hope that helps. You can get the source code for this sample here.
Comments
Anonymous
March 10, 2009
PingBack from http://aspdotnetmvc.com/blogs/default.aspxAnonymous
July 05, 2009
Hi ! Much thanks first for this sample! The idea is very good [and I am interested to parameters to the SL control for other reasons also]. But sorry, there is something wrong? I expected the app would show the results on the searchpage, bit it comes up empty? What's wrong and what can I do [I am new to SL] ? Thanks a lot! br--mabraAnonymous
July 11, 2009
Hi again ! Ok, after the relaese of SL 3 I updated the project and now, it works fine! Thanks again for the sample! br--mabraAnonymous
July 12, 2009
Hi mabra, I'm glad you got it working. I'm not sure why it wasn't working for you before, perhaps it was a change between ASP.NET MVC releases. Anyway, let me know how you get on passing args into SL via MVC - i'd be interested in how people use it.Anonymous
January 26, 2010
Can it work under your local IIS? As for my similar attempt, a Silverlight contained in a MVC view page does not run nor appear under my local IIS whose version is 7.0 running on W2K8, though it DOES under ASP.NET development server and siliverlight web applications themselves can run under both servers. ymAnonymous
September 29, 2010
Very impressive! An improvement over Tim's work indeed! Looks like this might make the purists happy - now that SIlverlight object is a pure MVC view, just consuming what is passed as view model data. Great work!Anonymous
October 31, 2010
Fantastic! This is exactly what I was looking for. How can we extend this concept to multiple views? Are we limited to one view per SL app or can we embed multiple SL pages into the same XAP and reference the correct views inside the XAP? In fact I am more concerned about whether we need a SL project per view or whether we can have multiple SL views in the same project. JMFAnonymous
November 18, 2010
This is exactly what I have been looking for, with the MS TAG you get better clarity and don’t need to worry much about cleaning your lens all the time u need to shoot. I will come back to read more updates on this. <a href="http://dogtagsfordogs.org">engraved dog tags</a>