What's in this article:
Building a Silverlight Web Part for SharePoint 2010
Accessing SharePoint Data from Silverlight Via the Client Object Model
Using Silverlight to access external systems like WCF and REST while inside a sandboxed solution
Deploying a Silverlight Web part for a Sandboxed Solution via a Feature
Debugging Silverlight Web Parts in SharePoint
A Faster Option: Piggyback on the HTML Bridge
DOWNLOAD Source code for the following...
My goal with this blog post is to take a 360 degree tour of where SharePoint and Silverlight technologies come together. Silverlight is undoubtedly a technology that is growing in popularity, and from a SharePoint perspective introduces some unique benefits that can't be achieved any other way. One such benefit is the ability to access external systems from Silverlight while in SharePoint Sandboxed Solutions. This is not possible with a .NET web part.
From a SharePoint data access perspective, there are several compelling techniques that I outline herein, such as using REST and the Client Object Model. Another important technique to understand is HTML bridging, whereby you can reduce server round trips and increase performance of your applications.
But before I get too dirty, I want to start off with a quick Silverlight/SharePoint 101... building a Hello World Silverlight web part...
1) Building a Silverlight Web Part for SharePoint 2010
To get started with our Silverlight journey, let's begin by building a simple "Hello World" Silverlight web part. To spice it up a bit, we'll add some animations, and color effects that show off the "brilliance" of Silverlight.
Building a Silverlight web part involves essentially 3 steps, 1) build the Silverlight application, 2) deploy that application into a SharePoint document library, 3) use the Silverlight web part to render the application. Steps 2 and 3 are pretty elementary, but for newbies, let's walkthrough how to build an application. Note - steps 2 and 3 don't necessarily represent the best practice approach to deploying your Silverlight application. Later in the article we'll touch on a better way, but for now let's keep things simple.
Next choose to create a new hosting site. This site will be used to unit test our Silverlight application before we bother getting it into SharePoint. Choose Silverlight 4 from the drop down (if not present, download the Silverlight 4 toolkit for Visual Studio):
If you double click on MainPage.xaml, you'll notice the Silverlight xaml surface where we can start building our application. Enter the following TextBlock to simply render our text onto the Silverlight application:
<TextBlock x:Name="Text" Text="Hello World!" FontSize="50" VerticalAlignment="Center"TextAlignment="Center" FontFamily="Georgia" FontStyle="Italic"
If you're impatient or nervous, you can test the application by first building the Silverlight project, then building the test project, and simply right click the HTML page and choose View in Browser:
Pretty easy huh?
Well technically we've built our application, but it sure isn't "cool". Let's extend it a bit to change the font color, make it rotate when a user hovers their mouse over it, and make it fade in and out (opacity). First let's make the color a rainbow gradient by adding the following into the xaml just before the </TextBlock> tag:
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Yellow" Offset="1.0" />
To make the text move around we need to add a Silverlight transform. Again, just before the </TextBlock> tag add the following xaml:
<RotateTransform x:Name="spin" Angle="0" CenterX="200" CenterY="50" />
This code define the transform the text block will use. Note it's name is "spin", which is another piece of xaml we'll need to add later. This xaml is more or less a pointer to "spin" which will define what actually will happen.
Now let's add a storyboard and an animation named "spin" that will dictate the movement of the text block. Add the following xaml just above the <TextBlock> tag:
From="0" To="360" Duration="0:0:5"
This xaml defines a storyboard named homeboard, with one animation named spin (a story may have many animations, etc). Spin instructs the object to rotation on its axis over a half second.
The next step is to start the story board. We want our story board to start when the user clicks on the text block. Add the following xaml as a property of the text block (similar to FontFamily, FontSize, etc):
StartAnimation points to our custom event handled defined in the MainPage's code behind. Right click on MainPage in the solution explorer and click View Code. Next add the following code into the class:
private void StartAnimation(object sender, MouseEventArgs e)
This code simply executes the homeboard storyboard.
The last thing we wanted to do was make the text fade in and out by changing its opacity. To do this, add another animation directly below the DoubleAnimation within the storyboard:
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Text" RepeatBehavior="Forever"Storyboard.TargetProperty="(Opacity)" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:01.0000000" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.0000000" Value="1"/>
Notice how we're targeting the "Opacity" property of the TextBlock, and it takes 2 seconds. The first second it fades out, and the second it fades back in.
Before we deploy into SharePoint, build the project(s) again and ensure all the animations are working. The next step is to upload the XAP file (project directory/bin/debug) into a document library in your SharePoint site (note, a more advanced approach would be to package the XAP in a feature and module to have it automatically deployed). Following this, find a page and drop in the Silverlight web part and set the URL property and point it to your XAP file. That should be it!
2) Accessing SharePoint Data from Silverlight via the Client Object Model
I doubt you'll ever build a web part for SharePoint that doesn't need to interact with data that is actually stored in SharePoint. This next section will extend the "Hello World" web part we built in the previous section to include a walkthrough on how to read the web parts text (Hello World) out of a SharePoint list. With a .NET web part this would be trivial. You'd simply do SPContext.Current.Web.Lists["ListName"].GetItemByID(1)["Title"].ToString(), for example. Well the bummer with Silverlight is we're not going to have the same typical Object Model experience. In fact, Silverlight is treated as if it were a client application, such as Word, Excel, etc, and we're going to need to leverage SharePoint's WCF web services to get at our SharePoint data.
If this sounds scary - don't worry! A new feature with SharePoint 2010 is the new client object model. With the client object model, you can access SharePoint data via their WCF services in nearly the identical way you would if you were using the standard OM. The client object model wraps the WCF services with classes that have that work in nearly the same way as standard OM classes work.
For example, the standard OM has a class called "SPWeb", which you are likely familiar with. The client OM has a similar class named "Web". This paradigm is true with other classes such as SPContext->Context, SPSite->Site, SPList->List, etc. The following is a quick example of how these classes are used (for more info, reference this MSDN article):
using (ClientContext context = new ClientContext("http://intranet"))
Web web = context.Web;
string title = web.Title;
The first thing the code does is wrap the Context in a using statement pointing to the SharePoint site we want to get data from. Next the Web object is loaded, followed by calling Execute. When Execute is called, the Client OM will invoke the WCF services and take the round trip to get the data from SharePoint. Note, only the data that is loaded will come back not the entire site collection. If you ever had to use the web services in SharePoint 2007, you'll know how incredibly easy this is in comparison.
With this quick tutorial under our belt, let's extend our Hello World example to read a list item in SharePoint, and retrieve the Hello World text:
Add a reference to the Microsoft.SharePoint.Client.Silverlight DLL and the Microsoft.SharePoint.Client.Silverlight.Runtime DLL (TEMPLATE\Layouts\ClientBin).
Right click on App.xaml, and click View Code. Then, add the following using statement:
Within the Application_Startup method, add the following line of code:
Just within the class, add the following global fields:
Back at the MainPage code behind, add another using statement for Microsoft.SharePoint.Client, and paste the following code inside the constructor, bellow the InitializeComponent() call:
context = new ClientContext(ApplicationContext.Current.Url);
web = context.Web;
List list = web.Lists.GetByTitle("Hello World Test");
item = list.GetItemById(1);
context.ExecuteQueryAsync(new ClientRequestSucceededEventHandler(success), null);
Notice how the first thing you do is instantiate the context off the current Silverlight's application URL. Next we load the web, lists, and the list we want, in this case the list titled Hello World Test. Then we get the item we want and load that as well. The last thing is to call Execute which is what initiates the round trip to the server. The main difference with this example, versus the previous one, is this code works in Silverlight and the other doesn't. Essentially all calls in Silverlight must be asynchronous, hence ExecuteQueryAsync. The next step is to add the "success" method delegate defined in the Async Execute.
Below the constructor paste in the following success method:
private void success(Object sender, ClientRequestSucceededEventArgs args)
Text.Text = item["Title"].ToString();
All we want to do in the success method is update the Text of our TextBlock. The trouble is the UI thread and the background thread (thread we're currently in), are just that... two separate threads. It's impossible for the background thread to update the UI, so the Dispatcher is what allows us to get our code block in the UI thread's queue. With this in place, we're DONE! time to test...
Build the project, upload the new XAP file into the doc library (override old one), and test your web part. This time it will read the Title field from the 1st list item in the list specified in code.
NOTE: CLEAR YOUR BROWSER CACHE! If you are thinking you're not seeing your XAP changes taking hold, clear your browser's cache (delete cookies, etc), then close and reopen your browser. Thereafter you will see your changes.
NOTE: if you get the following error, make sure that you have loaded **ALL** the objects before calling Execute, as well as use the Dispatcher to update the UI. If anyone tells you the XAP file has to be in the Layout\ClientBin directory, they're lying.
Unhandled error in silverlight application invalid cross-thread access
3) Using Silverlight to access external systems like WCF and REST, while inside a sandboxed solution
There are two MAIN reasons why I feel Silverlight is becoming an important asset in a SharePoint Developer's toolbox. The first is obvious... Silverlight gives a developer the ability to produce compelling user interfaces. Unfortunately the benefit of an improved user interface is very subjective, and a developer may have trouble convincing management that it's worth the time and money to build. However, the second reason Silverlight is becoming more prevalent is much less subjective... in fact it is the only viable course of action, given the right requirements. You see, SharePoint 2010 has a new featured called Sandboxed solutions. Sandboxed solutions allow you to deploy code into SharePoint that is isolated from the outside world. Essentially that code can only interact with the site collection that it is deployed into. Sandboxed solutions also allow code usage to be monitored. So for example, say an IT company has a charge back model to its business units. In SharePoint 2007, IT could only charge based on Disk usage, # of sub-sites, or number of site collections. If one business unit has more requests per second, for example, and put a heavier load on their infrastructure, there was no way to monitor that. Now with Sandboxed solutions, IT can monitor the usage of code, such as web parts, but monitoring CPU cycles, etc.
Another important reason sandboxed solutions are being adopted is because infrastructure teams want to keep their servers clean, and "out-of-the-box". Sandboxed solutions allow you to keep a vanilla infrastructure, but still deploy custom code such as custom Web Parts.
It is easy to see why a lot of companies are adopting sandboxed solutions, but here is the problem. What if you want the benefits of Sandboxed solutions, but you still need custom code that can access external systems, such as invoke a web service? Remember, a .NET sandboxed web part can only interact within the site collection that it resides in.
And here is the benefit of Silverlight that a standard .NET sandboxed web part cannot boast - it has the ability to access external systems, even when deployed inside a sandboxed solution! So you see... in SharePoint 2007, Silverlight was "neat" and "cool", but you didn't have a hard and fast reason that would necessitate it. But now in SharePoint 2010, there's a specific use case that brings Silverlight skills for SharePoint Developers into the limelight.
So now that you're convinced of the "why", lets get to the fun part, the "how".
Up until this point we really haven't seen anything that a standard .NET web part can't do (albeit, the animations would be hard in .NET). Let's change the pace by taking changing our Silverlight app to call a WCF service to get it's text. Again, this is not allowable in a Sandboxed solution.
Just like newbies for Silverlight, I'm going to assume I may have some newbie for WCF, and we'll quickly walkthrough how to create a WCF service that returns the string "Hello WCF". We'll then add another TextBlock, but this time it'll read it's string out of the result from the WCF call.
Start by adding a new project into our existing solution. Use the WCF Service Application template.
Paste the following service method above the auto-generated GetData method:
public string GetSilverlightString()
return "Hello WCF!";
in the service interface, IService1.cs, paste the following above the GetData contract:
Rename IService1 to "IHelloService" and rename Service1 to "HelloService" - use right click, rename. Also rename the .cs files in the solution.
With our HelloService built and ready to test, we can now add a reference to our Silverlight project. Go back to the Silverlight project and right click References, and choose Add Service Reference. Then, Paste the URL of the HelloService service and click Go. Lastly, click HelloService under Services, change the Namespace to HelloService, and lastly click Ok:
With our service reference in place, we can now invoke the service in our Silverlight application. Go back to your MainPage.xaml, and just before the end of the constructor paste the following code to invoke the service:
HelloService.HelloServiceClient hs = new HelloService.HelloServiceClient();
hs.GetSilverlightStringCompleted += new EventHandler
This code instantiates the service, and sets up another asynchronous call by registering the Completed method. Then, we call the Get-Async method to invoke the service, and thereafter our "hs_GetSilverlightStringCompleted" method is called with the result of the call. All the following method to update our TextBox text with the result of our service call:
object sender, HelloService.GetSilverlightStringCompletedEventArgs e)
Text.Text = e.Result;
TEST! Build your Silverlight app and update your XAP (either by overriding XAP in document library, or by deploying as described in section 6 of this article). After you update your XAP you'll see that your Silverlight web part is now... BROKEN!
Webpage error details
User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
Timestamp: Sat, 22 Jan 2011 01:56:59 UTC
Message: Unhandled Error in Silverlight Application An exception occurred during the operation, making the result invalid. Check InnerException for exception details. at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary()
SO WHAT GIVES????? The problem is with cross-domain access. You see, Silverlight applications can only access resources in their current domain, eg, if your SharePoint site's URL is http://intranet, but your WCF service is on http://extranet, you'll get this annoying "making the result invalid" error. The there's a easy fix! What you can do is tell your WCF service to ALLOW cross domain access! To do this you drop a clientaccesspolicy.xml file at the root of your WCF service directory. This xml file specifies what domains are allowed to connect to the service. If you use a "*" asterisk, all domains are allowed. Note - this is a huge topic, that I can't do justice in this article, so I recommend reading up on Network Security Restrictions in Silverlight, as well as Making a Service Available across Domain Boundaries.
So then to get past this error, our last step is to create our clientaccesspolicy.xml file. In Visual Studio, inside your WCF project, add a new XML file titled clientaccesspolicy.xml and paste in the following xml:
<?xml version="1.0" encoding="utf-8"?>
<resource path="/" include-subpaths="true"/>
Now with this policy in place, simply go back to your Silverlight web part and refresh... and presto!!!!
4) Deploying a Silverlight Web part for a Sandboxed Solution via a Feature
To be perfectly fair - up until this point you've been working out of a document library with the out-of-the-box Silverlight viewer web part, which is fine. However, not all your web parts are going to be Silverlight web parts. You'll likely have standard .NET web parts along side Silverlight web parts. Why deploy them in 2 different ways? Let's consolidate by having one standard approach. Plus - you'll see in section 7 this deployment approach is very much a necessity.
So to summarize, you're going to create a new SHAREPOINT project. Along side that project, you'll going to create a new Silverlight project in the same solution. In the SharePoint project you'll create a feature that deploys the XAP file into a gallery, and instead of the out-of-the-box Silverlight reader web part, you'll going to create our own reader so end users won't have to bother with the URL property - simply drag-n-drop the web part!
Much cleaner approach, huh? I think so...
To get started, create a new Visual Studio project titled Contoso.SharePoint.WebParts, and leave the project as a Sandboxed Solution when prompted. With this selection, when we deploy the WSP package will be deployed to our site collection's solutions gallery, rather than the farm gallery in Central Administration.
Straight away, add a second project in the same solution. User the Silverlight Application template like before. Name the project Contoso.SharePoint.PictureApp.
Now - to keep things simple and since in section 1 I already went through a lengthy example, I'm just going to replace the Grid in the MainPage.xaml with a Image:
<Image x:Name="CatPic" Source="http://www.katzy.dsl.pipex.com/LOLCats/lolcat-i-question-the-general-assumption-that-feli1.jpg" />
The next thing to do is get the XAP file generated in this project deployed by our SharePoint project. To do this we need a feature and a model. On the SharePoint project, right click the Feature folder and choose Add Feature. Then, right click the project and click Add->New Item, select the SharePoint tab, and select Module. Name the Module PictureAppXAP:
The next "trick" we need to do is tell the module to include the output of the Silverlight project. Right click the Module, and select Properties, and then click on the Project Output References collection property. This will load a popup. Click the Add button to add a new reference and change the reference project to be your Silverlight project. Lastly, change the Deployment Type to be ElementFile:
Credit: I first saw this tip on Andrew Connell's blog.
After you click OK, your module will have a new File in its XML. You'll want to change the URL property of the file to the URL of a Document library that the XAP file should be deployed into, for example:
<?xml version="1.0" encoding="utf-8"?>
Before we go too far, let's test our deployment. Right click the SharePoint project and click Deploy. Then, go to your SharePoint site and check the library and ensure the XAP file was deployed on your behalf. Alternatively, you can build the project, and then right click and click Package. This will generate the WSP which can be uploaded to the solution gallery manually, and thereafter you can manually activate the feature.
Note - Sandboxed solutions must be enabled in Central Admin. If you get the following error, from Central Admin, services on server, start the "Microsoft SharePoint Foundation Sandboxed Code Service":
Error occurred in deployment step 'Activate Features': Cannot start service SPUserCodeV4 on computer 'DEV01'
With our XAP getting deployed properly, it's now time to create a standard web part to render the Silverlight application. This will allow our users to simply drop the web part on the page, without having to worry about entering the XAP URL. To get started, right click the SharePoint project and choose Add->New Item->Web Part (not Visual WP) and name it PictureAppWebPart.
Within CreateChildControls of the new web part, add the following code to render the Silverlight application (make sure to update the URL to the XAP):
StringBuilder o = new StringBuilder();
type=\"application/x-silverlight-2\" width=\"100%\" height=\"100%\">")
.Append("<param name=\"source\" value=\"/Documents/Contoso.SharePoint.PictureApp.xap\" />")
.Append("<param name=\"background\" value=\"white\" />")
.Append("<param name=\"minRuntimeVersion\" value=\"4.0.50826.0\" />")
.Append("<param name=\"autoUpgrade\" value=\"true\" />")
.Append(" <img src=\http://go.microsoft.com/fwlink/?LinkId=161376\
alt=\"Get Microsoft Silverlight\" style=\"border-style: none\" />")
style=\"visibility: hidden; height: 0px; width: 0px; border: 0px\"></iframe>");
note - This code is a bit ugly, but since we're in a sandboxed solution we can't use the Visual Web Part, otherwise it would've been slick to stick this HTML into the ASCX.
That should be it! Deploy your project again, and that will deploy the new web part into the sandboxed solution. Then, drop the web part on the page and you should see you Silverlight Image app!
5) Debugging a Silverlight Web Part in SharePoint
Debugging a Silverlight app is pretty straight forward - when SharePoint isn't in the equation. Before you deploy to SharePoint, try to do as much debugging as possible in the Silverlight project, because it is easy to set a breakpoint and hit F5.
When you get into the context of SharePoint you'll need to do a few more things. First off, you MUST deploy your XAP via a feature, thus following the steps outlined in section 4 of this article. Also a must - your breakpoints will never hit until you tell your SharePoint project to debug Silverlight. Right click your project and click the SharePoint tab. Then at the bottom check the Enable Silverlight debugging checkbox:
Now, simply open your SharePoint site to the page that has you Silverlight application on it. Then, click Attach to Process... and select your browser's process. Make sure to select the one with the Type of Silverlight, x86:
6) A Faster Option: Piggyback on the HTML Bridge
Silverlight can be chatty. When a browser does a GET, the server then POSTs the page back to be rendered. It's at this point that Silverlight loads, after the first round trip to the server and back. This is also the point when Silverlight calls your custom code, such as invoking a service or using the Client Object Model. When your Silverlight app runs this custom code it is taking a second round trip to the server. These subsequent round trips is an areas we you may able to be more efficient.
The basic strategy is to build a standard .NET web part that sticks the data that the Silverlight web part needs on the page in hidden fields. Then, the Silverlight app can access the page's DOM and retrieve the data off the page, and skip the trip to the server. This is what's called the "HTML Bridge", or piggyback. More generally, you can do some of the following things (among others) on the bridge:
Access DOM from SL
Call JS from SL
Call Silverlight code from JS
Send and receive types from JS/SL
This is a significant topic and I recommend reading this MSDN article for further study. However, that article is kind of an abyss of information, so my goal in this section is to demonstrate the most common scenario of accessing the DOM from a Silverlight app. What we're going to do is create a standard web part that renders a hidden field which stores a url to an image. This URL is pulled from a SharePoint List. Then, our Silverlight app will look for this hidden field, rather than do a round trip to the server to lookup the URL from the SharePoint list... aka one less round trip and a faster load time.
In section 4 of this article you built a Silverlight web part that was deployed via a feature. This silverlight app simply rendered a Image of a cat. Instead of hardcoding the image url, let's stick the url in a hidden field and change the web part to look at the hidden field. To render the hidden field, paste the following code at the end of CreateChildControls:
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists["CatPics"];
SPListItem item = list.GetItemById(1);
string imageurl = item["Title"].ToString();
o = new StringBuilder();
o.Append("<input type=\"hidden\" id=\"SharePointData\" value=\"" + imageurl + "\" />");
This code simply reads a SharePoint list and pulls the url out of a title column in the first list item. Then, it sticks that url in a hidden field, which is rendered via a LiteralControl.
The next step is to change the Silverlight app that was created in section 4 to use this url in the hidden field, from the hard coded url in the Image Silverlight control. In the PictureApp project, go to the code view of the MainPage.xaml and paste the following code into the Initialize method:
HtmlDocument doc = HtmlPage.Document;
HtmlElement field = doc.GetElementById("SharePointData");
string source = field.GetProperty("value") as string;
CatPic.Source = new BitmapImage(new Uri(source, UriKind.Absolute));
This code uses a HtmlDocument object to access the Silverlight's page's DOM to grab the hidden field called "SharePointData". The value of the hidden field is then retrieved, and the Image source property is updated, thus updating the UI.
So you can see that this process of HTML Bridging is very easy, and is a great way to make your Silverlight applications perform faster.