Performance Tips for Silverlight SharePoint Applications
How to improve loading and response time
April 15, 2011
SharePoint 2010 provides developers with a new way of interacting with SharePoint sites through the client object model. This new support enables developers to create rich and interactive client-side applications that use SharePoint as the back end for data storage, content versioning, document management capabilities, workflow, and more. Developing against the client object model means that Silverlight applications must include the assemblies that contain the client object model, which can dramatically increase their size. I’ll demonstrate two performance techniques Silverlight developers can use to make faster loading, more responsive, and highly adaptive SharePoint applications.
Technique 1: Download the Silverlight Client Object Model on Demand
The SharePoint 2010 client object model consists of three different implementations: .NET, ECMAScript, and Silverlight. Like most client applications, the .NET implementation is typically embedded within the application that uses the client object model. The ECMAScript implementation is found within a specific JavaScript library that can be added to the page when it’s utilized.
The Silverlight implementation of the client object model consists of two assemblies found in the [..]14TEMPLATELAYOUTSClientBin folder: Microsoft.SharePoint.Client.Silverlight.dll (266KB) and Microsoft.SharePoint.Client.Silverlight.Runtime.dll (142KB). These files are also available as a redistributable package from Microsoft. To use the client object model in Silverlight applications, both assemblies must be loaded at runtime. Unfortunately, including these two assemblies means your Silverlight application is going to be bloated by more than 400KB. However, they’ll be compressed down to about 140KB in the .xap file.
The additional content in the .xap file might present a challenge for some developers. The Silverlight Web Part that Microsoft includes with SharePoint 2010 has a built-in timer that cancels any .xap download if it takes longer than five seconds. One workaround is to simply create your own Web Part that doesn’t include this timer.
A common practice followed by many Silverlight developers is to load the application as quickly as possible so that the user sees something, even if it’s another notification that the application is downloading the additional resources it needs. The same approach can be adopted in applications that leverage the Silverlight client object model. Microsoft includes an .xap file that contains the Silverlight client object model in the same location on the server where the two aforementioned assemblies are found. After initially loading, your custom application can download the client object model on demand and load the necessary assemblies.
To implement this approach, first add the two references to the client object model assemblies to your project in Visual Studio 2010 so that you can develop and compile your project. However, change the Copy Local property of the two references from True to False, as shown in Figure 1, so they aren’t included in the main application’s .xap file.
Figure 1: Changing the Copy Local property from True to False
The next step is to add some code to your application that will download the Silverlight client object model. This is best done using a helper class like the one in Listing 1. The ClientOMHelper class downloads the .xap file that contains both assemblies, then extracts and loads the assemblies. When both assemblies have been extracted and loaded, the class tells the application that the client object model is now available.
To utilize the ClientOMHelper class, you can use code like that in Listing 2. This code calls the DownloadAndLoadClientObjectModelAsync() method, then listens for the ClientOmLoaded event, which indicates the client object model has been loaded.
One added benefit to this approach is that the browser will cache the .xap file containing the client object model, no matter how many custom applications download it or how often they’re updated. Therefore, this approach could theoretically pay dividends over and over. In a real application, you probably wouldn’t want the user to have to click a button to download the client object model. I’ve found that it’s best to create some sort of loading animation using a control like the IsBusyIndicator control included in the Silverlight 4 Toolkit.
Technique 2: Make Custom Applications Aware of SharePoint’s Heath Status
Over the years more and more applications are being integrated into SharePoint. SharePoint 2010 makes this external integration even easier with features like the client object model and the new OData service (ListData.svc). All these requests can quickly start to put a burden on the SharePoint servers. In SharePoint 2010, Microsoft added a small indicator to each response telling the caller how the server is doing. This indicator tells the calling application how busy the server is using a scale from 0 to 10, where 0 is not very busy. When SharePoint’s health score reaches 10, it starts to throttle requests and gives priority to HTTP POST requests over GET requests.
The indicator is sent in the form of an HTTP header. The custom header, X-SharePointServerHealth, is determined from three performance counters on the server:
Memory – Available Bytes
ASP.NET – Requests Queued
ASP.NET – Request Wait Time
Some applications are already using this server health indicator to adjust how frequently they synchronize with SharePoint. For instance, SharePoint Workspace 2010 uses this indicator in its adaptive updates. When SharePoint Workspace starts to see that the SharePoint servers with which it’s synchronizing start to encounter some stress, it increases the time between its synchronization requests to the servers, reducing some of the overhead.
Developers can use this server health indicator in their custom Silverlight applications as well. It can serve as a throttling mechanism that at best reduces the number of calls or at least notifies the user that the custom application might take longer to respond to queries or commands.
I find it easiest to create a helper class to do most of the plumbing, as the code in Listing 3 shows. In the ServerHealthHelper class, the UpdateServerHealthScoreAsync() method issues a request to a known file that’s one of the smallest files on the SharePoint server: /_layouts/blank.htm. When this method receives a response, it looks at the headers, converts the X-SharePointHealthScore to an integer, and raises an event that indicates the score has been updated. The SharePointHealthScore property implements the INotifyPropertyChanged interface (via the custom reusable NotfiableObject class the helper class inherits from) so it can be used as a bindable property.
To utilize the ServerHealthHelper class, you can use code like that in Listing 4. This code simply subscribes to the SharePointServerScoreUpdated event and calls the asynchronous UpdateServerHealthScoreAsync() method.
When testing, it can be challenging to simulate a heavy enough load on the SharePoint servers to impact this number. Therefore, Microsoft provides a way to hard-code the number you want returned while in development. This is done by going to the registry subkey HKEY_LOCAL_MACHINESOFTWAREMicrosoftShared ToolsWeb Server Extensions14.0WSS and adding a new DWORD entry named ServerHealthScore. The value you set will be returned in the health score.
Make Your Custom Applications Load Faster
Microsoft introduced multiple ways to interact with SharePoint 2010 when your custom code isn’t running on the server but rather running from within the browser or on a remote server. For Silverlight, Microsoft introduced an implementation of the client object model that developers can use in custom applications when interacting with SharePoint sites. The client object model is quite full featured; thus, the required assemblies can add to the total file size of the custom application’s .xap file. To prevent performance problems due to the increase in size, you can have your custom applications download the client object model on demand and only when needed to provide a better user experience. In addition, your custom applications can monitor a custom HTTP header that SharePoint adds to each response, telling the calling application the current health status of the server. Custom applications can use this number to provide feedback to the users or to adjust the frequency of how often they issue additional calls to the server.
Listing 1: ClientOMHelper class
public class ClientOMHelper {#region ClientOmLoaded event plumbing
public delegate void ClientOmLoadedEventHandler();
public event ClientOmLoadedEventHandler ClientOmLoaded;
protected virtual void OnClientOmLoaded(EventArgs args) {
if (ClientOmLoaded != null)
ClientOmLoaded();
}
#endregion
public void DownloadAndLoadClientObjectModelAsync() {
WebClient webClient = new WebClient();
// After downloading the .xap file containing the client object model, load the assemblies.
webClient.OpenReadCompleted += (sender, args) => {
StreamResourceInfo zipPackageInfo =
new StreamResourceInfo(args.Result, "application/binary");
Stream assemblyStream;
AssemblyPart assemblyPart;
// Load the runtime.
assemblyStream = Application.GetResourceStream(zipPackageInfo,
new Uri("Microsoft.SharePoint.Client.Silverlight.Runtime.dll", UriKind.Relative)).Stream;
assemblyPart = new AssemblyPart();
Assembly runtime = assemblyPart.Load(assemblyStream);
// Load the client object model.
assemblyStream = Application.GetResourceStream(zipPackageInfo,
new Uri("Microsoft.SharePoint.Client.Silverlight.dll", UriKind.Relative)).Stream;
assemblyPart = new AssemblyPart();
Assembly clientOM = assemblyPart.Load(assemblyStream);
// Notify the application about the client object model's availability.
OnClientOmLoaded(new EventArgs());
};
// Load the .xap file asynchronously.
webClient.OpenReadAsync(new Uri("/_layouts/ClientBin/Microsoft.SharePoint.Client.xap",
UriKind.Relative));
}
}
Listing 2: Code that calls the DownloadAndLoadClientObjectModelAsync() method
private void OnDownloadClientOm(object sender, RoutedEventArgs e) {ClientOMHelper clomHelper = new ClientOMHelper();
clomHelper.ClientOmLoaded += () => {
MessageBox.Show("ClientOM Downloaded");
};
clomHelper.DownloadAndLoadClientObjectModelAsync();
}
Listing 3: ServerHealthHelper class
public class ServerHealthHelper : NotifiableObject {#region SharePointServerScoreUpdated event plumbing
public delegate void SharePointServerScoreUpdatedEventHandler();
public event SharePointServerScoreUpdatedEventHandler SharePointServerScoreUpdated;
protected virtual void OnSharePointServerScoreUpdated(EventArgs args) {
if (SharePointServerScoreUpdated != null)
SharePointServerScoreUpdated();
}
#endregion
private int _sharePointHealthScore;
private const string SharePointHealthScorePropertyName =
"SharePointHealthScore"; public int SharePointHealthScore {
get { return _sharePointHealthScore; }
set {
if (_sharePointHealthScore != value) {
_sharePointHealthScore = value;
RaisePropertyChanged(SharePointHealthScorePropertyName);
}
}
}
public void UpdateServerHealthScoreAsync() {
// Prep any requests to go through Silverlight and not the browser.
bool httpResult = WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
// Issue a request to the tiny out-of-the-box file served up by SharePoint.
HttpWebRequest request =
WebRequest.Create(new Uri("http://intranet.wingtip.com/_layouts/blank.htm",
UriKind.Absolute)) as HttpWebRequest;
request.Method = "GET";
request.BeginGetResponse((asyncResult) => {
// Get the request’s response.
HttpWebResponse response = request.EndGetResponse(asyncResult) as HttpWebResponse;
// Update the health score.
SharePointHealthScore = Int32.Parse(response.Headers["X-SharePointHealthScore"]);
OnSharePointServerScoreUpdated(new EventArgs());
}, null);
}
}
Listing 4: Code that calls the UpdateServerHealthScoreAsync() method
SynchronizationContext _syncContext;
public MainPage() {
InitializeComponent();
_syncContext = SynchronizationContext.Current;
}
private void OnUpdateServerHealth(object sender, RoutedEventArgs e) {
ServerHealthHelper serverHelper = new ServerHealthHelper();
serverHelper.SharePointServerScoreUpdated += () => {
_syncContext.Post((data) => {
MessageBox.Show("Server health updated");
ServerHealthTextBlock.Text = serverHelper.SharePointHealthScore.ToString();
}, null);
};
serverHelper.UpdateServerHealthScoreAsync();
}
About the Author
You May Also Like