Oct 8, 2008

Creating a Windows SharePoint Services 3.0 Custom Field by Using the EntityPicker

Article From (http://msdn.microsoft.com/en-us/library/cc837956.aspx)

Creating a Windows SharePoint Services 3.0 Custom Field by Using the EntityPicker

Summary: Learn to create an advanced custom field for Windows SharePoint Services 3.0 and use built-in search dialogs in SharePoint to your advantage. You will also be shown how to implement custom field settings. (17 printed pages)

Wouter van Vugt, Code Counsel

August 2007

Applies to: 2007 Microsoft Office System, Windows SharePoint Services 3.0

Contents

Overview

One of the most popular features in SharePoint is the area of extensibility using the list and the concept of a field, or column if you will. Windows SharePoint Services 3.0 includes many built-in field types such as the basic Text Field or the more advanced Lookup Field. In the SharePoint framework, you can extend the list of built-in fields with your own customizations. One customization of a Windows SharePoint Services field is found in Microsoft Office SharePoint Server 2007 that takes this approach to allow data obtained through the Business Data Catalog to appear as a field in your lists and document libraries. In this article, you will learn how you can extend the functionality of Windows SharePoint Services with a custom field type that allows you to look up records in other lists in the site collection with a similar user interface as found in the Business Data Field.

When you create a new field, various components must work together to form the final solution in SharePoint. In its most basic form you need to define an XML file containing the field definition, various pieces of metadata and the Collaborative Application Markup Language (CAML) based rendering logic you use to display the field. The second requirement is the .NET class that implements your customization. While the combination of a single XML file and a class might be appropriate for simple fields such as a Text Field using a regular expression based validation extension, it is not uncommon to have more advanced requirements. SharePoint provides this by an extensible field framework that allows various parts of the field to be altered, for example the User Interface you display to edit field values. The custom field implementation presented in this article uses these extensibility points to provide a rich and intuitive user interface using the built-in customizable search dialog.

The Sample Field

Accompanying this article is code for a new field type called the Record Lookup Field. As the name implies, it allows you to look up records in other lists, similar to the built-in Lookup Field. The main difference with the Lookup Field is that you can link to an entire record in the targeted list instead of including a value of a single column. Another difference is that you are allowed to include extra columns to accommodate values taken from the target list item. Do not mistake this for a database-like foreign key construction because the values are copied and not referenced.

First, let me give you an impression of the field in the SharePoint user interface, the way it is most commonly used. In the following scenario, a list called 'Projects' is already created and filled in a team site. The sample uses a Custom List containing a few extra fields that describe a fictional project.

The first step in showing the Record Lookup Field is to create a new list called Project Reviews. The Custom List template is also used for this second list.

To allow a project review to reference an item in the projects list, you add a Record Lookup Field. The user interface that is presented when the Record Lookup Field is configured is displayed in Figure 1. As you can see, the user can choose from a site in the site collection and select a list to target. The main display column is set to the Title column in the target list, and there are two extra columns included. The RecordLookupField creates additional fields to store the values of these two extra columns.

Figure 1. The RecordLookupField editor


The display of the list after the field has been added is shown in Figure 2. Notice the inclusion of three new columns, one for the main display value and two others for the included columns of the target list.

Figure 2. A list using the RecordLookupField


The next obvious step is to add new project reviews. The user interface that is presented to add or edit a project review is displayed in Figure 3 and should be recognizable to anyone who has used SharePoint before. You will see the familiar textbox that allows you to enter a known project name and the validate and search button beside it.

Figure 3. The list item editing display


The text that you type in the text box is validated against the main display column you chose when you added the field. The browse button takes you to a custom SharePoint dialog that allows the search of items in the target list. I will explain how this custom dialog is implemented later on. The current implementation allows you to base your search on any of the columns that were selected when the field was added. This search dialog is displayed in Figure 4. The displayed data is taken directly from the target list by issuing a CAML query against it and is independent of the field settings. It is taken from the default view of the target list.

Figure 4. Looking up a value in a list


Understanding the Components of Custom Fields

Each field type inside SharePoint is made up of various interconnected components, some you observed in the previous demonstration of the field interface. These components work together to provide a rich environment for creating custom data types specific to your company's needs. You can use custom fields to implement things such as custom user interfaces for all the places where the field shows up, custom data storage and validation of custom data. You can also extend the field lifecycle with custom actions such as creating additional related fields during the creation or modification of settings of the field.

The following figure shows the main tasks of the components related to the RecordLookupField.

Figure 5. Components of the RecordLookupField


A few new components are used in addition to the XML definition and the field class. Let's go over these components in a little more detail before going into some of the more intricate parts of the code.

The XML Field Definition

SharePoint knows about your fields because they are defined in XML files deployed to each front-end server in your SharePoint farm. You will find these XML files inside the \Template\Xml subfolder of the SharePoint installation. The term fldtypes always prefixes these field-defining files. For example, fldtypesACME.xml is a valid name for the file. The main field definition file belonging to SharePoint is called fldtypes.xml and contains definitions of the basic fields such as the Text Field or Choice Field. An important side note is that you should not alter the fldtypes.xml file because SharePoint relies on it for built-in features. In all likelihood, updates to SharePoint are allowed to overwrite this file. Within one XML file you can define multiple fields, their metadata and rendering logic.

The basic field definition of the Record Lookup Field is displayed in the following sample. The TypeName property is the main identifier for the field. It is used when you create field through code with the CreateField method on the SPFieldCollection class. TheTypeShortDescription is displayed in the SharePoint user interface when the field is created or edited. The XML definition identifies two .NET classes. The first is a class representing the field; the second identifies the field editor control. This field editor control is displayed when the field settings are modified. After a short and incomplete list of meta-data settings (there are more settings available) the custom settings appear and the file ends with the CAML based rendering pattern. This pattern is used to display the field in the list viewing interface.

Code Sample 1: The XML definition for the RecordLookupField.

C#

Code Sample goes here

<?xml version="1.0" encoding="utf-8" ?>

<FieldTypes>

<FieldType>

<Field Name="TypeName">RecordLookupField</Field>

<Field Name="TypeDisplayName">Record Lookup</Field>

<Field Name="TypeShortDescription">Record Lookup (Records already on this site collection)</Field>

<Field Name="ParentType">Text</Field>

<Field Name="FieldTypeClass">CodeCounsel.SharePoint.Fields.RecordLookupField, CodeCounsel.SharePoint.Fields, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=9e1907c8b7a1abf2</Field>

<Field Name="FieldEditorUserControl">/_controltemplates/RecordLookupFieldEditor.ascx</Field>

<Field Name="UserCreatable">TRUE</Field>

<Field Name="Sortable">TRUE</Field>

<Field Name="Filterable">TRUE</Field>

<PropertySchema>

<Fields>

<Field Name="WebID" Type="Text" Hidden="TRUE" />...

</Fields>

</PropertySchema>

<RenderPattern Name="DisplayPattern">

...``

</RenderPattern>

</FieldType>

</FieldTypes>

The custom settings specific to the RecordLookupField such as the WebId property and CAML rendering pattern are important because in order to access the settings at runtime through the RecordLookupField class (the class implementing the field in .NET) you first need to define them here. There is also a limited set of allowed data types you can use. Basically, strings and simple primitives are allowed. The CAML rendering logic is left out of the sample and is discussed later.

The deployment location of the field definition is also an important consideration. Because the XML is deployed to a global directory, all the sites within each web application on that web server can access the field definition and then display it in the list-column creation interface. There is no clean way of hiding the field definition, so the best that you can do to prevent using the field in certain sites is to use the Feature Framework to block the field settings interface and disallow creating the field that way.

Field Classes

The field class is the main implementation of a field. All fields in SharePoint derive either directly or indirectly from the SPField class. The field class provides a storage point for the field settings defined in the XML field definition and allows the modification of these setting through code. The field class also takes part in other tasks such as validating the field value.

There are a few interesting points to know about field classes. First, you should derive from the class which corresponds to the ParentType setting in the XML field definition. You should not derive directly from SPField. The mapping is rather obvious. For the Text parent you need to derive from SPFieldText and so on. The class must be public and have two specific public constructors to work properly. This is due to instantiating the field dynamically at runtime using Reflection. Additionally, using fields requires the SharePointObjectModel permission. This permission is demanded from the callers.

Code Sample 2: The class representing the custom field.

C#

[CLSCompliant(false)]

[SharePointPermissionAttribute(SecurityAction.Demand, ObjectModel = true)]

public
class RecordLookupField : SPFieldText

{


public RecordLookupField(

SPFieldCollection fields, string fieldName)

: base(fields, fieldName)

{

}


 


public RecordLookupField(

SPFieldCollection fields, string typeName,


string displayName)

: base(fields, typeName, displayName)

{

}


 


public override void OnAdded(SPAddFieldOptions op) { }


 


public override void OnUpdated() { }


 


public override void OnDeleting() { }

}

In the SPField class there are three methods that you can override to customize the behavior for various phases of the field's lifetime. The code running inside these methods needs to consider various implementation details of SharePoint, which are handled in the next section.

Now let's look at the first extra component used by the RecordLookupField, the field editor.

Field Editors

The XML field definition can define custom settings for the field by applying the <PropertySchema> element, as shown earlier in the sample XML field definition. The Record Lookup Field uses these settings to identify the site, list and display columns used for looking up the field values. The editor provides a user interface for editing field settings. If implemented correctly, you can also modify the field settings directly in the field class without using field editor, for example in a Windows Forms management application running locally on the web server.

In its most basic form, a field editor is auto-generated based on the settings defined within the XML field definition. Based on the property type, a specific editing control is available. You can hide certain controls by setting the Hidden property to TRUE in the XML definition.

The auto-generated editor is suitable for the most basic requirements, but for more advanced needs it is also possible to go further into the framework by building your own custom interface using an ASP.NET control.

By implementing a UserControl and deploying it to the SharePoint control templates folder you can show a custom user interface that better serves the requirements of the field. The XML field definition holds a reference to this control. Figure 8 displays a part of the markup for the control that the RecordLookupField uses. Note that the control inherits from a class provided by the solution, and there is no code-behind file specified as you might find in the control directive of a normal ASP.NET UserControl. The class is deployed to the global assembly cache, which is why the Inherits attribute uses a fully qualified assembly name.

Code Sample 3: The field editing UserControl.

C#

<%@ Control language="C#"

Inherits="CodeCounsel.SharePoint.Fields.RecordLookupFieldEditor,

CodeCounsel.SharePoint.Fields, Version=1.0.0.0,

Culture=Neutral, PublicKeyToken=9e1907c8b7a1abf2" %>

...

<asp:DropDownList id="_webField" runat="server"

AutoPostBack="true"

OnSelectedIndexChanged="WebField_SelectedIndexChanged" />...

In order to interact with SharePoint, a field editor UserControl must implement the IFieldEditor interface. Figure 9 shows the basic layout of the RecordLookupFieldEditor. The first notable part is the use of protected members for the controls defined in the ASCX part of the UserControl. One common pitfall I come across is accessing these controls. You should not resort to using the FindControlmethod to get to them. Remember that you can create protected instance fields with the same name as the ID of the control to allow ASP.NET to link the instances.

The rest of the sample shows the IFieldEditor implementation. The interaction with SharePoint is very basic. There is theInitializeWithField method to initialize the editor with the settings of a specific field and a second method to save the field settings back again. An important note is that the InitializeWithField method runs for each request, the initial request as well as post-back requests. You should handle the new data and old field data accordingly. Also, the field passed to the InitializeWithField method can be a null value. This occurs when you create a new field and there are no settings with which to initialize.

Code Sample 4: The code for the field editor control.

C#

public
class RecordLookupFieldEditor

: UserControl, IFieldEditor

{


protected DropDownList _webField = null;


protected DropDownList _listField = null;


protected DropDownList _mainDisplayColumnField = null;


protected CheckBoxList _additionalColumnsField = null;


protected CheckBox _linkToRelatedRecordField = null;


 


bool IFieldEditor.DisplayAsNewSection

{


get { return
false; }

}


 


void IFieldEditor.InitializeWithField(SPField field)

{

}


 


void IFieldEditor.OnSaveChange(SPField field, bool isNewField)

{

}

}

There are a few interesting implementation notes on the field editor in the next section. Before showing the specific implementation, I will discuss the last component of an advanced field implementation.

Field Controls

The field control defines the appearance of the field when editing list item values. Figure 3 displays the one the RecordLookupField uses. As you can observe you can provide your own interface.

When using custom field controls, like the RecordLookupField does, you need to create a composite control that derives fromBaseFieldControl. A field control is just a normal composite control plus some extras to make it interact with SharePoint. The control has access to the value in the list item you are editing and has a property where its own current value is stored. The Value property stores the current value of the editing control, the ItemFieldValue points to the value in the list item. Validate is called to check whether the field value is valid and then the UpdateFieldValueInItem is called to update the value of the list item.

Code Sample 5: The field value editing control.

C#

public
class RecordLookupFieldControl

: BaseFieldControl

{


public RecordLookupFieldControl()

{

}


 


public override void Validate()

{ }


 


public override void UpdateFieldValueInItem()

{

ItemFieldValue = Value;

}


 


protected override void CreateChildControls()

{

}

}

This concludes the lab around the main classes that form the RecordLookupField. I spend the rest of the article going into the details of the implementation.

Now that you have explored the components of a field, it is time to discuss some of the details of the code in relation to the SharePoint interaction. There are a few intricacies in the implementation that I hope will help you implement your own custom fields.

Storing Field Settings

Your custom field may need some metadata to work correctly, such as the regular expression used by a custom field type implementing regular expression based validation or the site, list and columns that the RecordLookupField uses. One of the most common things people have difficulty with is storing custom field settings correctly. You should not persist the Field settings to your own database; instead they should go into a SharePoint content database that belongs to the site collection. The way this works is through the SchemaXml property of your field class, which stores the values of the field settings as a simple XML structure. All built-in settings are persisted as attributes of the root element, which usually is the only element there. An example of this persistency store is shown in Figure 11. This is taken from an instance of a random list in SharePoint by code similar to mySite.Lists[0].Fields[0].SchemaXml.

Code Sample 6: The XML persistency store for field settings.

C#

<Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"

Type="Text"

Name="Title"

DisplayName="Title"

Required="TRUE"

SourceID="http://schemas.microsoft.com/sharepoint/v3"

StaticName="Title"

FromBaseType="TRUE"

ColName="nvarchar1" />

The XML looks similar to the XML field definition markup, only the field has a unique ID attached to identify the field in the list instance. SharePoint internals use the attributes that you see in the example to persist the field specific state. Using the GetProperty of theSPField class method you can access the value of one of these attributes by name. Notice that the SetProperty method is not present in the SPField class. While you can write your own code to persist to the attributes of SchemaXml by editing the XML directly, you should instead use a different part of the XML data store and the access methods that accompany it.

For custom properties, the SharePoint framework creates a separate child element available in the XML structure calledCustomization. You can write objects to this part of the data store using the GetCustomProperty and SetCustomProperty methods. An example of the custom settings part of the XML data store is as follows.

Code Sample 7. The XML persistency store for custom field settings.

C#

<Field

ID="{388cd95a-ae80-47cc-b487-08c7ee784c71}"

Type="Text"

Name="Title"

DisplayName="Title"

Required="FALSE"

SourceID="http://schemas.microsoft.com/sharepoint/v3"

StaticName="Title"

ColName="nvarchar1"

FromBaseType="True">

<Customization>

<ArrayOfProperty>

<Property>

<Name>MyCustomProperty</Name>

<Value p4:type="q1:string"

xmlns:q1="http://www.w3.org/2001/XMLSchema"

xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">

The MyCustomProperty value

</Value>

</Property>

</ArrayOfProperty>

</Customization>

</Field>

Now you might wonder why this is something people have difficulty with. The split between SharePoint and user settings is reasonable so why would it be difficult? There are a few things to know about the custom property persistency store. The first is the moment when calls to the database occur. The GetCustomProperty and SetCustomProperty methods by themselves do not cause database interaction to occur. You should call Update to persist the custom properties to the database. Updating SchemaXml directly works differently. Changing the property updates the database immediately without the need to call Update. The second interesting point is the persistence format used by SharePoint. Internally your objects are serialized to XML using the XmlSerializer when the field settings are persisted to the database.

Before going to the XmlSerializer all reference types are converted to a string using the ToString method. (I swear I used trial and error to find this out). This means you can only store rich objects such as Array objects through your own serialization mechanism.

Another difficulty is cross-instance persistence of settings. For me to explain this problem fully, first you need to know the way new fields are processed by SharePoint.

The Field Creation Process

The process of creating new fields is a basic one. You first configure a field instance and then add it to the SPFieldCollection of the list you want to extend with a new column. The mechanism underneath is slightly different. Based on the user's actions, first an instance of the field is created using the CreateNewField method on the field collection. Next, this instance is configured and finally it is added to the list using the Add method. In the Add method the SchemaXml property is accessed and persisted to the database. When you access the new field using its internal name, you get a second instance. The following sample demonstrates this. TheWriteLine call displays the text "False."

Code Sample 8: Multiple field instances when creating fields.

C#

SPList list = web.Lists["Project Reviews"];

// Create field and alter settings

SPField field = list.Fields.CreateNewField(


"Text", "My Field");

field.Title = "This is an altered setting";

// Add and retrieve again

string name = list.Fields.Add(field);

SPField field2 = list.Fields.GetFieldByInternalName(name);

// Compare instances

Console.WriteLine(field == field2);

Now let me explain this in relation to the part of SchemaXml that stores your settings. As discussed, the root element stores the built-in settings and the Customization element stores the custom settings. You should, of course, use the Customization element as a good behaving SharePoint citizen. The difficulty occurs when you create and configure a new before you add it to the SPFieldCollection. Your configuration is stored in a temporary cache in the field and not in SchemaXml just yet (the cache is flushed when you callUpdate remember?) When adding the field to the field collection internally SchemaXml is accessed and stored in the content database, without your custom settings!

You can use the three life-time related events (OnAddedOnUpdated and OnDeleting) to work around this situation. Inside the OnAdded and OnUpdated events you can call Update to move your custom settings from the temporary cache into SchemaXml and the content database. But now you will run into another difficulty. I demonstrated earlier that SharePoint creates separate instances of your field class after initially adding the field to the field collection. The difficulty is that while you have configured the first instance, the lifetime events fire on the second. The event is not named OnAdded for nothing, the database interaction for creating the field has already occurred.

All of this basically means that you store custom settings in a temporary persistency store to survive the creation of a new field by SharePoint. After, you can use the lifetime event to move the custom setting from the temporary store into the cache and. Then you finish it off with a call to Update to move the setting to the SchemaXml persistency store and next to the database. This does mean that two database calls will occur.

Persisting Values Across Instances

You can think of many solutions for the temporary storage of field settings, but most are unusable when you want your field to work in Windows Forms management applications that runs on the front-end server. When accessing SharePoint from Windows Forms applications there is no ASP.NET context, which means the Application/Session/Request and other ASP.NET caches are not available. One thing that these applications do have in common is a thread that operates it. The temporary persistency store used for storing setting values across instances when adding fields can use thread local storage. The only thing you need to worry about is which property to access during the OnAdded and OnUpdated events. This is discussed next. The following code shows how a custom field setting is persisted.

Code Sample 9: Persisting and retrieving custom field settings.

C#

class RecordLookupField

{ ...


public
string MainDisplayColumn

{


get

{ // Only used during field updates


return GetCustomProperty(


"MainDisplayColumn");

}


set

{


// Used during field creation

SetNamedThreadData("MainDisplayColumn", value);


// Used during field updates

SetCustomProperty("MainDisplayColumn", value);

}

} void SetNamedThreadData(string propertyName, string value)

{

Thread.SetData(

Thread.GetNamedDataSlot(propertyName),

value);

}

...

}

Added, Updated and Deleted Events

The SPField class provides three methods you can override that are called during the lifetime of the field. The OnAddedOnUpdatedmethods are fired after and the OnDeleting method is fired before the three main events in that lifecycle: adding, updating and deleting the field. There are a few intricacies to the implementation based on how settings are stored and there is interdependence between the OnAdded and OnUpdated events. Because you need to store your custom settings during the OnAdded event due to behavior discussed earlier, you need to call the Update method. This in turn raises the OnUpdated event, which if not careful, updates the database again for a third time.

So let's go over how this looks for a custom field implementation. The following code is simplified code from the RecordLookupFieldclass.

Sample 1: Simple Custom Setting

The first sample is a setting that uses a simple value that does not require any further calculation. The setting is defined in the XML field definition and the SPField derived class contains a .NET property to access the setting. Because the setting does not require any calculation, the setter of the property stores the field directly in the base class using SetCustomProperty and stores the value in the thread-local storage for later retrieval in the OnAdded event.

Code Sample 10: A custom setting that does not require calculation.

C#

public
string MainDisplayColumn

{


get

{


return GetCustomProperty("MainDisplayColumn");

}


set

{

SetCustomProperty("MainDisplayColumn", value);

SetNamedThreadData("MainDisplayColumn", value);

}

}

The OnAdded method running in the second field instance is responsible for transferring the field setting from the temporary thread-based storage to the SharePoint based storage. The Update method moves the custom setting value from the internal container in the base field to the SchemaXml property and then stores everything in the database. This also makes the new value available in theMainDisplayColumn property getter. Because the Update method is called, the OnUpdated method is prevented from running.

Code Sample 11: The OnAdded event for a custom setting that does not require calculation.

C#

public override void OnAdded(SPAddFieldOptions op)

{

_preventUpdate = true;


string mainDisplayColumn = GetNamedThreadData("MainDisplayColumn");

SetCustomProperty("MainDisplayColumn", mainDisplayColumn);

Update();

ClearNamedThreadData("MainDisplayColumn");

}

The OnUpdated method may look little bit silly right now, but stay with me. When you use calculated settings this method will get bigger, so consider it a piece of framework code. In the update scenario, the user of the field is the one modifying settings and the value of the setting is stored when the user calls Update through code or the SharePoint UI. Because there is no creation of multiple field instances during update scenarios, there is no need to transfer the setting from the thread-based storage to the custom property container. However, changes to the property through the setter do add the value to the thread-local storage which you should cleared.

Code Sample 12: The OnUpdated event for a custom setting that requires calculation.

C#

public override void OnUpdated()

{


if (_preventUpdate == false)

{

_preventUpdate = true;

ClearNamedThreadData("MainDisplayColumn");

}

}

Sample 2: Calculated Custom Setting

The second sample is for a setting that is calculated when you add or update the field, also neatly stored in the Customization part ofSchemaXml. Because the value is calculated, the property only has a get-accessor. The data is stored in the Customization part of the settings storage so the GetCustomProperty method is used to access the value.

Code Sample 13: A custom setting that requires calculation.

C#

public
string MainDisplayColumn

{


get { return GetCustomProperty("MainDisplayColumn"); }

}

The OnAdded method is basically the same as shown earlier only now the value is not stored in the thread based storage but is calculated. Also, clearing the thread-based storage is not necessary because it is not used.

Code Sample 14: The OnAdded event for a custom setting that requires calculation.

C#

public override void OnAdded(SPAddFieldOptions op)

{

_preventUpdate = true;


string mainDisplayColumn = "A calculated value";

SetCustomProperty("MainDisplayColumn", mainDisplayColumn);

Update();

}

The OnUpdated method is more significant in this scenario. The field value is recalculated and stored in the custom settings cache. The Update method is called again to transfer the values to the content database.

Code Sample 15: The OnUpdated event for a custom setting that requires calculation.

C#

public override void OnUpdated()

{


if (_preventUpdate == false)

{

_preventUpdate = true;


string mainDisplayColumn = "A re-calculated value";

SetCustomProperty("MainDisplayColumn", mainDisplayColumn);

Update();

}

}

Of course, most of the time these two different samples are combined because there are multiple settings defined for the field.

Technically, you can also store your field settings in the root element of SchemaXml as do the rest of the SharePoint internals. The main reason you may want to do this is that your field settings should be available to drive the CAML rendering pattern defined in the XML field definition.

Now that we examined how to store custom properties, I want to focus on the SharePoint search control and dialog, also known as the EntityPicker.

Entity Pickers

One of the features that I find highly usable for your field implementations is the entity picker. The entity picker provides a common user interface for a search field using the well known search box and search dialog.

Figure 6. An entity picker dialog


To implement a custom entity picker you require at least three classes. First of all, you need a class deriving fromEntityEditorWithPicker, which is an ASP.NET control class. This class provides the search box and the two buttons of the basic search interface seen in Figure 3. Next, you create a class deriving from PickerDialog, which implements the dialog part of the user interface. You create the top part of the picker dialog using a specific query control, the bottom using a results control. The query control is something you implement yourself and the results control is commonly a TableResultControl. These three controls work in unison to form the dialog.

An important aspect of working with the entity picker is that an entity picker uses its own data format in the form of PickerEntityobjects. These PickerEntity objects form the data shown in the controls. You need to translate the PickerEntity-based data to real data for your field. There is a second data conversion required for working with the picker dialog. Search results in the search dialog are provided as an ADO.NET DataTable. You need to convert the DataTable to PickerEntity-based data, which will then convert to your field data later on.

PickerEntity is a simple data type that has a key, a display name, and a Boolean value indicating whether the PickerEntity is valid, known as a resolved entity. The PickerEntity class also allows you to add custom name/value pair useful for transferring arbitrary meta-data for a selected item in the search interface. The RecordPicker uses this to transfer the values of additional display columns. The key of the PickerEntity stores the GUID identifier of the selected list item.

Let's go over some of the interesting details of the components found in the RecordPicker, the entity picker used by theRecordLookupFieldControl.

The RecordPicker Class

The RecordPicker class is the main editing control. By deriving from EntityEditorWithPicker you get the search box and buttons for free, forming the familiar search interface.

Figure 7. The built-in SharePoint search field


You can set some of the properties in the RecordPicker constructor. One important property is the PickerDialogType that points to the class that implements the dialog part of the user interface.

Code Sample 16: The RecordPicker class.

C#

public
class RecordPicker

: EntityEditorWithPicker

{


public RecordPicker()

{

PickerDialogType = typeof(RecordPickerDialog);

ValidatorEnabled = true;

AllowTypeIn = true;

MultiSelect = false;

}

}

Because the RecordPicker needs to determine where it needs to lookup records, you need to configure the RecordPicker with the appropriate settings. This data should be available for the search box as well as the dialog so storing data in view-state will not suffice. The EntityEditorWithPicker base class provides the CustomProperty property, which you can use to store a string of custom data available to both the dialog and the search box.

To provide richer data than a single string you can simply use object serialization. The RecordPicker uses this to store aRecordPickerData object containing all the configuration information required.

Code Sample 17: Storing custom data for the RecordPicker.

C#

RecordPickerData _pickerData = null;

public RecordPickerData PickerData

{


get

{


if (_pickerData == null &&

String.IsNullOrEmpty(CustomProperty) == false)

{

byte[] buffer = Convert.FromBase64String(CustomProperty);


using (MemoryStream stream = new MemoryStream(buffer))

{

BinaryFormatter formatter = new BinaryFormatter();

_pickerData =

(RecordPickerData)formatter.Deserialize(stream);

}

}


return _pickerData;

}


set

{


if (value != null)

{

_pickerData = value;


using (MemoryStream stream = new MemoryStream())

{

BinaryFormatter formatter = new BinaryFormatter();

formatter.Serialize(stream, value);

CustomProperty = Convert.ToBase64String(stream.ToArray());

}

}


else

{

CustomProperty = null;

_pickerData = null;

}

}

}

The most important part of the RecordPicker code is the ValidateEntity method. You quite obviously use this method to validate aPickerEntity, which represents a selected list item in the targeted list. There are two occasions for this. First of all, when you press the validate button after entering some text this method is called with an unresolved PickerEntity that you are then asked to resolve. This method is also called when the OK button is pressed in the field editing page. To prevent validating twice, resolved PickerEntityobjects are put aside.

Code Sample 18: Validating unresolved entities.

C#

public override PickerEntity ValidateEntity(PickerEntity needsValidation)

{


if (needsValidation.IsResolved)

{


return needsValidation;

}


// real validation code

}

When an unresolved entity is passed, the assumption is that the DisplayText property of the PickerEntity contains the text the user has entered in the search box. You can safely make this assumption because the only time you are asked to validate unresolved entities is when you use the search box. The search dialog provides resolved entities. For the RecordLookupPicker the first real task is to create a CAML query for the targeted list and to get the list items that match. Because the validate button is expected to find exact matches, the CAML query uses the <Eq> element. The main display column is used for the query.

Code Sample 19: Querying the target list for the item using CAML.

C#

string fieldText = needsValidation.DisplayText;


 

SPQuery query = new SPQuery();

query.Query = String.Format(

@"<Where>

<Eq>

<FieldRef Name='{0}'/>

<Value Type='Text'>{1}</Value>

</Eq>

</Where>",

PickerData.MainDisplayColumn, fieldText);

_currentMatches = List.GetItems(query);

The last part is checking the results. A resolved entity is returned only when there is exactly one result. A static method serves as a factory because creating a PickerEntity from a SPListItem is a common task in the code of the RecordLookupField.

Code Sample 20: Verifying the search result and building a PickerEntity.

C#

switch (_currentMatches.Count)

{


case 0:

needsValidation.Description = "No match";


break;


case 1:

needsValidation = RecordPicker.CreatePickerEntity(

_currentMatches[0],

PickerData.IDFieldName,

needsValidation.DisplayText,

PickerData.MainDisplayColumn,

PickerData.AdditionalDisplayColumns,

PickerData.AdditionalDisplayColumns,

_currentMatches[0].UniqueId.ToString());


break;


 


default:

needsValidation.Description = "Multiple matches";


break;

}return needsValidation;

The RecordPickerDialog

To further facilitate in the quest for list-items you can reference using the RecordLookupField, the search dialog allows the user to search for items instead of entering them manually in the search box. The RecordPickerDialog class is responsible for this. First of all, it defines which controls to show for the query and display part of the dialog, as well as building up the results view in the dialog. Note that the results displayed in the dialog can differ from what is actually needed as the final field value. You are responsible for the value mapping between PickerEntity and field value.

The basic layout of the RecordPickerDialog is displayed below.

Code Sample 21: The RecordPickerDialog constructor.

C#

public
class RecordPickerDialog

: PickerDialog

{


public RecordPickerDialog()

: base( new RecordPickerQueryControl(),


new TableResultControl(), new RecordPicker())

{

}

}

Because the results control is a table, you need to define columns. The OnPreRender method facilitates this. This is an important part, because later on you will also define columns in the DataTable containing the query results. You can have more columns in theDataTable than are actually shown in the results control. The RecordPickerDialog uses this feature to store addition list-item values (ID and title) used for building the final result later on in the lifecycle.

Code Sample 22: Building the results table.

C#

protected override void OnPreRender(EventArgs e)

{

TableResultControl resultControl = (TableResultControl)ResultControl;

ArrayList columnDisplayNames = resultControl.ColumnDisplayNames;

ArrayList columnNames = resultControl.ColumnNames;

ArrayList columnWidths = resultControl.ColumnWidths;


// Create a display column for each field in the default view

SPView defaultView = List.DefaultView;


int percentage = (int)(100 / defaultView.ViewFields.Count);


foreach (string fieldName in defaultView.ViewFields)

{

CreateResultColumn(

columnDisplayNames, columnNames,

columnWidths, percentage, fieldName);

}


base.OnPreRender(e);

}

The RecordPickerQueryControl

The simple query control used by the RecordPickerDialog provides two controls for issuing queries. You will find a DropDownListwhere you can search for columns and a TextBox where you can enter the search query.

Figure 8. The query control in the search dialog


The first step in the lifetime of the RecordPickerQueryControl is the creation of filter columns displayed in the dropdown list. TheOnLoad method is used for this. The RecordLookupField allows you to search for values in each of the configured columns.

Code Sample 23. The query control for the RecordPickerDialog.

C#

class RecordPickerQueryControl

: SimpleQueryControl

{


protected override void OnLoad(EventArgs e)

{

EnsureChildControls();

DropDownList list = base.ColumnList;

SPList list = this.RecordPickerDialog.List;

SPField mainDisplayField = list.Fields.GetField(

PickerData.MainDisplayColumn);

list.Items.Add(


new ListItem(

mainDisplayField.Title, mainDisplayField.InternalName));


 


foreach (string additionalColumn in

PickerData.AdditionalDisplayColumns)

{

SPField field = list.Fields.GetField(additionalColumn);

list.Items.Add(


new ListItem(field.Title, field.InternalName));

}


base.OnLoad(e);

}

}

The next interesting part in the query lifetime is the IssueQuery method. You use this method to query your data store for the user's searches. The RecordPickerQueryControl uses this to query the target list using a CAML query. The query is slightly different compared to the one the RecordPicker uses during validation. The following code sample shows the parts necessary for your own field implementations because the rest of the code is rather verbose. Note that the results table should contain the columns defined earlier in the RecordPickerDialog.

Code Sample 24: Issuing a search query.

C#

protected override int IssueQuery(


string search, string groupName, int pageIndex, int pageSize)

{

DataTable resultsTable = new DataTable();


// create columns


 


// add rows


 

RecordPickerDialog.Results = resultsTable;


return resultsTable.Rows.Count;

}

The final task is to convert a DataRow into a PickerEntity used by the rest of the query field infrastructure. The conversion is performed by an overload of the GetEntity method. To do this, you instantiate a new PickerEntity and configure it appropriately. ThePickerEntity has built-in storage facilities for the data key and display text and also serves as a container for extra data. An important aspect is setting the IsResolved property indicating the PickerEntity was successfully found in the target list. This property blocks the validation method discussed earlier.

Code Sample 25: Converting a search item to a PickerEntity.

C#

public override PickerEntity GetEntity(DataRow entityDataRow)

{


if (entityDataRow == null)

{

throw new ArgumentNullException("entityDataRow");

}

PickerEntity entity = new PickerEntity();

entity.Key = (string)entityDataRow[IDColumnName];

entity.IsResolved = true;

entity.DisplayText = (string)entityDataRow[TitleColumnName];

entity.Description = (string)entityDataRow[TitleColumnName];

SPList list = RecordPickerDialog.List;


foreach (string viewFieldName in

RecordPickerDialog.RecordPicker.PickerData.AllDisplayColumns)

{

SPField viewField = list.Fields.GetField(viewFieldName);


 

entity.EntityData[viewField.InternalName] =

entityDataRow[viewField.InternalName];

}

entity.EntityData.Add(

RecordPickerDialog.RecordPicker.PickerData.IDFieldName,

entityDataRow[IDColumnName]);


return entity;

}

Conclusion

One of the extensibility points of the SharePoint framework revolves around custom field implementations encapsulating business requirements in a direct and reusable manner. The Record Lookup Field displays how to implement an advanced field for the Windows SharePoint Services 3.0 framework and shows how to make use of advanced infrastructure such as custom settings and the EntityPicker based search controls.

nice web tools English Definitions and Dictionary dutch definition and Dictionary , Nederlands definitie finnish definition and Dictionary, hollantilainen sanakirja French definition and Dictionary, le dictionnaire Français arabic definition and Dictionary, قاموس اللغة العربية hindi definition and Dictionary, शब्दकोश, हिन्दी शब्दकोश bengali definition and Dictionary, বাংলা অভিধান portuguese definition and Dictionary, dicionário de Português urdu definition and Dictionary، اردو لغت russian definition and Dictionary, русский словарь spanish definition and Dictionary, diccionario de español