Creating a SharePoint Web Part that hosts a Silverlight 2 beta 2 application
Article written by: Karine Bosch (SharePoint Consultant at U2U), Kevin DeRudder (Silverlight Consultant and Trainer at U2U)
Introduction
In the beginning of this year I started working on the Silverlight BluePrint for SharePoint together
with my mentor Patrick Tisseghem who suddenly passed away recently. I dedicate this article to him.
With Silverlight you can really light up your SharePoint user interfaces. It can be used in different
SharePoint domains like web parts, application pages, navigation, content management, custom fields, editor parts, etc.
In this tutorial I’m going to explain how you can host a Silverlight 2 beta 2 application from within a
SharePoint Web Part. The Web Part will pass the URL of the SharePoint site together with the name of the list
for which the Silverlight application will show the data. The retrieval of the data will be done by the
Silverlight application using the HttpWebRequest technique for calling the SharePoint web services. As
the SharePoint web services return a chunk of XML the XML will be handled by using LINQ for XML. The
data will be bound to the Silverlight controls.
When the Web Part loads the complete list of AdventureWorks products will be loaded. In the Topic box you can
enter a part of a product number and click the Search button. Based on that search string a restricted list of
products will be returned. You can select a product from the list to view its details.
You can also modify its list price if you want and update the AdventureWorks products list. This will again use
the HttpWebRequest technique to call the UpdateListItems method of the Lists.asmx web service.
The Silverlight application will be deployed as an embedded resource of the SharePoint Web Part.
You can download the source code of this tutorial from here.
The zip contains the sources for the SharePoint Web Part and the Silverlight application, together with two SharePoint
list templates for the AdventureWorks Products and the AdventureWorks Product Pictures.
The sample code works for Silveright 2 beta 2 and will shortly be updated for working with the RTM version of Silverlight.
Creating the Web Part
I created the sample Web Part using the Visual Studio 2008 extenstions for WSS 3.0. It creates the necessary
infrastructure for the deployment afterwards.
Before you can start coding you need to add a reference to the System.Web.Silverlight.dll (version 2.0.5.0)
and to the System.Web.Extensions.dll (version 3.5.0.0).
Open the AdventureWorksProducts.cs file in Code View and declare a class level variable for the silverlight
control that will host the silverlight application:
System.Web.UI.SilverlightControls.Silverlight silverlightControl = null;
Override the OnLoad method in which you need to check whether a script manager already exists on the page.
If not, you have to add one at the first position of the Controls collection:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Script manager instance may appear only once on a page
ScriptManager scriptManager = ScriptManager.GetCurrent(this.Page);
if (scriptManager == null)
{
scriptManager = new ScriptManager();
this.Controls.AddAt(0, scriptManager);
}
}
Within the CreateChildControls method that is already prepared for you, you have to instantiate the
Silverlight control and set its properties. One of these properties is the Source property. The setting depends
on where you are going to deploy your silverlight application. In this sample I will deploy the Silverlight
application as an embedded resource of my SharePoint Web Part so the Source property has to point to that
location. I will come back to this property once the Silverlight application is created. Don’t forget to specify
a Width and Height, otherwise the Silverlight application will not be visible. Another property
that is worth mentioning is the InitParameters property. I use this property to pass the URL to the SharePoint
site and the name from the list that needs to be queried. The retrieval of the data, and thus the communication with
SharePoint, will be done from within the Silverlight application. Notice that this property is a string. You can
pass data in it by respecting the syntax name1=value1,name2=value2,… Within the Silverlight application this
data will be unpacked in a dictionary. But more on this in next section.
Don’t forget to add the Silverlight control to the Controls collection of the Web Part.
protected override void CreateChildControls()
{
base.CreateChildControls();
// instantiation of the silverlight control
silverlightControl =
new System.Web.UI.SilverlightControls.Silverlight();
silverlightControl.ID = "SLAdventureWorks";
silverlightControl.MinimumVersion = "2.0.30523";
silverlightControl.Width = new Unit(750);
silverlightControl.Height = new Unit(600);
silverlightControl.Source = null;
// parameters passed are the the URL of the SharePoint site and the
// name of the list that contains the AdventureWorks products
silverlightControl.InitParameters =
"siteurl=" + SPContext.Current.Web.Url
+ ",listname=AdventureWorks Products";
this.Controls.Add(silverlightControl);
}
Now override the RenderContents method to render the Silverlight control:
protected override void RenderContents(HtmlTextWriter writer)
{
if (silverlightControl != null)
silverlightControl.RenderControl(writer);
}
That’s all for the Web Part code.
Creating the Silverlight application
The development of the Silverlight application is a bit harder, certainly if you are a normal ASP.NET
or SharePoint developer. Add a new project to the solution using the Silverlight Application
template. This template comes with a standard Page.xaml and an App.xaml. The App.xaml
file inherits from the System.Windows.Application class which represents the Silverlight application
while the Page represents a Silverlight control.
Before designing the Silverlight controls you will have to retrieve the parameters that have been passed
by the SharePoint Web Part. Open the App.xaml.cs file and locate the Application_Startup
method. The second incoming argument is of type StartUpEventArgs and contains in its InitParams
property the data you passed in via the Web Part. At this side of the Silverlight application it is a
dictionary of key-value pairs.
To be able to use this data from within the Silverlight application you have to pass the data to constructor
of the Page control.
private void Application_Startup(object sender, StartupEventArgs e)
{
string siteUrl = null;
string listName = null;
if (e.InitParams != null && e.InitParams.Count == 2)
{
siteUrl = e.InitParams["siteurl"];
listName = e.InitParams["listname"];
}
// Load the main control
this.RootVisual = new Page(siteUrl, listName);
}
Open the Page.xaml.cs code behind file and modify the constructor:
public Page(string siteUrl, string listName)
{
}
The Silverlight application also contains a class that defines the Product object. The different text
boxes in the Silverlight controls will be bound to the properties of this type of objects. The class
inherits from INotifyPropertyChanged, to enable the object to be notified when the data changes.
This interface requires only one thing: that the class has an event of type PropertyChangedEventHandler,
named PropertyChanged. This event is fired when any property that is tied to a UI control is changed.
In this case the Product object will be notified when the user tries to update the product price.
public class Product : INotifyPropertyChanged
{
public int Id { get; set; }
public int ProductId { get; set; }
public string ProductNumber { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
private double listPrice;
public double ListPrice
{
get { retur n listPrice; }
set { listPrice = value; OnPropertyChanged("ListPrice"); }
}
public string Color { get; set; }
public string Size { get; set; }
public string Weight { get; set; }
public string ThumbNail { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
Now it is time to design the Silverlight controls.
The Silverlight application consists of 3 controls: the main control that displays a search box with a
button and a ListBox for displaying the list of AdventureWorks products. The second control displays
the details of the product selected from the list. In the details control you will also be able to update
the product price and save your changes to the SharePoint site. The third control is a rotator control that
is only shown when data is retrieved from the SharePoint web service with the purpose to show the user that
the application is busy.
The XAML for the main Silverlight control is not much:
<UserControl x:Class="SL.XAML.AdventureWorksProducts.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:SL.XAML.AdventureWorksProducts"
Width="750" Height="600">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Style="{StaticResource Header}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Border Style="{StaticResource TitleBorder}">
<TextBlock Text="AdventureWorks Product Search"
Style="{StaticResource TitleText}" />
</Border>
<TextBox x:Name="SearchTextBox" Text="Topic..."
FontFamily="Trebuchet MS" Grid.Column="1"
FontSize="12" Padding="1,3,1,1"/>
<Button x:Name="SearchButton"
Content="Search"
Click="SearchButton_Click"
Style="{StaticResource SearchButton}" />
</Grid>
<ListBox x:Name="ProductsList" Style="{StaticResource ProductsList}"
SelectionChanged="ProductsList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- Product Thumbnail -->
<Image Source="{Binding ThumbNail}"
Style="{StaticResource ThumbNailPreview}" />
<!-- Product Number-->
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding ProductNumber}"
Margin="5"
Style="{StaticResource TitleBlock}"
FontWeight="Bold" />
<TextBlock Text="{Binding ProductName}"
Margin="5"
Style="{StaticResource TitleBlock}" />
</StackPanel>
<!-- List Price-->
<TextBlock Text="{Binding ListPrice, Mode=TwoWay}"
Margin="5"
Style="{StaticResource PriceBlock}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock x:Name="MessageTextBlock" Grid.RowSpan="2"
Visibility="Collapsed" Text="No items found" />
<my:ProductDetails x:Name="DetailsView" Grid.RowSpan="2"
Visibility="Collapsed" />
<my:Rotator x:Name="LoadRotator" Grid.RowSpan="2"
Visibility="Collapsed" />
</Grid>
</UserControl>
The main grid contains two rows: one for the search text box and button, and one for the products list box.
The first row of the main grid contains a second grid with a border and 3 columns: one for the Title, one
for the search text and one for the search button. It applies a style that is defined as resource in the
App.xaml file defining the margin:
<Style x:Key="Header" TargetType="Grid">
<Setter Property="Margin" Value="7" />
<Setter Property="Grid.Row" Value="0"/>
</Style<
Also the border, the title and the button apply a style that is defined as resource in the App.xaml.
The button has an event handler attached to it, which will be explained later in the article.
The second row of the main grid contains the list box for the AdventureWorks products. The list box
contains a number of stack panels for the layout of the product data. Besides styles the individual
text boxes also bind to properties of the product object. For the list price text block, the binding
mode is set to TwoWay.
At the end of the Page control you see the XAML for the ProductDetails control. Initially this control
is hidden. It is only when the user selects a product that this controls is populated with the selected product
and that the controls is made visible.
The rotator control will only be shown when data is retrieved from the SharePoint web service.
The ProductDetails control contains a main grid that contains a rectangle and border element defining the
background. The controls displaying the details of the selected product are drawn within grids and stack panels
for a good positioning on the control. Most of the controls apply styles defined in the App.xaml. The text blocks
displaying the data are bound to a property of the product object. The XAML for the control looks as follows:
<UserControl x:Class="SL.XAML.AdventureWorksProducts.ProductDetails"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="250">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Opacity="0.765" Fill="#FF8A8A8A" />
<Border CornerRadius="20" Background="#FF5C7590" Width="600" Height="250">
<StackPanel Margin="5, 7, 0, 5">
<!-- Top Right Close Button -->
<Button Content="Close" Style="{StaticResource CloseButton}"
Click="Button_Click"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Left: Large Picture -->
<Image Source="{Binding ThumbNail}"
Style="{StaticResource DetailsThumbNailPreview}"/>
<!-- Center: Product Details -->
<StackPanel Orientation="Vertical" Grid.Column="1" >
<TextBlock Text="Product number:"
Style="{StaticResource DetailBlock}"
FontWeight="Bold"
Height="30"/>
<TextBlock Text="Product name:"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="Color:"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="Size:"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="Weight:"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="List price in $:"
Style="{StaticResource DetailBlock}" Height="30" />
<TextBlock Text="Description:"
Style="{StaticResource DetailBlock}" />
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Column="2" >
<TextBlock Text="{Binding ProductNumber}"
Style="{StaticResource DetailBlock}" FontWeight="Bold"
Height="30"/>
<TextBlock Text="{Binding ProductName}"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="{Binding Color}"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="{Binding Size}"
Style="{StaticResource DetailBlock}" />
<TextBlock Text="{Binding Weight}"
Style="{StaticResource DetailBlock}" />
<TextBlock x:Name="ListPriceTextBlock"
Text="{Binding ListPrice, Mode=TwoWay }"
Style="{StaticResource DetailBlock}"
Height="30" />
<TextBox x:Name="ListPriceTextBox"
Text=""
Width="200"
Visibility="Collapsed"
HorizontalAlignment="Left" />
</StackPanel>
<TextBlock Text="{Binding Description}"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1"
Style="{StaticResource DetailBlock}"
FontStyle="Italic" />
</Grid>
<Button x:Name="EditButton"
Width="80"
Content="Edit Price"
HorizontalAlignment="Right"
Margin="20"
Click="EditButton_Click"/>
</StackPanel>
</Border>
</Grid>
</UserControl>
Now to the code behind of both Silverlight controls. When the Silverlight application loads, all
products of the AdventureWorks Products list are retrieved using the HttpWebRequest for
calling the GetListItems method of the Lists.asmx web service.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(siteUrl +
"/_vti_bin/Lists.asmx", UriKind.Absolute));
request.Method = "POST";
request.ContentType = "application/soap+xml; charset=utf-8";
request.Headers["ClientType"] = "Silverlight";
request.BeginGetRequestStream(new AsyncCallback(RequestCallback), request);
The call to the Request is asynchronous an thus needs a callback method. In that callback method the soap
envelope is build. If you don’t know the correct envelope of a web service method you have to browse to
that web service and check the method of your choice. You will get a page with the necessary information of
the envelope.
The envelope contains the necessary XML to execute the GetListItems method on the AdventureWorks Products
list passed in via the Web Part. If the user entered a string in the Search text box a CAML query is build to
retrieve only the products containing that string. The body of the request is retrieved by calling the
EndGetRequestStream method of the HttpWebRequest object. Then the envelope is written to the body
and the request is send back to the server.
Also the Response is asynchronous so you need to define another callback method for HttpWebRequest
when coming back from the server.
private void RequestCallback(IAsyncResult asyncResult)
{
try
{
string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
<soap12:Body>
<GetListItems xmlns=""http://schemas.microsoft.com/sharepoint/soap/"">
<listName>{0}</listName>
<query>
<Query xmlns="""">{1}
<OrderBy><FieldRef Name=""Title"" /></OrderBy>
</Query>
</query>
<viewFields><ViewFields xmlns="""">
<FieldRef Name=""ID"" />
<FieldRef Name=""Title"" />
<FieldRef Name=""ProductName"" />
<FieldRef Name=""ListPrice"" />
<FieldRef Name=""Thumbnail"" />
<FieldRef Name=""Color"" />
<FieldRef Name=""Weight"" />
<FieldRef Name=""Size"" />
<FieldRef Name=""Description"" />
</ViewFields>
</viewFields>
<queryOptions>
<QueryOptions xmlns="""">
<IncludeMandatoryColumns>False</IncludeMandatoryColumns>
</QueryOptions>
</queryOptions>
</GetListItems>
</soap12:Body>
</soap12:Envelope>";
string query = string.Empty;
if (!string.IsNullOrEmpty(searchstring) && searchstring != "Topic...")
{
query = "<Where><Contains><FieldRef Name=\"Title\" />"
+ "<Value Type=\"Text\">{0}</Value></Contains></Where>";
query = string.Format(query, searchstring);
}
envelope = string.Format(envelope, listName, query);
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
body = request.EndGetRequestStream(asyncResult);
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] formBytes = encoding.GetBytes(envelope);
body.Write(formBytes, 0, formBytes.Length);
body.Close();
request.BeginGetResponse(new AsyncCallback(ResponseCallback),
request);
}
catch (WebException ex)
{
string s = ex.Message;
}
}
Both the Request and the Response are started asynchronously on another thread.
When the response comes back from the server, it is not on the worker thread. So the data is
stored in a class level variable and the ProcessResponse method on the worker thread is
called.
private void ResponseCallback(IAsyncResult asyncResult)
{
LoadRotator.Visibility = Visibility.Collapsed;
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse response =
(HttpWebResponse)request.EndGetResponse(asyncResult);
if (response.StatusCode == HttpStatusCode.OK)
{
products = new ObservableCollection();
Stream content = response.GetResponseStream();
using (StreamReader reader = new StreamReader(content))
{
responsestring = reader.ReadToEnd();
}
try
{
this.Dispatcher.BeginInvoke(ProcessResponse);
}
catch { }
}
else
{
MessageTextBlock.Visibility = Visibility.Visible;
}
}
The ProcessResponse method parses the XML into a collection of Product objects using
LINQ to XML. For this purpose a reference to the System.Xml.Linq assembly is added. All
products are added to the Products collection and then assigned to the ItemSource
property of the products list box in order to enable the data binding.
private void ProcessResponse()
{
LoadRotator.Visibility = Visibility.Collapsed;
products = new ObservableCollection<Product>();
XDocument results = XDocument.Parse(responsestring);
var query = from item in results.Descendants(XName.Get("row",
"#RowsetSchema"))
select new Product()
{
Id = System.Convert.ToInt32(item.Attribute("ows_ID").Value,
CultureInfo.InvariantCulture),
ProductNumber = item.Attribute("ows_Title").Value + "_",
ProductName = item.Attribute("ows_ProductName").Value,
ListPrice =
System.Convert.ToDouble(item.Attribute("ows_ListPrice").Value,
CultureInfo.InvariantCulture),
Description = item.Attribute("ows_Description") != null ?
item.Attribute("ows_Description").Value : string.Empty,
Color = item.Attribute("ows_Color") != null ?
item.Attribute("ows_Color").Value : string.Empty,
Size = item.Attribute("ows_Size") != null ?
item.Attribute("ows_Size").Value : string.Empty,
Weight = item.Attribute("ows_Weight") != null ?
item.Attribute("ows_Weight").Value : string.Empty,
ThumbNail = GetThumbnailUrl(item.Attribute("ows_Thumbnail"))
};
foreach (Product p in query.ToList())
{
products.Add(p);
}
ProductsList.ItemsSource = products;
}
When the user selects a product in the list, a box containing the detailed information is shown
to the user. This is triggered by the SelectionChanged event of the list box. The selected product
is retrieved from the list box and assigned to the ProductDetails control, which is then made visible.
private void ProductsList_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
Product product = (Product)ProductsList.SelectedItem;
if (product != null)
{
DetailsView.DataContext = product;
DetailsView.SiteUrl = siteUrl;
DetailsView.ListName = listName;
DetailsView.Visibility = Visibility.Visible;
}
}
This brings us to the code behind of the ProductDetails control. The control is populated by the main
control. This control contains also an Edit Price button. When the user clicks this button, the list
price text block is changed into a text box in which the user can enter the new price. When the user then
clicks the Update button, the UpdateListItems method of the Lists.asmx web service is called
to save the new list price. The update is also performed by using HttpWebRequest, which runs
asynchronously on different threads.
private void UpdateListPrice()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
new Uri(siteUrl + "/_vti_bin/Lists.asmx", UriKind.Absolute));
request.Method = "POST";
request.ContentType = "application/soap+xml; charset=utf-8";
request.BeginGetRequestStream(new AsyncCallback(RequestCallback),
request);
}
private void RequestCallback(IAsyncResult asyncResult)
{
string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
<soap12:Body>
<UpdateListItems xmlns=""http://schemas.microsoft.com/sharepoint/soap/"">
<listName>{0}</listName>
<updates>{1}</updates>
</UpdateListItems>
</soap12:Body>
</soap12:Envelope>";
string query = string.Empty;
query = @"<Batch PreCalc=""TRUE"" OnError=""Continue"">
<Method ID=""1"" Cmd=""Update"">"
+ "<Field Name=""ID"">{0}</Field><Field Name=""ListPrice"">{1}"
+ "</Field></Method></Batch>";
query = string.Format(query, selectedProduct.Id, newListPriceString);
envelope = string.Format(envelope, listName, query);
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
body = request.EndGetRequestStream(asyncResult);
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] formBytes = encoding.GetBytes(envelope);
body.Write(formBytes, 0, formBytes.Length);
body.Close();
request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
}
private void ResponseCallback(IAsyncResult asyncResult)
{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse response =
(HttpWebResponse)request.EndGetResponse(asyncResult);
Stream content = response.GetResponseStream();
if (response.StatusCode == HttpStatusCode.OK)
{
try
{
this.Dispatcher.BeginInvoke(ProcessResponse);
}
catch { }
}
}
private void ProcessResponse()
{
selectedProduct.ListPrice = newListPrice;
}
Because of the TwoWay binding mode set on the ListPriceTextBlock on the list box,
the price change is propagated to the item in the list box during the execution of the ProcessResponse method.
Build the Silverlight application and copy the resulting .xap file to your SharePoint project.
I copied it to a Resources sub directory.
Deploying the Solution
Now that it comes to the deployment of the SharePoint web part and its Silverlight application, you have to make
some modifications to the Web Part files that contain the metadata for the Web Part.
Set the properties of the embedded silverlight resource. Include the Resources folder and the .xap file
to the Web Part project. In the file properties set the Build action to Embedded Resource.
Build the SharePoint project and open the DLL with a tool like Reflector in order to copy the right namespace of
the embedded resource which is the silverlight application. Return to the Web Part project and open the Web Part
class. Before the namespace declaration you have to add the metadata attribute that enables an embedded resource
in an assembly:
[assembly:
WebResource("SL.AdventureWorksProducts.Resources.SL.XAML.AdventureWorksProducts.xap",
"application/x-silverlight-app")]
namespace SL.AdventureWorksProducts
{
//… Web Part code …
}
In the CreateChildControls method set the source property of the silverlight control to the location of the
silverlight application. You can retrieve this location by using the GetWebResourceUrl method:
silverlightControl.Source =
this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
"SL.AdventureWorksProducts.Resources.SL.XAML.AdventureWorksProducts.xap");
If needed you can modify the .webpart file and the .xml file to set certain Web Part properties.
When working with the Visual Studio 2008 extensions for WSS you can set the Start browser with URL property
of the web part project to the URL of your SharePoint site. This will deploy the Web Part to this URL.
When deployed you can add the Web Part to a web part page. If you don’t yet have configured your SharePoint server
for Silverlight you can read the necessary steps to take in the following section.
Configuring your SharePoint server for Silverlight
Installations to perform
The following is an excerpt of the configuration document of the Silverlight BluePrint for SharePoint.
When you need to develop and host Silverlight 2 applications into SharePoint 2007, you need to do the following
installations and configurations:
-
Have
service pack 1 for WSS 3.0 installed.
-
If you have MOSS 2007 installed, install
service pack 1 for MOSS 2007
-
.NET Framework 3.5 must be installed on the server: the System.Web.Extensions dll version 3.5.0.0 is required server-side.
The redistributable .NET 3.5 framework can be downloaded here.
-
Install SP1 for Visual Studio 2008 (this seems to be a requirement thought it is not described as such).
-
Download and install the
Silverlight Chainer.
-
For the development of Silverlight 2 applications, you need to work with Visual Studio 2008 extended with the
Microsoft Silverlight Tools Beta 2 for Visual Studio 2008. This will also install the Silverlight 2 Beta 2 SDK.
If you get an error when creating a new Silverlight project from within Visual Studio 2008 you have to follow the steps indicated in
this blog post
-
The System.Web.Silverlight.dll must also be placed in the Global Assembly Cache. This dll is located in the
C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Server directory.
-
A new MIME type must be registered at the root of IIS or for each SharePoint IIS Web Application where you want to host
Silverlight applications:
o Extension: .xap
o MIME type: application/x-silverlight-2-b2
-
If you already configured your SharePoint site for Silverlight 2 beta 1 you don’t have to modify the web.config.
-
If you didn’t configure your SharePoint site for Silverlight 2 Beta 1, you need to follow the instructions in next section.
Necessary modifications to the web.config
Following modifications must be done for each SharePoint IIS Web Application where you want to host Silverlight applications.
Before starting to make modifications, make a backup of the existing web.config.
-
Locate the <configuration><configSections> node. Add the following section group:
<sectionGroup name="system.web.extensions"
type="System.Web.Configuration.SystemWebExtensionsSectionGroup,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35">
<sectionGroup name="scripting"
type="System.Web.Configuration.ScriptingSectionGroup,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35">
<section name="scriptResourceHandler"
type="System.Web.Configuration.ScriptingScriptResourceHandlerSection,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"
requirePermission="false"
allowDefinition="MachineToApplication"/>
<sectionGroup name="webServices"
type="System.Web.Configuration.ScriptingWebServicesSectionGroup,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35">
<section name="jsonSerialization"
type="System.Web.Configuration.ScriptingJsonSerializationSection,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"
requirePermission="false"
allowDefinition="Everywhere"/>
<section name="profileService"
type="System.Web.Configuration.ScriptingProfileServiceSection,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"
requirePermission="false"
allowDefinition="MachineToApplication"/>
<section name="authenticationService"
type="System.Web.Configuration.ScriptingAuthenticationServiceSection,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"
requirePermission="false"
allowDefinition="MachineToApplication"/>
<section name="roleService"
type="System.Web.Configuration.ScriptingRoleServiceSection,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"
requirePermission="false"
allowDefinition="MachineToApplication"/>
</sectionGroup>
</sectionGroup>
</sectionGroup>
-
Locate the <configuration><system.web><compilation><assemblies> node and add the following assembly:
<add assembly="System.Web.Silverlight,
Version=2.0.5.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
-
Locate the <configuration><system.web><pages><controls> node and add the following controls:
<add tagPrefix="asp" namespace="System.Web.UI"
assembly="System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
<add tagPrefix="asp"
namespace="System.Web.UI.WebControls"
assembly="System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
Locate the <configuration><system.web><httpHandlers> node and add the following:
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx"
validate="false" type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
<add verb="*" path="*_AppService.axd" validate="false"
type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
<add verb="GET,HEAD" path="ScriptResource.axd"
type="System.Web.Handlers.ScriptResourceHandler,
System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35" validate="false"/>
Locate the <configuration><system.web><httpModules> node and add the following:
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
Add an extra section at the bottom of the
<configurations> node:
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules>
<remove name="ScriptModule"/>
<add name="ScriptModule" preCondition="managedHandler"
type="System.Web.Handlers.ScriptModule,
System.Web.Extensions, Version=3.5.0.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
<handlers>
<remove name="WebServiceHandlerFactory-Integrated"/>
<remove name="ScriptHandlerFactory"/>
<remove name="ScriptHandlerFactoryAppServices"/>
<remove name="ScriptResource"/>
<add name="ScriptHandlerFactory" verb="*" path="*.asmx"
preCondition="integratedMode"
type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
<add name="ScriptHandlerFactoryAppServices" verb="*"
path="*_AppService.axd" preCondition="integratedMode"
type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions,
Version=3.5.0.0,
Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
<add name="ScriptResource" preCondition="integratedMode"
verb="GET,HEAD" path="ScriptResource.axd"
type="System.Web.Handlers.ScriptResourceHandler,
System.Web.Extensions,
Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>
</handlers>
</system.webServer>
Locate the <configuration><runtime><assemblyBinding> node and add an 2 extra dependentAssembly nodes node to it:
<dependentAssembly>
<assemblyIdentity name="System.Web.Extensions"
publicKeyToken="31bf3856ad364e35"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Extensions.Design"
publicKeyToken="31bf3856ad364e35"/>
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</dependentAssembly>
That’s it. Save your web.config and you should be ready to start with Silverlight within SharePoint 2007.
Conclusion
If you want to learn more about Silverlight integration in SharePoint you can download the Silverlight BluePrint
for Sharepoint on the CodePlex workspace.