How to connect Business Data Catalog (BDC) lists to other SharePoint lists
Recently I had to create Web Part connection between BDC based list and usual SharePoint list. BDC list was provider and usual SharePoint list was consumer. I was pretty surprised if found out that I was able to connect BDC based list only to other BDC based Web Parts. All the other Web Parts were not able to be consumers for them. Same time BDC lists were able to be consumers of the other web parts.
This time Google wasn't my help and documentation. Also MSDN wasn't able to help me - there were documents for everything but no texts or explanations about how and what I can use. Here is the example of perfect documentation. After hours of investigating and experimenting and hacking everything started working. As a result I created a web part that reads data from BDC list and provides it to other Web Parts.
Wrapper Web Part
The Web Part I created works as proxy between BDC lists and usual SharePoint lists. If you need common solution you can easily extend the code I will give here. Schematically works my wrapper like this.

Business Data Catalog consumer
Let's start with the darkest part of this journey - BDC consumer and its completely undocumented API. There are some things I want to say before we go and look at the code. Number one thing is - I am not 100% sure if this solution is 100% correct. But this is the way I got things to run.
In the constructor of Web Part I will initialize DataTable where we hold the value that provider part of our Web Part provides. If there is no connection from BDC list then we will provide default value to consumers of our Web Part. Consumer part of our Web Part follows ASP.NET Web Part consumers and providers way. We will define method SetConnectionPoint and accept IEntityInstanceProvider as its argument. Through this interface we are able to find out what object was selected from BDC list.
Now, here is the code.
BDC Consumer part of Web Part
using System;
using System.Collections;
using System.Data;
using System.Runtime.InteropServices;
using System.Security;
using System.Web.UI;
using AspWebParts = System.Web.UI.WebControls.WebParts;
using MdModel = Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
using Microsoft.Office.Server.ApplicationRegistry.Runtime;
using Microsoft.SharePoint.Portal.WebControls;
using SPWebParts = Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebPartPages.Communication;
using Microsoft.SharePoint;
namespace MyWebPart
{
[Guid("YOUR-GUID-HERE")]
[Obsolete()]
public class BDCWrapper : SPWebParts.WebPart, IRowProvider
{
private string serviceCaseId = string.Empty;
private string defaultValue = string.Empty;
private string filterField = string.Empty;
private DataTable filterTable;
/// <summary>
/// Constructor of class. Let's initialize data table that
/// holds filter value.
/// </summary>
public BDCWrapper()
{
this.filterTable = new DataTable();
DataColumn column = new DataColumn();
column.DataType = System.Type.GetType("System.string");
column.ColumnName = this.filterField;
column.Caption = this.filterField;
this.filterTable.Columns.Add(column);
DataRow dataRow = filterTable.NewRow();
dataRow[this.filterField] = this.defaultValue;
filterTable.Rows.Add(dataRow);
}
/// <summary>
/// Render Web Part. Write out filter field's current value.
/// </summary>
protected override void Render(HtmlTextWriter writer)
{
writer.Write(this.filterField);
writer.Write("=");
writer.Write(this.filterTable.Rows[0][this.filterField]);
}
/// <summary>
/// Connection point for Business Data Catalog Web Parts.
/// </summary>
/// <param name="provider">Connecting BDC provider.</param>
/// <remarks>
/// Keep the ID (currently Cons2193838) and don't delete it or
/// you may face somenasty problems. You can always change
/// this ID.
/// </remarks>
[AspWebParts.ConnectionConsumer("BDCConsumer", "Cons2193838")]
public void SetConnectionPoint(IEntityInstanceProvider provider)
{
// Method is called but there is no provider.
if (provider == null) return;
// Method is called but there is no selected entity.
MdModel.Entity ent = provider.SelectedConsumerEntity;
if(ent==null) return;
// Get a view we have to use to ask selected entity.
MdModel.View view = ent.GetSpecificFinderView();
if (view == null) return;
// Now we have view, let's ask entity instance.
// NB! View cannot be null!
IEntityInstance inst = provider.GetEntityInstance(view);
if(inst == null) return;
// Let's ask entity as data table.
DataTable dt = inst.EntityAsDataTable;
if(dt==null) return;
// Check if data table has filter field.
if (dt.Columns.IndexOf(this.filterField) <0) return;
// Check if data table has rows.
if (dt.Rows.Count == 0) return;
// Everything is okay, let's save filter value;
this.serviceCaseId = dt.Rows[0][this.filterField].ToString();
}
}
}
Of course, for production code you should have here also lines of code for logging anomalies. Also some error handling will be fine. I removed this code because otherwise it is too long to show here.
Consumer part of Web Part
The other part of wrapper works as row provider and is understandable to all usual SharePoint lists. I had some troubles getting it work using IWebPartRow, so I stayed on older and unfortunately deprecated IRowProvider interface. The last one of them is very well documented, by the way. I think this part of my code doesn't need further explanations, so here it is.
NB! Add this code to the end of class given below.
/// <summary>
/// Provider initialization event.
/// </summary>
[Obsolete]
public event RowProviderInitEventHandler RowProviderInit;
/// <summary>
/// Provider is ready to provide data.
/// </summary>
[Obsolete]
public event RowReadyEventHandler RowReady;
private bool connected = false;
private string connectedWebPartTitle = string.Empty;
private string consumerMsg = string.Empty;
/// <summary>
/// Register provider interfaces.
/// </summary>
[Obsolete]
public override void EnsureInterfaces()
{
try
{
RegisterInterface("MyRowProviderInterface",
InterfaceTypes.IRowProvider,
SPWebParts.WebPart.UnlimitedConnections,
ConnectionRunAt.Server,
this,
"",
"Provide filter to",
"Provides a row to a consumer Web Part.",
true);
}
catch (SecurityException ex)
{
this.consumerMsg = ex.ToString();
registrationErrorOccurred = true;
}
}
/// <summary>
/// Web Part can run only on server.
/// </summary>
[Obsolete]
public override ConnectionRunAt CanRunAt()
{
return ConnectionRunAt.Server;
}
/// <summary>
/// Web Part starts connecting.
/// </summary>
[Obsolete]
public override void PartCommunicationConnect(
string interfaceName,
SPWebParts.WebPart connectedPart,
string connectedInterfaceName,
ConnectionRunAt runAt)
{
if (interfaceName != "MyRowProviderInterface") return;
this.connected = true;
}
/// <summary>
/// Initialize communication.
/// </summary>
[Obsolete]
public override void PartCommunicationInit()
{
try
{
if (!this.connected) return;
if (RowProviderInit != null)
{
RowProviderInitEventArgs initArgs = new RowProviderInitEventArgs();
initArgs.FieldList = new string[] { this.filterField };
initArgs.FieldDisplayList = new string[] { this.filterField };
RowProviderInit(this, initArgs);
}
}
catch (Exception ex)
{
this.consumerMsg = ex.ToString();
}
}
/// <summary>
/// Called when provider is ready and consumer is waiting data.
/// </summary>
[Obsolete]
public override void PartCommunicationMain()
{
if (!this.connected) return;
if (this.RowReady == null) return;
try
{
RowReadyEventArgs initArgs;
initArgs = new RowReadyEventArgs();
initArgs.Rows = new DataRow[] { };
initArgs.SelectionStatus = "Standard";
RowReady(this, initArgs);
}
catch (Exception ex)
{
this.consumerMsg = ex.ToString();
}
}
/// <summary>
/// Returns initialization arguments.
/// </summary>
/// <param name="interfaceName">Name of interface.</param>
[Obsolete]
public override InitEventArgs GetInitEventArgs(string interfaceName)
{
if (interfaceName != "MyRowProviderInterface") return null;
try
{
EnsureChildControls();
RowProviderInitEventArgs initArgs;
initArgs = new RowProviderInitEventArgs();
initArgs.FieldList = new string[] { this.filterField };
initArgs.FieldDisplayList = new string[] { this.filterField };
return (initArgs);
}
catch (Exception ex)
{
this.consumerMsg = ex.ToString();
return null;
}
}
Now should everything be done. If you want you can use attribute consumerMsg in Render method to show consuming status of wrapper Web Part.
Conclusion
Nothing is impossible if you know reflecton and you have good sense when there is no more point to struggle in search engines hoping to find an answer. If you have any comments about this code please feel free to drop me a line here. Happy connecting!