Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Creating a Silverlight 5 Helper for ASP.NET MVC3 Razor

Pete Brown - 01 August 2011

Last night, while working on the REST chapter in Silverlight 5 in Action, I started playing around with ASP.NET MVC 3 and Razor. The REST library I'm using: WCF Web API, which works with MVC 3. (so yes, that one chapter has REST, WCF Web API, ASP.NET MVC 3, Razor and NuGet. fun!) While it does work, I didn't want to stick an .aspx file in the MVC 3 project as it just didn't seem right. So, I looked into how to get Silverlight on one of the MVC pages.

Today, I built the start of a Silverlight helper, something I'll likely expand upon later, and something you can modify yourself if you desire.

There's an existing helper, @Video, which apparently works with Silverlight projects. However, I didn't like that helper name for Silverlight apps, and I also wanted to try to build a helper myself for grins.

Warning!

I am generally a client developer (WPF, Silverlight, XAML in-general) and am completely new to MVC 3 and Razor. While I may have blindly stumbled into some best practices, don't assume there are any consciously included in this post.

To date, I have done absolutely nothing with Razor or MVC, so this was a learning experience. That said, I did find it very intuitive (well, as intuitive as HTML development ever is), and was able to quickly produce something usable. Let's get to it.

Prerequisites

  • Make sure you have ASP.NET MVC3 installed on your machine.
  • Make sure you have Silverlight 5 installed

MVC 3 Project Setup

Create a new ASP.NET MVC 3 Web Application named MvcSilverlightHelper

image

Go with the default (in my case) options on the project setup dialog. You want an empty project using the Razor view engine. You could pick one of the other application types, but I wanted to go through the process of creating the home page and controller myself.

image

When complete, you'll have a solution with a single MVC 3 project named MvcSilverlightHelper. The default MVC template sets up a pretty good project structure with all the necessary folders.

image

Silverlight Project Setup

Now we're ready to add in the Silverlight project. Right-click the solution and add a new Silverlight Application project. Name the project "SilverlightApp".

image

When prompted, you want to add this to the existing MVC web site. We'll eventually delete them, but for now, leave the options to add the test pages checked. We'll snag some information from those pages.

image

Silverlight App UI

You'll be popped right into familiar territory once that is created. We're not going to do anything spectacular with the Silverlight app, but we do want to make its existence obvious. So, use this XAML for the Silverlight app's main page.

<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Grid Grid.Row="0">
<Rectangle Fill="Orange" />
<TextBlock FontSize="30"
Foreground="White"
Margin="10"
VerticalAlignment="Center"
Text="Hello World!" />
</Grid>

<Grid Grid.Row="1"
Background="LightBlue"
Margin="0 5 0 0">
</Grid>
</Grid>

Once in, you should see an absolutely stunning orange and blue block like this on the design surface:

image

That's it for the Silverlight application. Of course, you can add any Silverlight application you want, but I chose this simple example so you could easily see the entire bounds of the Silverlight element on the page.

Build the solution, then right-click the SilverlightAppTextPage.aspx file and choose "View in Browser". If you see the designed UI in the browser, you are good to move on to the next step.

image

With the MVC project created, and the Silverlight project created and linked to the web site, the next step is to create the Silverlight helper itself.

The Silverlight Helper Placeholder

In the MVC project add a new folder named "App_Code". While this is a standard ASP.NET folder, it doesn't show up in the standard folder selections. However, once you add it, it will get magically recognized and the icon will change.

Then, in that folder, add MVC 3 Partial Page named "Helpers.cshtml". For this example, this file will simply be located in the root of the project.

image

Inside that page, add the following bit of code. The code here defines the Silverlight helper which takes two parameters. Inside the razor code we simply spit out a little bit of HTML that echoes back the parameter values.

@helper Silverlight(string xapPath, string version) {
<div>
<p>Silverlight Helper</p>
<p>@xapPath</p>
<p>@version</p>
</div>

}

Make sure you put that opening parenthesis on the same line as the @helper declaration. It took me a bit to figure out that was why my helper wasn't being recognized.

That's going to serve as a placeholder so we can test to make sure our helper can be used on a page. The next step is to create a page we can use to test this helper and make sure it works.

MVC Page Setup

In the Views folder, create a subfolder named "Home". Inside that home folder, add a new MVC 3 Partial Page named Index.cshtml.

This is going to be the home page for the site. The contents of that page are pretty basic, just a generated reference to the layout file, and a call to our Silverlight helper.


@Helpers.Silverlight("~/ClientBin/SilverlightApp.xap", "5.0")

Controller and Testing

The next step is to add the controller for this home page. Right-click the Controllers folder and select Add->Controller. Name the controller HomeController. This is the code responsible for rendering our view.

image

That will generate a controller which does nothing more than return the correct view for our home page. I didn't change any of the auto-generated code.

Before we test, there's just one more thing we need to do: eliminate the default document. Right-click the project, open the properties pages and navigate to the Web tab. On the web tab, change the Start Action to "Specific Page" and set it to blank

image

Now, run the project. You should see the output from your Silverlight helper as shown here:

image

Success! We now have an MVC project working with our own custom helper. Let's have it actually output something useful now.

The Silverlight Helper Itself

Now we get into the part that may require some debugging and testing should you expand upon this example. I hope you have a copy of the supported browsers hanging around - I have IE9, Chrome and Firefox all on my machine; that's the set I'll use for testing.

Crack open the SilverlightAppTestPage.html and grab all the HTML from the silverlightControlHost div through to the end of that div (on the same line as the closing object tag). It's important that you retain the format and not break apart any lines that are squished together, otherwise you'll end up with strangeness in various browsers.

Take that markup and paste it right over the <div></div> in our Silverlight helper. You should end up with a helper that looks like this (your Silverlight version number will likely be different as I'm running an internal interim build):

@helper Silverlight(string xapPath, string version)
{
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/SilverlightApp.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.60526.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.60526.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>

}

The next step is to substitute the parameters passed in.  The first parameter is for the XAP path. This tells the plug-in the location of our Silverlight application. I want it to support project-root-relative URLs starting with the tilde, so I'm using the @Href function.

<param name="source" value="@Href(xapPath)"/>

The second is for the version number. This needs to be changed on two lines, the minRuntimeVersion and the link to learn more about Silverlight.

<param name="minRuntimeVersion" value="@version" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=@version"
style="text-decoration:none">

Error Handling and Styles

Now, we'll add in the div styles from the original page (in-lining them into the div), as well as the error handler. The final Silverlight helper markup and code looks like this:

@helper Silverlight(string xapPath, string version)
{
<script type="text/javascript">
function onSilverlightError(sender, args) {
var appSource = "";
if (sender != null && sender != 0) {
appSource = sender.getHost().Source;
}

var errorType = args.ErrorType;
var iErrorCode = args.ErrorCode;

if (errorType == "ImageError" || errorType == "MediaError") {
return;
}

var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n";

errMsg += "Code: " + iErrorCode + " \n";
errMsg += "Category: " + errorType + " \n";
errMsg += "Message: " + args.ErrorMessage + " \n";

if (errorType == "ParserError") {
errMsg += "File: " + args.xamlFile + " \n";
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
else if (errorType == "RuntimeError") {
if (args.lineNumber != 0) {
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
errMsg += "MethodName: " + args.methodName + " \n";
}

throw new Error(errMsg);
}
</script>

<div id="silverlightControlHost" style="height: 100%;text-align:center;">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="@Href(xapPath)"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="@version" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=@version" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>

}

Note that I left out the silverlight.js line. That JavaScript file is very useful, but I didn't want to link it in on this example as it isn't strictly required here.

That's all there is to it! Be sure to read on below as I explain a few limitations of my implementation. Those are based simply on my inexperience with Razor and MVC 3.

Running

With that in place, run the application. You should end up with a page that looks like this:

image

image

Note the differences in how IE9 and Chrome render. One is too small, one is too big. Where's Goldilocks? Read on.

image

Woah. Firefox won't even show the plug-in. This is an issue we found out a loooong time ago with rendering differences, and is the reason behind the styles in the test pages. I bring it up here in case others run into it when incorporating Silverlight into their applications. Without setting the height to 100% and overflow to auto for the page, you get the inconsistent rendering above.  Firefox is actually rendering the Silverlight control, just in a 0px high div.

One way to fix this, and in fact the correct way for apps that aren't full screen, is to wrap the Silverlight rendering inside a div of the correct dimensions. Here, I've set it to be 500px.

<div style="height:500px;">
@Helpers.Silverlight("~/ClientBin/SilverlightApp.xap", "5.0")
</div>

And you can see that on all three browsers, the rendering is correct at 500px high.

image

I'm going to stop this example for now and save handling sizing and whatnot for a potential future post. You can now delete the .aspx and .html pages if you want. Or, you can keep them around so you can refer back to the original source when modifying this helper.

Limitations and Options

I don't modify the HTML page and body styles in this helper. For full-page Silverlight applications, the following styles are very important, as we saw above:

<style type="text/css">
html, body {
height: 100%;
overflow: auto;
}
body {
padding: 0;
margin: 0;
}
#silverlightControlHost {
height: 100%;
text-align:center;
}
</style>

Without the styles, you end up with the Silverlight application only taking up a portion of the page, or in the case of Firefox, not appearing at all. However, it would be completely in appropriate to modify the page style from inside this helper. Instead, I'll likely modify the helper so it can take a set of optional dimensions

I don't want to make any changes or publish this until I decide on an appropriate course of action for the helper's styles and dimensions. It needs to be respectful of the rest of the page, without nasty side effects.

This version of the control also supports only a single Silverlight instance on a page. You'd need to dynamically generate the div IDs, avoid creating a second iframe, and only generate a single error handler if you wanted to use more than one of these on a single page.

This version doesn't include any of the optional parameters, such as background color, initparams and more.

You can also create helpers completely from code. I'm sure in many instances, that will be what you need to do in order to have more flexibility in generating the output. You'll also want to package the helper into a library if you want to publish it on NuGet. I may visit that in a future post.

Note that there's no downloadable source for this at the moment, as I'm using pre-release Silverlight components, and this post is just to give you an idea of how to create a helper. The Helpers.cshtml file is really all you need if you have an existing MVC 3 project in any case.

     
posted by Pete Brown on Monday, August 1, 2011
filed under:      

17 comments for “Creating a Silverlight 5 Helper for ASP.NET MVC3 Razor”

  1. Jarrodsays:
    This will actually be really handy Pete! It would have saved a ton of html markup for my existing MVC3 + wcfapi + Silverlight app and kept my app more 'DRY.' Hopefully you can release it with SL5...
  2. austin avrashowsays:
    Only scanned article so far, but GREAT concept. Synergy between Microsoft technologies (esp web ones that all boil down to HTML) is "greater than the sum of the parts" utilizing the strengths of different technologies.

    I've always thought it's in the boundaries between Microsoft technologies that documentation and books lets down. Each technology is focused on its internals and the stitching between them is under explained, IMO.
  3. Bart Czernickisays:
    I wrote something similar using the new optional parameters in C# back in November 2010:

    http://www.silverlighthack.com/post/2010/11/16/Creating-a-ASPNET-MVC-HTML-Helper-for-Silverlight.aspx

    It doesn't use the new Razor syntax, but gets the job done.
  4. Martin Kirksays:
    The only problem with this helper, is that you are adding the Javascript to the page everytime you call the helper... therefore you'll run into trouble if you add 2 Silverlight controls to the same page.

    instead you should simply put the content of the JS into a file and reference it on your masterpage...

    secondly - i think it would be more clever to simply invoke a new Silverlight application using Javascript... i think that is still possible in SL 4-5 ???

    <body>

    <div id='errorLocation' style="font-size: small; color: Gray;" />

    <div id="SLControls">

    </div>
    </body>

    /**/*/*/*/*/*/*/*/*//**/*/*/*/*/*/*/*/*//**/*/*/*/*/*/*/*/*//**/*/*/*/*/*/*/*/*//**/*/*/*/*/*/*/*/*/

    var SLObjs = 1;
    function MakeSLPrinter(strarr) {
    var Parent = document.getElementById("SLControls");

    var newParent = document.createElement("div")
    newParent.setAttribute("id", "silverlightControlHost" + SLObjs);

    Parent.appendChild(newParent);

    var xap = "ClientBin/PlanITSilver.Interface.xap";
    var id = "Xaml" + (SLObjs++);
    var props = {
    width: '650',
    height: '950',
    inplaceInstallPrompt: false,
    background: '#FFFFFF',
    isWindowless: 'false',
    framerate: '24',
    version: '2.0.31005.0'
    };

    var events = {
    onError: onSilverlightError,
    onLoad: null
    };

    var initParam = QueryString.replace(/&/gi,",") + ", PrintList=" + strarr;

    Silverlight.createObject(xap,newParent,id,props,events,initParam);
    }
  5. Petesays:
    @Martin

    Yes, I specifically point that out near the end of hte article. Thanks.

    On using Javascript: It's unclear to me how one would package up the javascript file with the dll if they were going to distribute the helper on something like NuGet. Any idea?

    Pete
  6. Jimsays:
    Looks very interesting. Thanks for posting this.

    One question, in the prereq you mention to be sure Silverlight 5 is installed?
    I missed the particular features of SL5 that your taking advantage of to make this work, could you elaborate?
  7. aurthursays:
    Thanks Pete! Your post was a life saver! It has helped me tremendously as I was able to use it to successfully serve up a SL4 Bing Maps project within an MVC 3 solution/project/framework. The integration is smooth and works great. Now, I'm just trying to make it better. I hope you can help provide some guidance on the steps to take so that the SL4 page is not reloaded each time I navigate away from the Home page (for example, if user clicks the About tab to go to that page) and then return to it. I'm not finding much in my research as to how to accomplish this (assuming it's even possible).

    So, again, here's what my Index.cshtml file contains:
    @{
    ViewBag.Title = "Home Page";
    }

    <h2>@ViewBag.Message</h2>
    <p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
    </p>

    <div style="height:675px;">
    @Helpers.Silverlight("~/ClientBin/BingMaps.Silverlight.xap", "4.0")
    </div>

    If I click on the About button - which displays the About View - and then click on the Home button to get back to the Home View, the SL4 Bing Map reloads. This is not optimal (imagine that load going to a db to display all types of map layers and pushpins on those layers - you only want to do this once). How do I get it to do as follows: when I click the About button keep the SL4 "in memory" and then proceed to the About View, when I click back on Home button redisplay the in-memory view of the map.

    Surely this is doable, right? :P

    tia for any help
  8. John Cutburthsays:
    Hey this helped out I needed something more robust so I would not need to keep changing it on every project so I am going to add it here for anyone else to use.

    @helper Silverlight(string xapPath,
    string customParms = "",
    bool windowless = false,
    bool enableHtmlAccess = false,
    string backgroundColor = "white",
    string installSlImg = "http://go.microsoft.com/fwlink/?LinkId=161376", // for custom install img
    string width = "100%",
    string height = "100%",
    string version = "5.0.61118.0", //"4.0.50826.0" for SL 4
    bool AddSlErrorFunction = true, // when adding more than one control to a page
    string divObjectTagId = "silverlightControlHost"){
    if (AddSlErrorFunction)
    {
    <script type="text/javascript">
    function onSilverlightError(sender, args) {
    var appSource = "";
    if (sender != null && sender != 0) {
    appSource = sender.getHost().Source;
    }

    var errorType = args.ErrorType;
    var iErrorCode = args.ErrorCode;

    if (errorType == "ImageError" || errorType == "MediaError") {
    return;
    }

    var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n";

    errMsg += "Code: " + iErrorCode + " \n";
    errMsg += "Category: " + errorType + " \n";
    errMsg += "Message: " + args.ErrorMessage + " \n";

    if (errorType == "ParserError") {
    errMsg += "File: " + args.xamlFile + " \n";
    errMsg += "Line: " + args.lineNumber + " \n";
    errMsg += "Position: " + args.charPosition + " \n";
    }
    else if (errorType == "RuntimeError") {
    if (args.lineNumber != 0) {
    errMsg += "Line: " + args.lineNumber + " \n";
    errMsg += "Position: " + args.charPosition + " \n";
    }
    errMsg += "MethodName: " + args.methodName + " \n";
    }

    throw new Error(errMsg);
    }
    </script>
    }
    <div id="@divObjectTagId" style="height: 100%;text-align:center;">
    <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="@width" height="@height">
    <param name="source" value="@Href(xapPath)"/>
    <param name="onError" value="onSilverlightError" />
    <param name="background" value="@backgroundColor" />
    <param name="minRuntimeVersion" value="@version" />
    <param name="autoUpgrade" value="true" />
    @if (!string.IsNullOrWhiteSpace(customParms))
    {
    <param name="initParams" value="customParms" />
    }
    @if (windowless)
    {
    <param name="windowless" value="true" />
    }
    @if (enableHtmlAccess)
    {
    <param name="enableHtmlAccess" value="true" />
    }
    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=@version" style="text-decoration:none">
    <img src="@installSlImg" alt="Get Microsoft Silverlight"
    style="border-style: none" />
    </a>
    </object>
    <iframe id="_sl_historyFrame" style="visibility: hidden; height: 0px; width: 0px;
    border: 0px"></iframe>
    </div>
    }
  9. soumensays:
    instead of

    @helper Silverlight(string xapPath, string version) { <div> <p>Silverlight Helper</p> <p>@xapPath</p> <p>@version</p> </div> }


    i tried

    @helper Silverlight(xapPath as String, version as String)
    <div>
    <p>Silverlight Helper</p>
    <p>@xapPath</p>
    <p>@version</p>
    </div>
    End Helper

    as i am trying this in vb.net. But it is giving error:

    1. Attribute specifier is not a complete statement. Use a line continuation to apply the attribute to the following statement.
    2. '#ExternalSource' directives cannot be nested.
    3. Identifier expected.

    please help
  10. Jon Gallowaysays:
    @soumen -

    I don't write much VB.NET, but I think you need an @ before the first <div>, like this:

    @helper Silverlight(xapPath as String, version as String)
    @<div>
    <p>Silverlight Helper</p>
    <p>@xapPath</p>
    <p>@version</p>
    </div>
    End Helper

    Some sources on VB.NET Razor syntax:
    http://www.asp.net/web-pages/tutorials/basics/asp-net-web-pages-visual-basic#BM_CombiningTextMarkupAndCode
    http://www.asp.net/web-pages/tutorials/basics/asp-net-web-pages-visual-basic#BM_CombiningTextMarkupAndCode
  11. Rajsays:
    Nice post. I am trying to replicate the steps with ASP.NET MVC4 internet application where _layout.cshtml is there and can't get my Silverlight shown up on Razor view.

    Please advise if there is anything special I need to do.

    Thanks
  12. Vitlaiysays:
    I am very surprised to see how Html helper is implemented. Usually helpers implemented as extension methods...
    Probably it is even better than extension - as you have a lot of strings and it would be hard to create such long string in C# code... Cool -)

Comment on this Post

Remember me