Tuesday, November 3, 2009

Silverlight 3 + SharePoint: A wrapper web part that makes SharePoint user information available to Silverlight.

A few weeks ago I posted a “quick and dirty” way to integrate a Silverlight application into SharePoint. This was great for basic apps, but has no way of accessing a user’s information. After reviewing a number of products and a bunch of different approaches, I believe I have come up with a solution that works.

Disclaimer: This is mostly code and I haven’t included too many comments. This post assumes you are fairly familiar with Silverlight and SharePoint development. I use WSPBuilder for deploying my SharePoint web parts.

Web Part Code

This can also work with a standard ASP.NET Web Part, but you will need to derive from the System.Web.UI.WebControls.WebParts.WebPart base class and change SPContext references to HttpContext.Current.User.Identity.

Note: Instead of a bunch of crazy string concatenation, you could build all of the objects individually and then set their respective properties. You may also opt to omit or edit the style and script text.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;

namespace Acme.SharePoint.WebPart
{
[Guid("B7BD8441-FA27-43ab-8F92-708094D3FC28")]
public class SilverlightHostWp : Microsoft.SharePoint.WebPartPages.WebPart
{
[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("XAP Path")]
public string XapPath { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Width")]
public string ParentWidth { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Height")]
public string ParentHeight { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), Category("Silverlight Settings"), WebDisplayName("Parent Border Enabled")]
public bool ParentBorderEnabled { get; set; }

public SilverlightHostWp()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void CreateChildControls()
{
try
{
base.CreateChildControls();
if (string.IsNullOrEmpty(XapPath))
{
this.Controls.Add(new LiteralControl() { Text = "Update \"XAP Path\" in Silverlight Settings." });
}
else
{
SPUser user = SPContext.Current.Web.CurrentUser;
string fullName = user.Name.Replace(", ", "_"); //Silverlight expects a comma delimited value. This is parsed back in the Silverlight Appplication_Startup method. See below.
string loginName = user.LoginName.ToLower();
string email = user.Email.ToLower();
string userParams = "FullName=" + fullName + ",LoginName=" + loginName + ",Email=" + email;
if (string.IsNullOrEmpty(ParentHeight))
{
ParentHeight = "100"; //Defaults to 100 since otherwise nothing will be visible.
}
if (string.IsNullOrEmpty(ParentWidth))
{
ParentWidth = "100"; //Defaults to 100 since otherwise nothing will be visible.
}
StringBuilder styleScriptText = new StringBuilder();
styleScriptText.Append("<style type=\"text/css\">" +
"html, body" +
" {" +
" height: 100%;" +
" overflow: auto;" +
" }" +
"body" +
" {" +
" padding: 0;" +
" margin: 0;" +
" }" +
"</style>" +
"<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>");
StringBuilder objectText = new StringBuilder();
objectText.Append(styleScriptText.ToString() +
"<div id=\"silverlightControlHost\" style=\"width:" + ParentWidth + "; height:" + ParentHeight + ";");
if (ParentBorderEnabled)
{
objectText.Append(" border: silver 1px solid; ");
}
objectText.Append("\"><object data=\"data:application/x-silverlight,\" type=\"application/x-silverlight-2\"" +
" width=\"100%\" height=\"100%\">" +
"<param name=\"source\" value=\"" + XapPath + "\" />" +
"<param name=\"onerror\" value=\"onSilverlightError\" />" +
"<param name=\"background\" value=\"white\" />" +
"<param name=\"minRuntimeVersion\" value=\"3.0.40624.0\" />" +
"<param name=\"autoUpgrade\" value=\"true\" />" +
"<param name=\"initParams\" value=\"" + userParams + "\" />" +
"<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='_sl_historyFrame' style='visibility: hidden; height: 0; width: 0; border: 0px'>" +
"</iframe>" +
"</div>");
Literal literal = new Literal()
{
Text = objectText.ToString()
};
this.Controls.Clear();
this.Controls.Add(literal);
}
}
catch (Exception ex)
{
//Handle error…


            }
}
}
}


App.xaml.cs Application_Startup method



private void Application_Startup(object sender, StartupEventArgs e)
{
if (e.InitParams != null)
{
foreach (var item in e.InitParams)
{

if (item.Key == "FullName")
{
Resources.Add(item.Key, item.Value.Replace("_", ", ")); //Silverlight expected InitParams to be a comma delimited value, so this was changed to an underscore in the Web Part code. See above.
}
else
{
Resources.Add(item.Key, item.Value);
}
}
}
this.RootVisual = new UserNameControl();
}


Accessing App resource properties within a User Control



In my case, I have created a TextBox that toggles between a prompt to sign and their full name.



public class UserNameTextBox : TextBox
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (Text == "Click To Sign!")
{
if (App.Current.Resources.Contains("FullName"))
{
Text = App.Current.Resources["FullName"].ToString();
Tag = App.Current.Resources["LoginName"].ToString();
}
}
else
{
Text = "Click To Sign!";
}
}
}


XAML



Include something like the following in your UserControl tag.



xmlns:local="clr-namespace:Acme.SilverlightControls"




Add your control XAML like this.




<local:UserNameTextBox Name="UserName" Text="Click To Sign!" Background="Yellow" Width="200" Height="100"></local:UserNameTextBox>



Screenshots



For simplicity, I have added the XAP file and a Web Part Page to the same Document Library. Your path will need to be a relative or absolute URI otherwise. When you add the web part to the page, you have four properties to pick from. I included a property for Parent Border Enabled so that you can have a visual way to tweak the width and height.



image



When the user clicks, their full name appears.



image

4 comments:

  1. Thanks for the great post - it helped me a lot :)

    On a side note. The way you are using the StringBuilder class does not leverage its full potential. To leverage its benefits you should concatenate all your strings by using append. For example

    var sb = new StringBuilder();

    sb.Append("string1")
    .Append("string2");

    etc.

    Otherwise you are really just using the normal string concatenation and then wrapping it in a StringBuilder which does not avoid the memory overhead.

    ReplyDelete
  2. Very good point! I should update that code when I get a chance. It was a lazy copy and paste wrapped in the append.

    ReplyDelete
  3. Hi john Great Post! Can you please tell me how i can Access Data from my silverlight controls within the webpart code ? for example if i had a textblock saying "Hello World" how can i get to the textblock control and essentially its text from my webpart code that is hosting the silverlight object as you have above ?

    ReplyDelete
  4. This should get you started...
    Interaction between Silverlight and HTML
    http://www.silverlightshow.net/items/Interaction-between-Silverlight-and-HTM.aspx

    ReplyDelete