Creating Custom SharePoint 2010 Field Types and Controls with Visual Studio 2010 and Silverlight 3
Summary: SharePoint Foundation 2010 offers developers the capability to create custom field types and controls when a custom edit or display experience is needed. Using Microsoft Silverlight 3, developers can create rich experiences in SharePoint Server 2010 publishing sites.
Applies to: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio
Provided by: Andrew Connell, Critical Path Training, LLC (SharePoint MVP)
Contents
Introduction to Custom Field Types, Field Controls, and Silverlight 3
Overview of the Custom ATOM Field Type with a Silverlight 3 Edit and Display Experience
Configuring a Development Environment for Silverlight 3
Creating the Silverlight Experience
Creating the Application Business Logic
Testing the Silverlight 3 Application
Creating the Custom Field Type and Control
Creating the Custom Field Control Class and Rendering Template
Deploying and Installing Custom Field Types and Controls
Testing the Custom Field Type, Field Control, and Silverlight 3 Application
Conclusion
Acknowledgements
Additional Resources
Download code from Critical Path Training: SharePoint 2010 Publishing Site Silverlight 3 Custom Field Type and Control sample
Introduction to Custom Field Types, Field Controls, and Silverlight 3
Lists are the fundamental way to store data within SharePoint sites. When creating the schema of a list, either by using list columns or content types, users can select the data type of the column, known as a field type in Microsoft SharePoint 2010. Microsoft SharePoint Foundation 2010 offers various field type options, such as the following popular ones:
Single line of text
Multiple lines of text
Currency
Date and time
Choice
Lookup
Microsoft SharePoint Server 2010 adds field types such as the rich-text Publishing HTML field (used by publishing sites). Although these field types can meet the majority of business needs, they might not address each business requirement. For example, the contact list template contains individual fields for each element in a North American address. Is that really how the data is consumed by end users? Do users want to see an address rendered horizontally in a list or does it make more sense to render it with a few line breaks, as it would appear on the outside of an envelope or package? What about the editing experience? If the address is required, what does that mean? Usually it means that most elements are required (such as street 1, city, state, and postal code for North American mailing addresses), while some are optional (such as street 2, commonly used for suite number, building number, or apartment number). However, what if the address is not required? Is it acceptable to enter just a single value in the street field? No, because that is just part of the address, not the full address.
To help with these complex requirements, SharePoint offers developers the functionality to build custom field types. Custom field types enable you to define the core details of a field type, such as how the data is stored and how other developers can interact with it via the SharePoint API. You can also create a custom field control that is associated with the field type to provide a unique editing and display experience. Custom field controls can be used to include custom client-side validation and a unique editing experience.
Perhaps users want more than the typical HTML or static authoring and display experience when editing content in SharePoint. For these scenarios, you can create rich and engaging experiences by using Microsoft Silverlight.
This article describes the process of creating a custom Silverlight 3 control and testing it outside SharePoint. Then it describes the process of creating a custom field type and custom field control that will store the data used by the field type and present a unique, rich, and engaging user experience both in display mode and editing mode.
Overview of the Custom ATOM Field Type with a Silverlight 3 Edit and Display Experience
In this article, the field type that you create will display the contents of an ATOM feed. The display experience renders the contents of the feed to the user without authoring controls.
However, in edit mode the user is presented with a button that, when clicked, triggers a modal dialog box to appear in which he or she can enter the URL of the ATOM feed and the template to use to render the control.
The user can choose from three templates that are used to define how to render the feed contents on the site. One displays only the title, one displays the title and post publication date, and one displays the title, publication date, and an excerpt of the feed's contents.
Creating a custom Silverlight 3 application, custom field type, and custom field control, and then deploying and integrating them into an existing or new SharePoint site, is a lot of work. This article breaks down the work into multiple tasks so that troubleshooting is easier. First, you create the Silverlight 3 application and test it outside SharePoint.
Configuring a Development Environment for Silverlight 3
There are a few things that you must install to configure your development environment. First, install the Silverlight 3 runtime, also known as the browser plug-in. You can obtain this from the Microsoft Silverlight site's Get Started page.
Next, you must get the tools to develop a Silverlight application. Microsoft provides two development tools for this purpose:
Microsoft Expression Blend 3 Expression Blend 3 is primarily used to create rich user interface (UI) experiences. Although not required, it makes it easier to create animations and other UI elements with a designer instead of by writing the XAML by hand. To download a trial version of Expression Blend 3, see Microsoft Expression Blend 3 + SketchFlow Trial.
Microsoft Visual Studio 2010 Visual Studio contains a designer that you can use to place controls in a design surface.
Conveniently for developers, Microsoft uses the same project and solution files in Expression Blend that are used in Visual Studio. This enables you to easily switch from Expression Blend to Visual Studio when building custom applications.
Note
For more information about configuring your environment for Silverlight 3 applications, see the Getting Started with Silverlight Development video on Microsoft Silverlight.
For the application described in this article, Expression Blend 3 is not required but can be used to create the UI. However, only the XAML is shown in the code examples to create the application.
Creating the Silverlight Experience
The first step is to create the Silverlight 3 application. If you can, try to build the Silverlight 3 application completely outside a SharePoint environment. This can make the development and testing process quicker and easier. The application defined in this article has only two integration points with SharePoint, as follows:
Persisting two values selected by the user—the URL of the ATOM feed and the selected template to use in rendering—and using these values to render the specified feed in the specified template in presentation mode.
Determining the current mode of the page—presentation or edit.
Thankfully, you can simulate both of these integration points outside of SharePoint. This is because the Silverlight application does not talk directly to SharePoint. Instead, when launched, it communicates by using hidden form fields and an initialization string. You can simulate this in a test page, which can simplify your development effort.
The application that you will create has two modes: presentation mode and edit mode. Figure 1 shows how the application will appear in presentation mode.
Figure 1. Silverlight application in presentation mode
Figure 2 and Figure 3 show how the application will appear in edit mode. Notice that when in edit mode an additional button is shown above the rendered ATOM feed list (or, when no URL is specified, a rotating ATOM icon is displayed). Clicking the button opens a modal dialog window that enables the user to enter the URL and display template. Clicking OK closes the dialog box and refreshes the application with the new settings.
Figure 2. Silverlight application in edit mode with button
Figure 3. Silverlight application in edit mode with ATOM Feed Edit Dialog
Creating and Configuring the Project
First, create an application. Start by creating a project in Visual Studio 2010, expand the Visual C# node, expand Silverlight, and then select the Silverlight Application project template. Name the template ATOM Field. Visual Studio displays a prompt after creating the project, asking whether to create an additional web project to host the Silverlight application. Create the additional web project, and ensure that the project type is ASP.NET Web Site.
When creating the project, you must make a few additional configuration changes to the project. Right-click the ATOM Field project in the Solution Explorer tool window, and then click Properties. On the Silverlight tab, set the following properties, leaving the rest with their default values:
Assembly Name: ATOMField
Default Namespace: MSDN.SharePoint.Samples.AtomField
Xap File Name: SharePoint.AtomField.xap
Initializing the Application
Next, you set up the application initialization and define some global variables in the code-behind file of the App.xaml file. Right-click App.xaml, and then click View Code. Update the namespace to match the namespace listed previously. Also, add three application-scoped static fields to the App class. These are used to determine whether the application is in edit mode or presentation mode, and to provide the names of the two HTML input elements on the page that it will use to communicate with SharePoint. This is shown in the following code.
namespace MSDN.SharePoint.Samples.AtomField
{
public partial class App : Application
{
// Application-scoped fields.
public static bool IsEditMode = true;
public static HtmlElement FeedElement = null;
public static HtmlElement TemplateElement = null;
public App()
{
In the Application_Startup method, add the following code that will extract the values from the initialization parameters passed in by the Silverlight control on the page and the hidden HTML elements on the page. This is shown in the following code.
private void Application_Startup(object sender, StartupEventArgs e)
{
// Initialize the input parameters.
if (e.InitParams != null && e.InitParams.Count > 0) {
// Get the Feed Element ID.
if (e.InitParams.ContainsKey("fid") && !string.IsNullOrEmpty(e.InitParams["fid"]))
FeedElement = HtmlPage.Document.GetElementById(e.InitParams["fid"]);
// Get the Template Element ID.
if (e.InitParams.ContainsKey("tid") && !string.IsNullOrEmpty(e.InitParams["tid"]))
TemplateElement = HtmlPage.Document.GetElementById(e.InitParams["tid"]);
// Get the Enabled state.
if (e.InitParams.ContainsKey("enable"))
Boolean.TryParse(e.InitParams["enable"], out IsEditMode);
}
this.RootVisual = new MainPage();
}
Now that you have completed the application initialization, you can focus on the application itself. To create a more modular application, you must create two additional XAML files. One file will hold a rotating ATOM icon that is displayed when no ATOM feed is selected or when the application is retrieving the ATOM feed from the remote source, and one file will be the modal dialog box in edit mode.
Creating the Rotating ATOM Icon Control
Right-click the ATOM Field project, click Add, and then click New Item. Select the Silverlight User Control template and name it AtomRotator.xaml. Before creating the UI for this control, you must add an ATOM icon to the project. Create a folder named Images in the project, add an ATOM icon to it (found in the associated SharePoint 2010 Silverlight 3 Publishing Field Type sample), and set the build action of this file to Resource, as shown in Figure 4.
Figure 4. Build action set to Resource
Add the following XAML to the AtomRotator.xaml file. This does two things:
Adds an animation that constantly rotates the image. This is found in the <UserControl.Resources /> section.
Adds the UI within a <Grid /> section for the image, centered horizontally and vertically in a <Border /> control.
<UserControl
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="MSDN.SharePoint.Samples.AtomField.AtomRotator"
d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<Storyboard x:Name="RotateYStoryboard">
<DoubleAnimation Storyboard.TargetName="rotation"
Storyboard.TargetProperty="RotationY"
From="0.0" To="360.0" Duration="0:0:10" RepeatBehavior="Forever" />
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Width="200" Height="200">
<Border x:Name="RotatorBorder" Margin="0" BorderThickness="0">
<Image x:Name="AtomImage" Margin="0" Source="Images/ATOM3D.png"
Stretch="Fill" HorizontalAlignment="Center" VerticalAlignment="Center"
Opacity="0.5" Width="200" Height="200">
<Image.Projection>
<PlaneProjection x:Name="rotation" />
</Image.Projection>
</Image>
</Border>
</Grid>
</UserControl>
Although the animation is created, you must tell the application when to start the animation. In this case, when this control is displayed, the animation should start immediately. To do this, get a reference to the storyboard that contains the animation definition and start it when the control loads. Right-click the file, click View Code, and then add the following code to the class constructor.
public partial class AtomRotator : UserControl
{
public AtomRotator()
{
InitializeComponent();
Storyboard sb = this.FindName("RotateYStoryboard") as Storyboard;
if (sb != null)
sb.Begin();
}
}
Save all changes to these files.
Creating the Edit Modal Dialog Box
The next step is to create the modal dialog box that is shown in edit mode. To do this, add a new Silverlight Child Window item to the project named AtomEditDialog.xaml. This control must contain a text box for the URL of the ATOM feed and a radio button list of the rendering options that are available to the user. Add the following markup to the AtomEditDialog.xaml file and save your changes. Notice that you do not need to write any managed code to write for this control because the hosting control does all the work.
<basics:ChildWindow
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:local="clr-namespace:MSDN.SharePoint.Samples.AtomField"
x:Class="MSDN.SharePoint.Samples.AtomField.AtomEditDialog"
Title="ATOM Feed Edit Dialog">
<Grid x:Name="LayoutRoot" Background="White" VerticalAlignment="Top"
Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<!-- First row of the form. -->
<TextBlock x:Name="UrlTextBlock" Text="Enter URL:" TextWrapping="Wrap"/>
<TextBox x:Name="UrlTextBox" Margin="10,2" TextWrapping="Wrap"
Grid.Column="1"/>
<!-- Second row of the form. -->
<StackPanel Margin="10,2" Grid.Column="1" Grid.Row="1">
<RadioButton x:Name="TitleOnlyRadioButton" Content="Title Only"
IsChecked="True" ToolTipService.ToolTip="Render only the ATOM feed's
post title." />
<RadioButton x:Name="TitleAndDateRadioButton" Content="Title and
Publication Date" ToolTipService.ToolTip="Render the ATOM feed's post
title and publication date." />
<RadioButton x:Name="TitleAndBodyRadioButton" Content="Title,
Publication Date and Body" ToolTipService.ToolTip="Render the ATOM
feed's post title, publication date and body." />
<TextBlock x:Name="MessageTextBlock" Text="TextBlock"
TextWrapping="Wrap" Visibility="Collapsed" Foreground="Red"/>
</StackPanel>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click"
Width="75" Height="23" HorizontalAlignment="Right" Grid.Row="2"
Grid.ColumnSpan="3" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,0,79,0" Grid.Row="2"
Grid.ColumnSpan="3" />
</Grid>
</basics:ChildWindow>
Now that you have created the two dependent controls, you can move on to the main application.
Creating the Application User Interface
Silverlight 3 applications initially show the MainPage.xaml control when the application starts. Earlier versions of Silverlight used the Page.xaml file. This MainPage.xaml file, and its associated code-behind, are where most of the work happens.
The first thing that you have to do is modify the opening <UserControl /> element that defines the class, a reference to the project's namespace (so that the rotator control can be referenced in XAML), and the dimensions of the file.
<UserControl
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MSDN.SharePoint.Samples.AtomField"
x:Class="MSDN.SharePoint.Samples.AtomField.MainPage"
Width="500" Height="500" mc:Ignorable="d">
Create the Core User Interface Controls
Next, you add the UI for the control. You use a <StackPanel /> element because all controls will render in order as they are declared vertically.
Within this control, add the following elements:
A Button that is displayed in edit mode and used to open the model dialog box.
A TextBlock that is hidden unless there is an error message that must be shown to the user.
A ListBox that is used to display all the items in the specified ATOM feed. Later, you will add rendering templates to the control that are used to render out the ATOM feed's items.
The rotating image control that is displayed when no ATOM feed is specified or when the control is retrieving the feed.
The logic that handles what is shown is added to the control's code-behind later. For now, just add the following markup to the MainPage.xaml file.
<StackPanel x:Name="LayoutRoot" Background="White"
VerticalAlignment="Top" Margin="10">
<Button x:Name="AtomEditDialogButon" Content="Select ATOM Feed &
Rendering Template"
Click="AtomEditDialogButon_Click" Width="250" Height="30" />
<TextBlock x:Name="MessageTextBlock" Foreground="Red" Visibility="Collapsed" />
<ListBox x:Name="AtomItemsListBox" Visibility="Collapsed" Margin="5" />
<local:AtomRotator x:Name="AtomRotator" />
</StackPanel>
Add Data Templates for Dynamic Rendering
Rendering templates make the rendering of the ATOM feed items in the ListBox control much easier. These templates are known as data templates in Silverlight. This enables the complete separation of the data from the UI implementation. Data templates are defined as resources in a Silverlight application. These resources can be placed in various locations so that they are available to the entire application or scoped to a specific control. For this application, they are used only within the MainPage.xaml file, so they can be added as a user control resource.
To do this, add the following markup to the MainPage.xaml file. It contains three <DataTemplate /> controls, each with a unique name. These controls display text upon binding the ATOM feed's items. For now, their Text properties are blank.
<UserControl>
<UserControl.Resources>
<DataTemplate x:Name="TitleOnlyDataTemplate">
<TextBlock Foreground="#2555F1" Text="" />
</DataTemplate>
<DataTemplate x:Name="TitleAndDateDataTemplate">
<StackPanel Orientation="Vertical">
<TextBlock FontWeight="Bold" Foreground="#2555F1" Text="" />
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Gray" FontWeight="Bold" FontStyle="Italic"
Text="Published: " />
<TextBlock Foreground="Gray" Text="" />
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplate x:Name="TitleAndBodyDataTemplate">
<StackPanel Orientation="Vertical">
<TextBlock FontWeight="Bold" Foreground="#2555F1" Text="" />
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Gray" FontWeight="Bold" FontStyle="Italic"
Text="Published: " />
<TextBlock Foreground="Gray" Text="" />
</StackPanel>
<TextBlock Width="480" TextWrapping="Wrap" Margin="0,0,15,0"
Text="" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel />
</UserControl>
Add Control Data Binding Details and a Custom Text Converter
The raw content within the ATOM feed is not suitable for use in the control. Each ATOM item's Summary element could contain markup and could also be quite long. Before the data is bound to the controls, you must remove the markup and, if the content is too long, show only an excerpt. Silverlight provides an easy way to do this by using converters. When binding data to a control, you can specify a registered converter that is used before assigning the content to the control.
Create a Custom Text Converter
First, create an HTML converter by adding a new class named HtmlConverter.cs to the project. Add the following code to the file.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Browser;
namespace MSDN.SharePoint.Samples.AtomField {
public class HtmlConverter : IValueConverter {
public object Convert (object value, Type targetType, object param, CultureInfo ci) {
// Strip out all tags.
string result = Regex.Replace(value as string, "<.*?>", "");
result = HttpUtility.HtmlDecode(result);
// Trim to only show the first 160 characters.
if (result.Length > 160)
result = String.Format("{0}...", result.Substring(0, 160));
// Remove newlines.
result = result.Replace(Environment.NewLine, string.Empty);
result = result.Replace("\n", string.Empty);
return result;
}
public object ConvertBack (object value, Type targetType, object param,
CultureInfo ci) {
throw new NotImplementedException();
}
}
}
Register a Custom Converter as a Silverlight Resource
Return to the MainPage.xaml file, and add an element that references the HtmlConverter class in the user control's resources section. This is shown in the following example.
<UserControl.Resources>
<!-- Declare converter. -->
<local:HtmlConverter x:Key="HtmlConverter" />
Now, the converter can be called when the data binding occurs.
Data Bind the Data Template Controls
The last step is to add the data binding notation to the controls within the three <DataTemplate /> elements that are defined in the <UserControl.Resources /> element. The following example shows how to do this, including using the custom converter, for the final one listed that is named TitleAndBodyDataTemplate. Apply similar binding notations to the Text attributes on the other controls in the other two templates.
<DataTemplate x:Name="TitleAndBodyDataTemplate">
<StackPanel Orientation="Vertical">
<TextBlock FontWeight="Bold" Foreground="#2555F1" Text="{Binding
Title.Text, Converter={StaticResource HtmlConverter}}" />
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Gray" FontWeight="Bold" FontStyle="Italic"
Text="Published: " />
<TextBlock Foreground="Gray" Text="{Binding PublishDate}" />
</StackPanel>
<TextBlock Width="480" TextWrapping="Wrap" Margin="0,0,15,0"
Text="{Binding Summary.Text, Converter={StaticResource
HtmlConverter}}" />
</StackPanel>
</DataTemplate>
At this point, the application UI is complete. Next, you will add the required logic to the application.
Creating the Application Business Logic
With the UI complete, it is time to add code to make it work. You do this in the code-behind file for the MainPage.xaml file. Right-click this file in Solution Explorer, and then click View Code.
Because the project will be working with ATOM feeds, to make development simpler, add a project reference to an assembly that Microsoft included in the Microsoft .NET Framework named System.ServiceModel.Syndication. This manages the serialization and deserialization of the ATOM feed, so that you do not have to write the complex XML code to do it.
Before you proceed, a few class-scoped fields are required. Add the following fields after the declaration of the MainPage class, as shown in the following example.
// Sync context for threading.
private SynchronizationContext _syncContext;
// Remote feed.
private SyndicationFeed _atomFeed;
// Fields to hold values.
private string _atomFeedUrl = string.Empty;
private string _dataTemplateSelected = string.Empty;
Initialize the User Interface
When the application loads, you must first initialize the UI. This involves reading the values from the two HTML elements on the hosting web page that will tell the application the URL of the ATOM feed to retrieve and what data template is selected. If a feed is specified, the GetAtomFeed method is called, which retrieves the ATOM feed and binds it to the UI. If no feed is specified, the ATOM icon rotator control is shown.
Finally, you set the Visibility property of the button that opens the modal edit dialog box, depending on the current state of the page: edit mode or display mode.
Add the following code to the class constructor.
public MainPage () {
// Required to initialize variables.
InitializeComponent();
// Validate incoming data.
if (App.FeedElement != null) {
if (App.FeedElement.GetProperty("Value") != null && App.FeedElement.GetProperty("Value").ToString().Length > 0) {
_atomFeedUrl = App.FeedElement.GetProperty("Value").ToString();
// Initialize the data template selected.
if (App.TemplateElement != null)
_dataTemplateSelected = App.TemplateElement.GetProperty("Value").ToString();
if (string.IsNullOrEmpty(_dataTemplateSelected.Trim()))
_dataTemplateSelected = "Title";
// Retrieve feed.
GetAtomFeed();
}
} else // Else, show the rotator.
GetRotator();
if (!App.IsEditMode) {
// Hide the button and maximize the display window.
AtomEditDialogButon.Visibility = Visibility.Collapsed;
} else {
// Show the button.
AtomEditDialogButon.Visibility = Visibility.Visible;
}
}
The GetRotator method simply hides the ListBox that displays the ATOM feed's items and displays the rotator control that you previously created.
private void GetRotator () {
AtomItemsListBox.Visibility = Visibility.Collapsed;
AtomRotator.Visibility = Visibility.Visible;
}
Retrieve the ATOM Feed and Data Bind the Feed's Items
The next step is to write the code (GetAtomFeed) that will retrieve the ATOM feed asynchronously. Triggering the response calls a callback method (BeginResponse) that will retrieve the ATOM feed. If the call succeeds, it posts the response to the UI thread that the GetAtomFeed obtained a reference to, because network calls cannot exist on the same thread as the UI in Silverlight.
private void GetAtomFeed () {
// Show rotator animation...
GetRotator();
// Get the sync context while on UI thread.
_syncContext = SynchronizationContext.Current;
// Get URL of the ATOM feed.
string atomFeedUrl = HttpUtility.HtmlEncode(_atomFeedUrl);
// Get the feed.
HttpWebRequest request = HttpWebRequest.Create(new Uri(atomFeedUrl)) as HttpWebRequest;
request.BeginGetResponse(BeginResponse, request);
}
private void BeginResponse (IAsyncResult result) {
HttpWebRequest request = result.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(result) as HttpWebResponse;
if (response.StatusCode == HttpStatusCode.OK)
_syncContext.Post(ExtractResponse, response);
}
When the HTTP response is posted to the UI thread, it calls another method, ExtractResponse, which reads the ATOM feed's contents into a SyndicationFeed object. Then, it calls a SetListBoxDataTemplate method that sets the rendering template of ListBox to the one selected and binds the results.
private void ExtractResponse (object state) {
HttpWebResponse response = state as HttpWebResponse;
if (response != null && response.StatusCode == HttpStatusCode.OK) {
XmlReader reader = XmlReader.Create(response.GetResponseStream());
_atomFeed = SyndicationFeed.Load(reader);
if (App.FeedElement != null)
App.FeedElement.SetProperty("Value", _atomFeedUrl);
// Set the data template selected.
SetListBoxDataTemplate();
} else {
// An error occurred in retrieving the feed so display an error.
string message = string.Empty;
using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
message = reader.ReadToEnd();
}
MessageTextBlock.Text = string.Format("An error occured in retrieving the feed: {0}", message);
MessageTextBlock.Visibility = Visibility.Visible;
}
}
private void SetListBoxDataTemplate () {
if (_atomFeed != null) {
AtomItemsListBox.ItemsSource = null;
switch (_dataTemplateSelected) {
case "TitleAndDate":
AtomItemsListBox.ItemTemplate = Resources["TitleAndDateDataTemplate"] as DataTemplate;
break;
case "TitleAndBody":
AtomItemsListBox.ItemTemplate = Resources["TitleAndBodyDataTemplate"] as DataTemplate;
break;
default:
AtomItemsListBox.ItemTemplate = Resources["TitleOnlyDataTemplate"] as DataTemplate;
break;
}
if (App.TemplateElement != null)
App.TemplateElement.SetProperty("Value", _dataTemplateSelected);
// Bind the data.
AtomItemsListBox.ItemsSource = _atomFeed.Items;
AtomItemsListBox.Visibility = Visibility.Visible;
// Hide the rotator.
AtomRotator.Visibility = Visibility.Collapsed;
}
}
Notice that the ATOM image rotator control is hidden right after the ListBox is displayed, after the ATOM feed's items are bound to the ListBox.
Wire Up the Edit Modal Dialog Box for a Custom Editing Experience
The last step in modifying the UI is to create an event handler for the button click in edit mode. You do this by creating an instance of the dialog control, wiring up the event, initializing the values within the control, and then showing the dialog box. This is shown in the following code.
private void AtomEditDialogButon_Click (object sender, System.Windows.RoutedEventArgs e) {
AtomEditDialog atomEditorDialog = new AtomEditDialog();
// Wire up closed event.
atomEditorDialog.Closed += AtomEditorDialog_Closed;
// Initialize.
atomEditorDialog.UrlTextBox.Text = _atomFeedUrl;
atomEditorDialog.TitleOnlyRadioButton.IsChecked = _dataTemplateSelected ==
"Title" ? true : false;
atomEditorDialog.TitleAndDateRadioButton.IsChecked = _dataTemplateSelected
== "TitleAndDate" ? true : false;
atomEditorDialog.TitleAndBodyRadioButton.IsChecked = _dataTemplateSelected
== "TitleAndBody" ? true : false;
atomEditorDialog.Show();
}
After the user clicks the OK button or the Cancel button in the modal dialog box, the Closed event handler is raised. If the user clicked OK, the URL of the feed is retrieved and refreshed by calling GetAtomFeed again, followed by changing the data template that was selected, and refreshing the UI. This is shown in the following code.
protected void AtomEditorDialog_Closed (object sender, EventArgs e) {
AtomEditDialog atomEditorDialog = (AtomEditDialog)sender;
// If clicked cancel, do nothing.
if (atomEditorDialog.DialogResult == false)
return;
// Set values of text boxes.
if (_atomFeedUrl != atomEditorDialog.UrlTextBox.Text) {
_atomFeedUrl = atomEditorDialog.UrlTextBox.Text;
GetAtomFeed();
}
if ((bool)atomEditorDialog.TitleOnlyRadioButton.IsChecked)
_dataTemplateSelected = "Title";
if ((bool)atomEditorDialog.TitleAndDateRadioButton.IsChecked)
_dataTemplateSelected = "TitleAndDate";
if ((bool)atomEditorDialog.TitleAndBodyRadioButton.IsChecked)
_dataTemplateSelected = "TitleAndBody";
// Update UI.
SetListBoxDataTemplate();
}
At this point, the Silverlight application is complete. Build the project and fix any errors that might be reported. Next, you will test the application before integrating it into SharePoint.
Testing the Silverlight 3 Application
Testing the Silverlight 3 application outside SharePoint makes it much easier to identify and resolve issues. Otherwise, you must first determine whether the error is coming from SharePoint or Silverlight.
In the web project created by Visual Studio, you will find an HTML file and a few other files. Open the HTML file and scroll down to the <body> section. Visual Studio added the Silverlight player and set a few parameters. To test the previously created application, you need to make a few changes.
First, create the two HTML hidden fields that will contain the URL of the ATOM feed and the selected template. Add the following markup to the page, anywhere outside the <object /> element.
<input type="hidden" id="AtomFeedValueField" Value="" />
<input type="hidden" id="TemplateValueField" Value="" />
Now, ensure the <object /> tag is referencing the correct Silverlight application file in the source parameter. For our example, it should look like the following.
<param name="source" value="ClientBin/SharePoint.AtomField.xap"/>
The last step is to add a new parameter named initParams that passes values into the Silverlight application. Remember that the App.xaml.cs file now contains code that looks for specific values. These values tell the Silverlight application what HTML elements on the page contain the values that the application needs to run.
<param name="initParams" value="fid=AtomFeedValueField,tid=TemplateValueField,enable=true" />
Save all changes. Now the application can be tested. First, test it in edit mode (when enable=true in the initParams parameter). Right-click the HTML page, and then click View in Browser. This launches the application in a new browser window so that you can interact with it, and test to ensure that everything works correctly.
Figure 5. Testing the application in edit mode
Now, test the application in display mode. For this, modify the HTML elements to show the contents of an ATOM feed and specify the template TitleAndDate.
<input type="hidden" id="AtomFeedValueField" Value="http://feeds.feedburner.com/AndrewConnell" />
<input type="hidden" id="TemplateValueField" Value="TitleAndDate" />
Note
The application that was created assumed that the feeds are accessible by Silverlight. Making them accessible is done by adding a special XML file in the root of a site that contains the resources that are being accessed. If this is not present, Silverlight can access only resources from the site that contains the page that launched the application. For more information, see Making a Service Available Across Domain Boundaries. In the example in this article, the site http://feeds.feedburner.com already contains this file. However, other sites might not. One workaround is to use an ATOM proxy service, such as Yahoo Pipes (http://pipes.yahoo.com/pipes), which does include this file. However, using an ATOM proxy service is beyond the scope of this article.
In addition, change the initParams parameter's enabled property to false to start in display mode.
<param name="initParams" value="fid=AtomFeedValueField,tid=TemplateValueField,enable=false" />
Save the file and rerun the test by right-clicking the HTML file, and then clicking View in Browser, as shown in Figure 6.
Figure 6. Testing the application in display mode
With the Silverlight application created and tested, you can now integrate it into SharePoint.
Creating the Custom Field Type and Control
With SharePoint configured, it is now time to create the custom field type and field control. Using the existing Visual Studio solution, expand the Visual C# node, expand SharePoint 2010, and select the Empty SharePoint Project named AtomFieldType. Now, make the following few changes to the project to set it up as a SharePoint project:
Right-click the AtomFieldType project in Solution explorer, and then click Properties.
On the Application tab, set the following values:
Assembly Name: MSDN.SharePoint.Samples.AtomField
Default Namespace: MSDN.SharePoint.Samples.AtomField
On the .NET tab in the Add Reference dialog box, add an assembly reference to System.Web.
Create the Custom Field Type Object
First, you create the custom field type. To do this, create a C# class file in the root of the AtomFieldType project that is named AtomField.cs. Add the following code to the AtomField.cs file, noting that the class inherits from the SPFieldMultiColumn field type, which enables the storage of a delimited array within a single column in the database. Parsing this array is handled by a custom field value class that you create next, as shown in the following code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace MSDN.SharePoint.Samples.AtomField {
public class AtomField : SPFieldMultiColumn {
public AtomField (SPFieldCollection fields, string fieldName)
: base(fields, fieldName) { }
public AtomField (SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName) { }
public override BaseFieldControl FieldRenderingControl {
get {
BaseFieldControl fieldControl = new AtomFeedControl { FieldName =
InternalName };
return fieldControl;
}
}
}
Create the Custom Field Value Object
The purpose of the custom field value object that you create next is twofold. First, this class tells SharePoint what position in the delimited array is used to hold what value. Second, it is a strongly typed class that is returned to anyone accessing the value of this field by using the SharePoint API.
Create a C# class file named AtomFieldValue.cs, and then add the following code to it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace MSDN.SharePoint.Samples.AtomField {
public class AtomFieldValue : SPFieldMultiColumnValue {
private const int NUMBER_OF_FIELDS = 2;
public AtomFieldValue ()
: base(NUMBER_OF_FIELDS) { }
public AtomFieldValue (string value)
: base(value) { }
public string AtomFeedUrl {
get { return this[0]; }
set { this[0] = value; }
}
public string AtomTemplate {
get { return this[1]; }
set { this[1] = value; }
}
}
}
This class must be wired up to the field type class. To do this, override the GetFieldValue method of the AtomField class, and then return an instance of the new value object, as shown in the following code.
public override object GetFieldValue (string value) {
if (string.IsNullOrEmpty(value))
return null;
return new AtomFieldValue(value);
}
Creating the Custom Field Control Class and Rendering Template
You create the custom rendering for the custom field by using a custom field control. Custom field controls have two pieces: a class that SharePoint uses to get all the information that it requires to interact with the field control, and an *.ascx file that contains a rendering template that defines how to render the UI.
Create the Custom Field Control Rendering Template
First create a mapped folder by right-clicking the AtomFieldType project, clicking Add, and then clicking SharePoint Mapped Folder. Select the {SharePointRoot}\TEMPLATE\CONTROLTEMPLATES folder. Visual Studio automatically adds a subfolder with the project name in it. Usually, this is a best practice to follow, but custom field control rendering templates must be placed at the root of the CONTROLTEMPALTES folder. So, delete the subfolder created by Visual Studio. Next, create the rendering template by creating a file by using the Text File template, naming it SilverlightAtomFieldControl.ascx, and then saving it in the CONTROLTEMPLATES folder in the project.
Add the following markup to the file. Notice that it contains the two HTML hidden elements that will be used to communicate with the Silverlight 3 application that you created previously.
<%@ Control Language="C#" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %>
<SharePoint:RenderingTemplate id="SilverlightAtomFieldControl" runat="server">
<Template>
<asp:HiddenField ID="AtomFeedValueField" runat="server" />
<asp:HiddenField ID="TemplateValueField" runat="server" />
<asp:Panel ID="SilverlightPanel" runat="server" />
</Template>
</SharePoint:RenderingTemplate>
Add Silverlight Resources to the Project
Before creating the class file that will contain the custom field control, the next step is to add a few files that the control will reference. First, create a mapped folder. Right-click the AtomFieldType project, click Add, and then click SharePoint "Layouts" Mapped Folder. Place the two files in the mapped LAYOUTS\AtomFieldType folder in the project. The two files are as follows:
SharePoint.AtomField.xap This is the Silverlight 3 application that you previously created.
In the SilverlightATOM Field project, change the build output path to point to the mapped layouts folder that you just created. Build the Silverlight project. In the Solution Explorer tool window, select the AtomFieldType project, and then click Show All Files. This causes the *.xap file to appear in the mapped LAYOUTS subfolder. Right-click the file, and then click Include in Project. This ensures that the packager will include the file in the packaging and deployment process. Right-click Show All Files to hide the files that will not be included in the project.
SharePointSilverlightUtility.js This is a custom JavaScript library that makes it easier to write out the Silverlight plug-in with its associated parameters.
Note
The JavaScript library is provided in the SharePoint 2010 Silverlight 3 Publishing Field Type sample.
Create the Custom Field Control Object
Now create the class that will contain the custom field control object. Create a C# class file named AtomFieldControl.cs in the root of the project. This file creates the communication between the Silverlight 3 application and the field type. Begin by adding the code of the class, as follows.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MSDN.SharePoint.Samples.AtomField {
public class AtomFeedControl : BaseFieldControl {
}
}
Now, add a few constants and web controls to the class. The web controls are used as references to the controls in the rendering template that you defined previously in the *.ascx file.
public class AtomFeedControl : BaseFieldControl {
private const string RENDERING_TEMPLATE = "SilverlightAtomFieldControl";
private const string RESOURCE_ROOT = "~/_layouts/SharePointAtomField";
private LiteralControl _silverlightHost;
private HiddenField _atomFeedValueField;
private HiddenField _dataTemplateValueField;
private Panel _silverlightPanel;
}
The next step, and one of the most important, is to override the CreateChildControls method, which does two things. First, it establishes references to the web controls that are defined in the *.ascx file. It then generates the JavaScript that will create the Silverlight 3 plug-in reference and parameters. This JavaScript is written to the page by using a LiteralControl and adding it to the field control's Controls collection.
protected override void CreateChildControls () {
if (Field == null || ControlMode == SPControlMode.Invalid)
return;
base.CreateChildControls();
// Get references to controls.
_atomFeedValueField = TemplateContainer.FindControl("AtomFeedValueField")
as HiddenField;
if (_atomFeedValueField == null)
throw new ArgumentNullException("ATOM feed field not found. Corrupt
control template.");
_dataTemplateValueField =
TemplateContainer.FindControl("TemplateValueField") as HiddenField;
if (_dataTemplateValueField == null)
throw new ArgumentNullException("Data template field not found. Corrupt
control template.");
_silverlightPanel = TemplateContainer.FindControl("SilverlightPanel") as Panel;
if (_silverlightPanel == null)
throw new ArgumentNullException("Silverlight host not found. Corrupt
control template.");
// Fill in Silverlight host.
string xapURL = String.Format("{0}/SharePoint.AtomField.xap",
RESOURCE_ROOT);
string enable = ControlMode == SPControlMode.Display ? "false" : "true";
string initParams = string.Format("fid={0},tid={1},enable={2}",
_atomFeedValueField.ClientID,
_dataTemplateValueField.ClientID,
enable);
string silverlightHost = string.Format("<script type=\"text/javascript\">"
+ "createSL('silverlightHost','500','500','{0}','{1}');"
+ "</script>",
xapURL, initParams);
_silverlightHost = new LiteralControl(string.Format("<div
id=\"silverlightHost\" style=\"width:100%;height:100%\" />{0}",
silverlightHost));
Controls.Add(_silverlightHost);
}
To use the functions that are included in the JavaScript library, the current page must have a reference to it. You can do this by using the page's ClientScriptManager in the OnPreRender method, as show in the following code.
protected override void OnPreRender (EventArgs e) {
base.OnPreRender(e);
// Register the Silverlight 3 utility script on the page.
Page.ClientScript.RegisterClientScriptInclude("SharePointSilverlightUtility",
string.Format("{0}/SharePointSilverlightUtility.js", RESOURCE_ROOT));
}
Next, override two properties and one method that SharePoint will call when rendering the field control. Do this by telling SharePoint the ID of the rendering template (which it will find in an *.ascx file) and what to use when the field control is rendered in display mode.
protected override string DefaultTemplateName {
get { return RENDERING_TEMPLATE; }
}
public override string DisplayTemplateName {
get { return RENDERING_TEMPLATE; }
}
protected override void RenderFieldForDisplay (HtmlTextWriter output) {
if (ItemFieldValue != null) {
AtomFieldValue fieldValue = new AtomFieldValue(ItemFieldValue.ToString());
if (_atomFeedValueField != null) {
_atomFeedValueField.Value = fieldValue.AtomFeedUrl;
_atomFeedValueField.RenderControl(output);
}
_dataTemplateValueField.Value = fieldValue.AtomTemplate;
}
base.RenderFieldForDisplay(output);
}
Override the Value property that SharePoint will use to set any present values on the field control when the page is loaded, and to retrieve the values in the web controls when the page is saved.
public override object Value {
get {
EnsureChildControls();
AtomFieldValue fieldValue = new AtomFieldValue();
// Set the ATOM feed URL.
if (_atomFeedValueField != null && !string.IsNullOrEmpty(_atomFeedValueField.Value))
fieldValue.AtomFeedUrl = _atomFeedValueField.Value;
// Set the data template that is selected.
if (_dataTemplateValueField != null && !string.IsNullOrEmpty(_dataTemplateValueField.Value))
fieldValue.AtomTemplate = _dataTemplateValueField.Value;
return fieldValue;
}
set {
EnsureChildControls();
if (value != null && !string.IsNullOrEmpty(value.ToString())) {
AtomFieldValue fieldValue = new AtomFieldValue(value.ToString());
_atomFeedValueField.Value = fieldValue.AtomFeedUrl;
_dataTemplateValueField.Value = fieldValue.AtomTemplate;
}
}
}
The last step is to wire the custom field control to the field type class. You do this by overriding the FieldRenderingControl property on the AtomField object.
public override BaseFieldControl FieldRenderingControl {
get {
BaseFieldControl fieldControl = new AtomFeedControl { FieldName = InternalName };
return fieldControl;
}
}
Create the Custom Field Type Definition
After the custom field type is deployed, SharePoint needs to be made aware of it. This happens when the web server is recycled. When SharePoint loads, it examines all files named fldtypes*.xml that are located in the [..]\14\TEMPLATE\XML folder on the server. A fldtypes*.xml file contains metadata about the field type, the assembly that contains the field type, and the fully qualified type name of the field type class name.
First, create a mapped folder by right-clicking the AtomFieldType project, clicking Add, and then clicking SharePoint Mapped Folder. Select the {SharePointRoot}\TEMPLATE\XML folder. Visual Studio automatically adds a subfolder with the project name in it. As with the CONTROLTEMPLATES folder previously, this is usually a best practice to follow, but in this case the XML file must be placed at the root of the XML folder. Therefore, delete the subfolder created by Visual Studio. Next, create a new XML file named fldtypes_MSDNSHarePointSilverlight.xml in the XML folder in the project, and then add the following markup.
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
<FieldType>
<Field Name="TypeName">AtomFeedField</Field>
<Field Name="ParentType">MultiColumn</Field>
<Field Name="TypeDisplayName">ATOM Feed Picker</Field>
<Field Name="TypeShortDescription">ATOM Feed picker</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="ShowInListCreate">TRUE</Field>
<Field Name="ShowInSurveyCreate">TRUE</Field>
<Field Name="ShowInDocumentLibraryCreate">TRUE</Field>
<Field Name="ShowInColumnTemplateCreate">TRUE</Field>
<Field Name="FieldTypeClass">MSDN.SharePoint.Samples.AtomField.AtomField, $SharePoint.Project.AssemblyFullName$</Field>
<RenderPattern Name="DisplayPattern">
<Switch>
<Expr>
<Column />
</Expr>
<Case Value="" />
<Default>
<Column SubColumnNumber="0" HTMLEncode="TRUE" />
<HTML><![CDATA[, ]]></HTML>
<Column SubColumnNumber="1" HTMLEncode="TRUE" />
</Default>
</Switch>
</RenderPattern>
</FieldType>
</FieldTypes>
Notice the use of a token in the replaceable tokens in the FieldTypeClass definition. These tokens simplify creating custom components in SharePoint projects. For a list of all replaceable tokens, see Replaceable Parameters. In this case, a modification is made to the *.csproj project file to ensure that *.xml files are part of the token replacement process.
At this point, the custom field type and field control are complete. The remaining steps involve packaging, deploying, installing, implementing, and testing the custom field type, field control, and Silverlight 3 application. The final project should resemble Figure 7.
Figure 7. Final project appearance
Deploying and Installing Custom Field Types and Controls
Deploying custom field types and controls is very easy when you use the SharePoint development tools in Microsoft Visual Studio 2010: right-click the project, and then click Deploy. However, you must address one issue before doing this. When deploying custom field types with associated field controls for use in a SharePoint Foundation 2010 site, nothing more is needed. But if the intended use is a SharePoint publishing site, you must add a safe control entry to the hosting web application's web.config file. Usually, this is done by the SharePoint development tools. But they are not configured to do this for custom field types and controls. However, because of the very extensible nature of the tools, this is a relatively easy task.
To open the Package Designer, right-click the Package node in the project, and then click View Designer. Next, click the Manifest tab at the bottom of the page, and then click the [+] Edit Options control. The changes entered into the white area of the Manifest Template will be merged with what the tools generate automatically. To have the tools include a safe control entry for the assembly, the entire assembly must be duplicated, and then include the safe control entry as follows.
<?xml version="1.0" encoding="utf-8"?>
<Solution xmlns="https://schemas.microsoft.com/sharepoint/">
<Assemblies>
<Assembly Location="AtomFieldType.dll" DeploymentTarget="GlobalAssemblyCache">
<SafeControls>
<SafeControl Namespace="MSDN.SharePoint.Samples.AtomField" Safe="True" TypeName="*" />
</SafeControls>
</Assembly>
</Assemblies>
</Solution>
Notice in the upper portion of the window that the changes are shown in the Preview of Packaged Manifest.
Now, deploy the custom field type by right-clicking the AtomFieldType project, and then selecting Deploy.
The custom field type and field control are now deployed, installed, and ready for use in a SharePoint site.
Testing the Custom Field Type, Field Control, and Silverlight 3 Application
Although the custom field type, field control, and Silverlight 3 application can be used in a Microsoft SharePoint Foundation 2010 site, you will use a oss14long publishing site.
Browse to a publishing site and navigate to the site's Content Type Gallery (on the Site Actions menu, click Site Settings, and then in the Galleries section, click the Site Content Types link).
Find the Article Page content type, and then add a new site column to it. You should name this site column ATOM Feed and it should use the ATOM Feed Picker field type.
With the new field added to the content type, create a new page layout that is associated with the Article Page content type. Start SharePoint Designer 2010, and then open the publishing site that contains the new site column. In the Navigation section, in Site Objects, click Page Layouts. Next, click the New Page Layout button in the ribbon. In the New dialog box that opens, select the Article Page content type from the list.
Drag the ATOM Feed field from Toolbox to the page layout that you created. If the Toolbox is not visible, on the Insert tab in the ribbon, click SharePoint, and then click Show Toolbox. Save the changes to the file, check in the file, and then publish the changes.
In the browser, create a page by using the new page layout. When the page is created, the browser should show it in edit mode. The new field control should appear with the rotating ATOM icon, as shown in Figure 8.
Figure 8. Field control with rotating ATOM icon
Click the button, and then type a URL to a known ATOM feed. Click the Title & Publication Date button, and then click OK. The page should now show the rendered ATOM feed items. Finally, check the page in, which hides the edit mode button because the page is now in presentation mode, as shown in Figure 9.
Figure 9. Page in presentation mode
Conclusion
This article demonstrates how to create a custom field type and field control for use in a SharePoint site. SharePoint provides developers with the capability to create custom field types and controls when native field storage and rendering options for both display and edit modes do not suit the business requirements. In addition, this article demonstrates how to use Microsoft Silverlight 3 to create a custom display and a rich, engaging editing experience for the user. As part of this process, this article also addresses the requirements for updating SharePoint web applications to host Silverlight 3 applications.
Acknowledgements
I would like to acknowledge Karine Bosch (SharePoint MVP) for her assistance in the creation of the sample that is used in this article.
Additional Resources
For more information, see the following resources: