Lessons Learned Integrating Silverlight in MOSS 2007, Part 2
This is a continuation of yesterday's post detailing some lessons learned while integrating a Silverlight application into an Internet-facing customer service portal built on Microsoft Office SharePoint Server (MOSS) 2007.
As I mentioned in the previous post, the Fabrikam.Portal.Web.ServiceWheel project is the Silverlight application (a XAP file) which is deployed via a SharePoint Web Solution Package (WSP). The WSP is generated by the Fabrikam.Portal.Web project. The Silverlight application is hosted inside of the SharePoint site by a user control (ServiceWheel.ascx) which itself is hosted within a generic User Control Web Part (similar to SmartPart for SharePoint).
I also mentioned that the user control originally contained the following code:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="100%" height="100%">
<param name="source" value="_Layouts/Fabrikam/Wheel.xap" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40624.0" />
<param name="autoUpgrade" value="true" />
<a href="https://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="https://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
There are two problems with this code.
The first is that the Silverlight XAP file is served from the _layouts folder by specifying a simple path to the XAP file. This works great until you need to make a change to the Silverlight application -- for example, to fix a bug or implement an enhancement.
Regardless of whether or not you enable the BlobCache in SharePoint (and you should always enable it on your TEST and PROD environments), content served from _layouts includes a Cache-Control HTTP header that specifies the content should be cached by the client (max-age=31536000). Using IIS Manager, you can verify that SharePoint enables the setting on _layouts to expire Web content after 365 days (31,536,000 seconds).
This is actually a very good thing. For example, you certainly wouldn't want clients attempting to download the out-of-the-box SharePoint core.js file on each and every page request -- even if all the Web server does is send a small HTTP 304 (Not Modified) response. Likewise, you really don't want them requesting your Silverlight XAP file on every request.
Fortunately, there's an easy way to specify the versionof a file in the URL using the (rather poorly documented) SPUtility.MakeBrowserCacheSafeLayoutsUrl
method. This method actually specifies a hash of the file -- not a version -- but you get the point. This is covered in more detail in one of Stefan Goßner's blog posts.
To ensure that clients always have the latest version of the Silverlight XAP file, I modified the user control that hosts the Silverlight application as follows:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="380px" height="410px" onFocus="this.style.outline='none';">
<param name="source" value="<%= serviceWheelPackageUrl %>" />
...
</object>
The serviceWheelPackageUrl
variable is defined and set in the code-behind for the ASCX file:
public partial class ServiceWheel : System.Web.UI.UserControl
{
protected string serviceWheelPackageUrl;
protected void Page_Load(
object sender,
EventArgs e)
{
serviceWheelPackageUrl = SPUtility.MakeBrowserCacheSafeLayoutsUrl(
"Fabrikam/Fabrikam.Portal.Web.ServiceWheel.xap",
false);
...
}
...
}
Consequently, whenever a page is requested with the Silverlight control the generated URL is something like:
/_layouts/Fabrikam/Fabrikam.Portal.Web.ServiceWheel.xap?rev=H3EKfK%2FcQ5VV%2FpRcJ5MzwA%3D%3D
Note that I had previously changed the name of the XAP file generated by the Fabrikam.Portal.Web.ServiceWheel project from Wheel.xap (specified by the original Silverlight developer) to Fabrikam.Portal.Web.ServiceWheel.xap. Personally, I always like to see fully-qualified names used for assemblies, WSPs, etc. Assuming you begin your namespaces with your company name -- and you always should -- this makes it much easier to quickly identify "your stuff" amidst a bunch of "other stuff."
I mentioned before that there were two problems with the original HTML <object>
tag shown above. Actually there are more than two, but for the sake of keeping this post reasonably short, I'll only cover two of them here.
The second problem is a rather obscure issue in the <img>
element:
<a href="https://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="https://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
The intent of this markup is to show an image link to "Get Microsoft Silveright" whenever Silverlight is not installed. It seems relatively simple, doesn't it?
The problem is that Firefox attempts to download the image (at least in version 3.5), regardless of whether or not Silverlight is installed (unlike Internet Explorer, which ignores the image when Silverlight is installed). This typically doesn't pose a problem except for the fact that this site is an Internet-facing customer service portal which is configured to use Forms-Based Authentication. Consequently we seamlessly switch from HTTP to HTTPS when showing the login page.
Firefox subsequently complains that the page with the Silverlight control contains a mix of secured and unsecured content. In other words, the status bar shows a small lock icon with a bright red warning symbol, along with the following tooltip:
Warning: Contains unauthenticated content
To avoid this issue in Firefox, you might be tempted to simply change the image source to specify https://go.microsoft.com/fwlink/?LinkId=108181. However, if (like we did) you fire up Fiddler and request this URL, you will find the response to be an HTTP 302 (redirect) with the following header:
In other words, while the initial request for the image is HTTPS, the subsequent request will be HTTP. [Don't ask me to explain or make any sense of why go.microsoft.com does this. It seems like a bug, in my opinion.]
In order to avoid the switch from HTTPS to HTTP (and thus potentially any warnings in Firefox), we decided to simply host the InstallSilverlight.png image locally on our SharePoint servers (deployed via Fabrikam.Portal.Web.wsp):
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
width="380px" height="410px" onFocus="this.style.outline='none';">
...
<a href="https://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
<img src="/_layouts/Images/Fabrikam/InstallSilverlight.png" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
In hindsight, I suppose for consistency we could have used the SPUtility.MakeBrowserCacheSafeLayoutsUrl
method for the "Get Microsoft Silverlight" image, but honestly, I don't think there is sufficient justification in this scenario.
So, there you have it, two more important lessons learned while integrating Silverlight into SharePoint.
Stay tuned, there's more to come! Same bat time, same bat channel ;-)