code blog foo - tag line bar

Integrating Silverlight into ASP.NET MVC

click here to download source code

This presentation was given at Dallas Dev Cares. Above, is all the source code that was shown at the presentation. This source code was written in ASP.NET MVC 1.0. Instructions follow for upgrading this source code to ASP.NET MVC 2.0.

About the Source

This was written using the MVC 1.0 project template (I will update this to MVC 2.0 shortly). If you have difficulty opening the project in Visual Studio, chances are you have ASP.NET MVC 2.0 installed. Refer to this link for instructions on how to change over the project: Convert an MVC 1.0 Project to MVC 2.0.

Also in MVC 2, returning JSON via an HTTP/GET Controller action will throw an exception if you do:

public ActionResult Search(string searchString)
{
    //populate search results
    List<KeyValuePair<string,string>> searchResults = ...;     

    //this will throw an exception in ASP.NET MVC 2
    return Json(searchResults);  
}

Again, the code above will throw an exception and must be changed to the following if you plan on upgrading the source code for this post to ASP.NET MVC 2:

public ActionResult Search(string searchString)
{
    //populate search results 
    List<KeyValuePair<string,string>> searchResults = ...;     

    //this will fix the exception
    return Json(searchResults, JsonRequestBehavior.AllowGet);
}

Here we go...

When using Silverlight, is it possible to communicate with you server without using WCF? The answer is yes. You can download the source code (links above) for examples on how to do this. If you want a detailed explanation, keep reading.

To understand how we’ll go about communicating with ASP.NET MVC in Silverlight, lets start with how you can communicate with mvc through jQuery.

How to make asynchronous calls using jQuery (HTTP/GET)

First, you need to create a controller method with a NoCache attribute.

//NoCache attribute  code
//the no cache attribute ensures that 
//the browser doesn't cache the HTTP/GET
public class NoCache : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext
                     .Response
                     .Cache
                     .SetCacheability(HttpCacheability.NoCache);
    }
}
 

//Controller Action
AcceptVerbs(HttpVerbs.Get)
ActionName("People")
NoCache 
public JsonResult People()
{
    List<Person> person = new List<Person>();
    person.Add(new Person { FirstName = "John", LastName = "Doe" });
    person.Add(new Person { FirstName = "Jane", LastName = "Doe" });

    //if you are using MVC 1.0 you would do this:
    return Json(person);
   
    //if you are using MVC 2.0 you would do this:
    return Json(searchResults, JsonRequestBehavior.AllowGet);
}

Second, create a view that will call the controller action using jQuery’s $.getJSON method.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head id="Head1" runat="server">
        <title>Index</title>
        <script src="<%= Url.Content("~/scripts/jquery-1.3.2.js") %>" 
                   type="text/javascript"></script>
    </head>
    <body>
        
        <% 
            string getPeopleUrl = 
                Url.RouteUrl(new { controller = "Home", 
                    action = "People" }); 
                    //Create your mvc url for the controller method
        %>
        
        <script type="text/javascript">
            $(InitializeComponents);
            
            function InitializeComponents() {
                //wire up the click for the async button
                $('#buttonGetPeople').click(function() { GetPeople(); }); 
            }

            function GetPeople() {
                //call $.getJSON giving the controller method
                $.getJSON('<%= getPeopleUrl %>', 
                //PopulatePeople will be called with the async call returns
                PopulatePeople); 
            }

            function PopulatePeople(data) { 
                //data is the JSON serialization of List<People>
                for (i = 0; i < data.length; i++) {
                    //enumerate the list and put in alert boxes
                    alert(datai.FirstName + ' ' + datai.LastName); 
                }
            }
        </script>
        
        <input id="buttonGetPeople" type="button" value="get async" />
    </body>
</html>

How to make asynchronous calls using jQuery (HTTP/POST)

First, you need to create a controller method.

AcceptVerbs(HttpVerbs.Post) 
ActionName("People") 
public EmptyResult People(string firstName, string lastName) 
{ 
    //enter "Save" code here... 
    return new EmptyResult(); 
}

Second, create a view that will call the controller action using jQuery’s $.post method.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head id="Head1" runat="server">
        <title>Index</title>
        <script src="<%= Url.Content("~/scripts/jquery-1.3.2.js") %>" 
                   type="text/javascript"></script>
    </head>
    <body>
        <% 
            string savePersonUrl = 
                Url.RouteUrl(new { controller = "Home", 
                    action = "People" }); 
                    //create your mvc url for the controller action
        %>
        
        <script type="text/javascript">
            function InitializeComponents() {
                //wire up the click for the async button
                $('#buttonSavePerson').click(function() { SavePerson(); }); 
            }

            function SavePerson() {
                //pull out the value from textboxes
                var firstNameFromTextBox = $('#textBoxFirstName').val(); 
                var lastNameFromTextBox = $('#textBoxLastName').val();
                
                //call controller action asynchronously
                $.post('<%= savePersonUrl %>',
                        { 
                          firstName: firstNameFromTextBox, 
                          lastName: lastNameFromTextBox 
                        }, 
                        function() { alert('saved'); });
            }

           
            $(InitializeComponents);
        </script>
        
        First Name: <input id="textBoxFirstName" type="text" value="" /><br />
        Last Name: <input id="textBoxLastName" type="text" value="" /><br />
        <input id="buttonSavePerson" type="button" value="save" />
    </body>
</html>

How to communicate with mvc controllers in Silverlight

Some things to keep in mind:

  1. Download the sample code located at the top of this post for detailed example.
  2. Don’t rely on data existing in the session when communicating from Silverlight.
  3. You can’t entirely guarantee that the session will be authenticated, so you cannot use the Authorize attribute on your controller actions that are leveraged by Silverlight (consider passing in an authentication token/guid with every call).
  4. To share strongly typed classes (.cs files) between Silverlight and ASP.NET MVC, use linked files. There is no WSDL since we aren’t using WCF, so linked classes/files is the solution for creating fidelity between your mvc application and your Silverlight application.
  5. This technique for communication is about integrating Silverlight into and ASP.NET MVC website. Silverlight acts as a powerful component of the web solution. I would not recommend this approach to server side communication if your application is 100% Silverlight with no ASP.MVC pages.
  6. Is this best practices? To be honest I can’t say if it is/isn’t. “Best practices” recommends using WCF for Silverlight communication. Don’t get me wrong, WCF is an amazing platform with complex data bindings and amazing support for a Service Oriented Architecture. But, given that the only thing I need is an intermediary between my Silverlight client and the server, WCF becomes an added complexity that I get no benefit from…I am not trying to go for an SOA solution…I just want to be able to communicate with my server side assets. So…just keep it simple and don’t use WCF.

So how do you communicate with your server side assets from Silverlight with out using WCF?

Answer: You have to simulate the HTTP/GET and HTTP/POST using Silverlight’s System.Net.HttpWebRequest. The HTTP calls map to ASP.NET MVC controllers that return JSON serialized object which are reconstituted on the Silverlight Client.


Using the RestFacilitator (custom created class), you can call directly into your ASP.NET MVC controllers (example below):

public void SavePerson(string firstName, string lastName)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add("firstName", firstName);
    parameters.Add("lastName", lastName);

    _restFacilitator.Post("http://localhost:50400/Home/People",
    parameters,
    (e) => _view.NotifySaveSuccessful());
}

public void GetPeople()
{
    _restFacilitator.Get<List<Person>>(
    "http://localhost:50400/Home/People",
    (result) =>
    {
        List<Person> resultAsPerson = result as List<Person>;
        _view.PopulatePeople(resultAsPerson);
    });
}

Here is the code for a RestFacilitator class I’ve created to do both the HTTP/GET and HTTP/POST: (source code download link is at the top of this post)

public class RestFacilitator
{
    private SynchronizationContext _synchronizationContext;
    public RestFacilitator(SynchronizationContext synchronizationContext)
    {
        _synchronizationContext = synchronizationContext;
    }

    public void Post(string resource, Dictionary<string, string> parameters, 
                     SendOrPostCallback onComplete)
    {
        string uriPath = resource;
        Uri uri = new Uri(uriPath, UriKind.Absolute);
        HttpWebRequest httpWebRequest = 
            (HttpWebRequest)WebRequest.Create(uri);
            
        httpWebRequest.Method = "POST";

        AsyncCallback readResponse = null;
        AsyncCallback writeRequest = null;

        writeRequest = new AsyncCallback(
            (e) =>
            {
                httpWebRequest.ContentType = 
                    "application/x-www-form-urlencoded";
                    
                Stream stream = httpWebRequest.EndGetRequestStream(e);
                string postData = string.Empty;

                foreach (string key in parameters.Keys)
                {
                    postData += key + "=" + parameterskey + "&";
                }

                StreamWriter streamWriter = new StreamWriter(stream);
                streamWriter.Write(postData);
                streamWriter.Close();
                stream.Close();
                httpWebRequest.BeginGetResponse(readResponse, null);
            });

        readResponse = new AsyncCallback(
            (e) =>
            {
                _synchronizationContext.Post(onComplete, null);
            });

        //go statement
        httpWebRequest.BeginGetRequestStream(writeRequest, null);
    }

    public void Get<T>(string resource, SendOrPostCallback onComplete) 
        where T : class, new()
    {
        string uriPath = resource;
        Uri uri = new Uri(uriPath, UriKind.Absolute);
        HttpWebRequest httpWebRequest = 
            (HttpWebRequest)WebRequest.Create(uri);

        AsyncCallback done =
            new AsyncCallback(
                (e) =>
                {
                    HttpWebRequest request = 
                        (HttpWebRequest)e.AsyncState;
                    HttpWebResponse response = 
                        (HttpWebResponse)request.EndGetResponse(e);

                    Stream stream = response.GetResponseStream();
                    DataContractJsonSerializer dataContractJsonSerializer = 
                        new DataContractJsonSerializer(typeof(T));

                    T result = new T();
                    result = 
                        dataContractJsonSerializer.ReadObject(stream) as T;
                    _synchronizationContext.Post(onComplete, result);

                });

        //go statement
        httpWebRequest.BeginGetResponse(done, httpWebRequest);
    }}

Written: 3/25/2010