ASP.NET MVC 2 and Silverlight Playing Nice

I have been getting into Silverlight quite a bit lately, probably because I have Expression Studio 3 now (and soon to be Expression Studio 4). Originally being a windows developer then moving to an MVC web developer makes me miss some of the windows like stuff I used to do. Silverlight has given that back to me and Expression Blend has made creating a UI fun and easy. I originally found it awkward to inject a Silverlight application into an MVC web app. I even gave up on Silverlight the first time I tried it. Now I have a bit more knowledge about the two and I wanted them to work well together. I found some of the pain points and simplified them a bit.

Recently I needed more control over uploading a file in an ASP.NET MVC application. I outsourced to Silverlight and created a new control library. Since the control library is a separate project I also need to create a Silverlight application to use the new user control. This new Silverlight control also needs to have a web application to use the Silverlight application. When you add the Silverlight application through the MVC project in the project options you get a ClientBin folder off the root that holds an xap file that builds from your Silverlight application. Consider this your DLL from Silverlight. Also, by default, you will get an aspx test page for your Silverlight application. This is really the starting point, you're done with setup, now let's begin.

If you open the test page you will see a lot of scary Html/Javascript code. I have separated this out to make it easier to read. Start with the Javascript. I moved the reference to Silverlight.js out to my master page because my app uses Silverlight in several places.

<script type="text/javascript" src="<%= Url.Content("~/Scripts/Silverlight.js") %>"></script>

Notice the use of Url.Content to find the script instead of an absolute path. I do this so that the script can be found even if I am using a virtual directory.

Next, take the onSilverlightError Javascript function and move it to another script file. I moved mine to my default Javascript file for my website.

All you are left with is this nasty form and object tag that is incredibly complicated and something you could never recreate if you had to. We will deal with that later. By now you should be realizing that you really don't want to be using an aspx page in your nice, new MVC project. That is just wrong. Ideally, I want to be dropping Silverlight in a partial view. Especially if it just a file upload control. So let's do that.

I want to just drop this Silverlight control on my index page of this new MVC application. So I create a partial view off of my Home controller and call it UploadAsset. In my index view I can call the partial view.

<% Html.RenderPartial("UploadAsset"); %>

In the UploadAsset partial view all we really want is that scary form and object tag that was in the test page. Copy that and paste that into your partial view. Once that is done you should have a fully functional Silverlight application running in the context of MVC. I accepted this solution for a while. Then I realized it doesn't feel like I'm writing MVC code. It doesn't even feel like a good solution. I hate that object tag and I know I could never write that from scratch if I had to and if I had not created a test page I never would have even known how to create the tag.I want it to feel like Silverlight is made for MVC; I want to have a Silverlight reference in my model; I want to wire it up in the controller. And ideally I would want to use an Html helper class for Silverlight so that the framework could just know how to display it.

Let's start with the Model.

I created a model that holds the basic information about what a Silverlight reference is and defaulted the basics in the constructor.

public class SilverlightObject
{
    public string XapName { get; set; }
    public Size Size { get; set; }
    public string OnSilverlightError { get; set; }
    public Color BackgroundColor { get; set; }
    public string MinimumRuntimeVersion { get; set; }
    public bool AutoUpgrade { get; set; }

    public SilverlightObject()
    {
        OnSilverlightError = "onSilverlightError";
        BackgroundColor = Color.White;
        MinimumRuntimeVersion = "3.0.40624.0";
        AutoUpgrade = true;
    }
}

The non-defaulted properties are XapName and Size. The Xap name is the reference name you find under the ClientBin folder of you web project. The Size is just how big you want the Silverlight canvas to be. The rest of the properties are probably the same for every Silverlight application in your web project. If not, you can change the default.

Now that I have a new SilverlightObject type, I can use it in a view model.

public class IndexViewModel
{
    public SilverlightObject UploadControl { get; set; }
}

In the controller we can now wire this up like we would any view model.

public ActionResult Index()
{
    SilverlightObject uploadControl = new SilverlightObject
    {
        XapName = "UploadDemo",
        Size = new System.Drawing.Size(400, 800)
    };

    IndexViewModel model = new IndexViewModel
    {
        UploadControl = uploadControl
    };

    return View(model);
}

Then in the index view we can change where we call the partial because now our partial view can be strongly typed to a SilverlightObject.

<% Html.RenderPartial("UploadAsset", Model.UploadControl); %>

Now it feels more like MVC but it doesn't work. It doesn't use our settings we wired up for the UploadControl. Ideally we want our partial view to just contain:

<%= Html.DisplayForModel() %>

We can do that by telling the framework what to use when it sees a type of SilverlightObject. To do this, create a partial view under View\Home\DisplayTemplates called SilverlightObject and strongly type it to a SilverlightObject. Go back to your UploadAsset partial and get that scary form and object tag and replace it with the DisplayForModel call. Drop that form and object tag into the SilverlightObject template and now you can start piecing it together with the SilverlightObject model you have strongly typed to. In the end it can be as customizable as you want. Here is what I have.

<form id="silverlightForm" runat="server" style="height:100%">
  <div id="silverlightControlHost">
    <object data="data:application/x-silverlight-2,"
            type="application/x-silverlight-2"
            width="<%= Html.Encode(Model.Size.Width) %>"
            height="<%= Html.Encode(Model.Size.Height) %>">
      <param name="source"
             value="<%= Url.Content("~/ClientBin/" + Html.Encode(Model.XapName) + ".xap") %>"/>
      <param name="onError"
             value="<%= Html.Encode(Model.OnSilverlightError) %>" />
      <param name="background"
             value="<%= Html.Encode(Model.BackgroundColor.Name) %>" />
      <param name="minRuntimeVersion"
             value="<%= Html.Encode(Model.MinimumRuntimeVersion) %>" />
      <param name="autoUpgrade"
             value="<%= Html.Encode(Model.AutoUpgrade) %>" />

        <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration:none">
          <img src="http://go.microsoft.com/fwlink/?LinkId=108181"
               alt="Get Microsoft Silverlight"
               style="border-style:none"/>
        </a>
    </object>
    <iframe id="slhistoryFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>
  </div>
</form>

It's confusing, but look at why it is confusing. Silverlight made it confusing, I only made it so that I never have to see how confusing it is.

MVC will automatically pick up on this new template when you run it. When it parses the Html.DisplayForModel() it will see that your model has a SilverlightObject and it will look for a view that knows how to handle that type. We created one for it and it will use that one. MVC will pass the view the data for the SilverlightObject and our view will create that scary object tag.

This extension is even reusable for any Silverlight references you might have in your MVC application. It will even feel like Silverlight belongs in MVC!