I have decided to release the custom BDC column that I created to the public domain. This custom column type provides a BDC Column type that can be created at the Site Column level and therefore used in Site Content Types. Out of the box this is not possible, and the ability to reuse BDC columns across sites and content has been identified as a common requirement. At this stage there are a number of things that are not yet implemented, including related fields, refreshing and nicer admin UI. However, I have created a Project on CodePlex so if you are interested in participating let me know! There is plenty of scope for features and function!
The new column is included in a SharePoint Solution (.wsp package) and can be deployed to SharePoint via the standard solution deployment framework.
Once installed a new column type is available to be chosen when creating columns in lists and when creating site columns:

When the BDC field type is selected the user is presented with a custom UI which displays all BDC Applications (and their Entities) registered on the current Server’s Share Services Provider. The user also has the ability to set the fields that will be displayed in the Entity picker dialog when searching.

Enough of the UI already, where's the code I hear you ask! ...
To get the data for the Application and Entity drop downs, we query the BDC metadata:
//Declare the relevent references
using Microsoft.Office.Server.ApplicationRegistry.Runtime;
using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
For the Applications…
//Get the Line of Business Instances.
NamedLobSystemInstanceDictionary sysInstances = ApplicationRegistry.GetLobSystemInstances();
cboApplications.Items.Clear();
//Loop through their names and add them to the drop down.
foreach (String name in sysInstances.Keys)
{
cboApplications.Items.Add(name);
}
For the Entities, once an application is selected…
LobSystemInstance currentInstance = sysInstances[cboApplications.SelectedValue];
//Get the Entities for the given Application.
NamedEntityDictionary currentEntities = currentInstance.GetEntities();
foreach (String name in currentEntities.Keys)
{
cboEntities.Items.Add(name);
}
Once created the column appears as a Site Column, and is available for use in Content Types.

When rendered in a list form, the field is very similar to the out of the box BDC column:
The picker dialog used to find and select Entity items is almost identical to the standard SharePoint BDC Picker. This dialog is AJAX enabled so that if AJAX is enabled on the server the user will experience the AJAX feel, ie. no page refreshes for search and selection. The fall back if AJAX is not enabled is a standard aspx style form with full page refresh required for operations such as searching. There is a great blog entry from the Microsoft SharePoint Team (via Mike Ammerlaan’s blog on the subject) on Integrating ASP.Net AJAX with SharePoint that you should read if you want AJAX to be available on you SharePoint server. If you install your AJAX enabled solution on a server where this process is not complete, it should still function in the standard ASP.Net (ie. without AJAX) way. In theory, now that SP1 is out this is supported too, although I'm not sure that configuring it is any easier...
The Pop Up is called via a JavaScript function on the book image above. This function build a URL based on the Application, Entity and Display Fields properties of the column. To save time and make the popup consistent with the standard UI, it is based on the pickerdialog.master Master page.
The fields displayed in the list view are set when the column is defined, but can be modified once the column is created by editing the settings of the new column.
We use the following code to get the list of Finders and populate the dropdown of fields to search on. Finders are defined in the XML definition of the Application and control the way data is retrieved from the external source in relation to searching.
NamedLobSystemInstanceDictionary sysInstances = ApplicationRegistry.GetLobSystemInstances();
LobSystemInstance myIns = sysInstances[Request[“BDCApplication”]];
Entity myEntity = myIns.GetEntities()[Request[“BDCEntity”]];
FilterCollection fc = myEntity.GetFinderFilters();
The GridView containing the search results is contained within an AJAX UpdatePanel so that operations that would normally cause postback (and hence full page refreshes) are handled in the background. Inside the CreateChildControls we set up the AJAX UpdatePanel, GridView and Trigger Controls.
dvUpdatePanel = new System.Web.UI.UpdatePanel();
ajaxScriptManager = new System.Web.UI.ScriptManager();
ajaxScriptManager.EnablePartialRendering = true;
Controls.Add(ajaxScriptManager);
//Fixup the UpdatePanel. This function is defined in the blog from SharePoint Team on AJAX and SharePoint.
EnsureUpdatePanelFixups();
//Create a conditional Update Panel and make sure the Children of the Panel cause updates.
dvUpdatePanel.UpdateMode = System.Web.UI.UpdatePanelUpdateMode.Conditional;
dvUpdatePanel.ChildrenAsTriggers = true;
dvUpdatePanel.ContentTemplateContainer.Controls.Add(gvResults);
//Create a postback trigger on the search button to handle the search.
System.Web.UI.AsyncPostBackTrigger uptTrigger=new System.Web.UI.AsyncPostBackTrigger();
uptTrigger.ControlID = btnGoSearch.UniqueID;
uptTrigger.EventName = "Click";
dvUpdatePanel.Triggers.Add(uptTrigger);
//Set up a new SharePoint Grid View and turn off Auto Generate so we can control the columns.
gvResults = new SPGridView();
gvResults.ID="gvResults";
gvResults.AutoGenerateColumns = false;
Searching for Entities to show in the GridView is handled using the objects and methods provided by the SharePoint object model:
NamedLobSystemInstanceDictionary sysInstances = ApplicationRegistry.GetLobSystemInstances();
LobSystemInstance myIns = sysInstances[Request[“BDCApplication”]];
Entity myEntity = myIns.GetEntities()[Request[“BDCEntity”]];
//Build up a finder collection which is the query for the BDC source.
FilterCollection fc = myEntity.GetFinderFilters();
for (int iCounter = 0; iCounter < fc.Count; iCounter++)
{
if (fc[iCounter].Name == sFinderName || sFinderName == "")
{
switch (fc[iCounter].GetType().FullName)
{
case "Microsoft.Office.Server.ApplicationRegistry.Runtime.WildcardFilter":
((WildcardFilter)fc[iCounter]).SystemIndependentValue = "*" + sQueryString + "*";
break;
case "Microsoft.Office.Server.ApplicationRegistry.Runtime.ComparisonFilter":
((ComparisonFilter)fc[iCounter]).Value = Convert.ChangeType(sQueryString, ((ComparisonFilter)fc[iCounter]).GetFilterValueType());
break;
case "Microsoft.Office.Server.ApplicationRegistry.Runtime.LimitFilter":
((LimitFilter)fc[iCounter]).Value = Math.Min(0xc9, ((LimitFilter)fc[iCounter]).MaximumValue);
break;
}
}
}
IEntityInstanceEnumerator prodEntityInstanceEnumerator = myEntity.FindFiltered(fc, myIns);
//Create a data table to store the results.
DataTable Results = new DataTable("BDCData");
Resultset.Tables.Add(Results);
//Get the collection of IDs and create an array to store their names.
IdentifierCollection EntIDS = myEntity.GetIdentifiers();
string[] sIDNames = new string[EntIDS.Count];
//Get the names.
int nCounter = 0;
foreach (Identifier currentID in EntIDS)
{
sIDNames[nCounter] = currentID.Name;
nCounter++;
}
//Add columns to the results data table named as per the Entities fields.
//Check if there any of the fields have show in picker set so we know to display only those fields.
Boolean blnShowInPickers = false;
foreach (Field f in myEntity.GetFinderView().Fields)
{
if (f.TypeDescriptor.GetProperties().ContainsKey("ShowInPicker"))
{
if (((Boolean)f.TypeDescriptor.GetProperties()["ShowInPicker"]))
{
blnShowInPickers = true;
}
}
}
DataColumn currentColumn = null;
foreach (Field f in myEntity.GetFinderView().Fields)
{
//Add the default display name to the caption if there is one so we can pull it out when we create the list.
if (f.DefaultDisplayName == "")
{
currentColumn = Results.Columns.Add(f.Name);
}
else
{
currentColumn = Results.Columns.Add(f.Name);
currentColumn.Caption = f.DefaultDisplayName;
}
if (blnShowInPickers)
{
//Only show fields that have show in picker set (if any do).
if (f.Name != sTitle)
{
if (f.TypeDescriptor.GetProperties().ContainsKey("ShowInPicker"))
{
if (((Boolean)f.TypeDescriptor.GetProperties()["ShowInPicker"]))
{
currentColumn.ExtendedProperties.Add("ShowInPicker", "TRUE");
}
else
{
currentColumn.ExtendedProperties.Add("ShowInPicker", "FALSE");
}
}
}
else
{
currentColumn.ExtendedProperties.Add("ShowInPicker", "TRUE");
}
}
else
{
//Else if the field is an identifier or it’s the title field then show it.
if (f.TypeDescriptor.ContainsIdentifier || f.Name == sTitle)
{
currentColumn.ExtendedProperties.Add("ShowInPicker", "TRUE");
}
else
{
currentColumn.ExtendedProperties.Add("ShowInPicker", "FALSE");
}
}
}
//Loop through the found Entity instances and add them to the results.
while (prodEntityInstanceEnumerator.MoveNext())
{
try
{
IEntityInstance IE = prodEntityInstanceEnumerator.Current;
DataRow newResultRow = Results.NewRow();
System.Collections.IList MyList = (System.Collections.IList)IE.GetIdentifierValues();
//Create an array to hold the identifiers.
object[] oIdentitiers = new object[sIDNames.Length];
for (int iCounter = 0; iCounter < MyList.Count; iCounter++)
{
oIdentitiers[iCounter] = MyList[iCounter].ToString();
}
foreach (Field f in myEntity.GetFinderView().Fields)
{
if (IE[f] != null)
{
newResultRow[f.Name] = IE[f];
}
}
Results.Rows.Add(newResultRow);
}
catch (Exception rowex)
{
}
}
//Grab the Display Fields and add columns for them in the Grid View.
gvResults.DataSource = Resultset;
string[] keys = Request["DisplayFields"].ToUpper().Split(',');
gvResults.DataKeyNames = keys;
gvResults.Columns.Clear();
foreach (DataColumn currentCol in ((DataTable)gvResults.DataSource).Columns)
{
string[] displayFields = Request["DisplayFields"].ToUpper().Split(',');
if (Array.BinarySearch(displayFields,currentCol.ColumnName.ToString().ToUpper())>=0)
{
BoundField newfield=new BoundField();
newfield.HeaderText=currentCol.ColumnName.ToString();
newfield.DataField=currentCol.ColumnName.ToString();
gvResults.Columns.Add(newfield);
}
}
//Bind the results to the grid.
gvResults.DataBind();
Feel free to wander over to CodePlex and grab a copy if this is a requirement for you or your clients and feel free to offer any suggestions you think of!
I'm off for a month with the Kids and the Family, so I wish you all a great Xmas, Holiday, or work time!
Posted
12-21-2007 10:02 AM
by
adrh