在开发SharePoint项目时,经常会遇到需要一个消费者WebPart连接到多个提供者WebPart的场景。然而,ASP.NET / WSS 3.0 / MOSS 2007默认只允许一对一的连接。本文将介绍一种解决方案,使得这种多对一的连接成为可能。
几周前,在开发一个SharePoint项目时遇到了一个难题。需求是当点击列表项时,需要在列表右侧显示一些帮助/信息文本。同时,一个门户页面上可以有多个列表,且相同的帮助/信息文本WebPart需要被复用。本以为这很容易实现,因为知道ConnectedConsumerAttribute类有一个AllowMultipleConnection属性,只需要将其设置为true即可。但事实证明错了。在查阅了众多论坛、博客、维基后,发现许多人都遇到了和一样的问题。遗憾的是,未能找到问题的解决方案,这促使提出了一个潜在的解决方案(虽然在此过程中也想出了其他几种方案,但认为这是最优雅的)
这不是一个使用VS 2008开发代码的分步教程;假设已经知道如何操作。将使用代码描述解决方案的概念。开发环境如下:
请注意,如果是在Vista上使用,请以提升的权限启动VS,并不要忘记在项目调试属性中更改WSS URL。
首先,创建了一个名为ConnectedConsumerInterfaceAttribute的自定义属性类,用于标记消费者WebPart上的相关方法。这个方法将由提供者WebPart调用。
public class ConnectConsumerInterfaceAttribute : Attribute
{
private Type _interfaceType;
public ConnectConsumerInterfaceAttribute(Type interfaceType)
{
_interfaceType = interfaceType;
}
public Type InterfaceType
{
get { return _interfaceType; }
}
}
接下来,扩展了ASP.NET的WebPart类,创建了一个名为ProviderWebPart的类。所有需要连接到多连接消费者的提供者都需要继承这个新类。添加了一个名为InvokeConnectedConsumer的受保护虚拟方法,其签名如下:
protected virtual void InvokeConnectedConsumer<TInterfaceType>(TInterfaceType graph)
{
// Retrieve all the consumer web parts using the SPSHelper class...
WebPart[] consumerWebParts = SPSHelper.GetConnectedConsumerWebParts(
this.WebPartManager,
this,
typeof(TInterfaceType));
// Process all the consumer web parts...
foreach (WebPart consumerWebPart in consumerWebParts)
{
// Retrieve all the methods of the consumer web part...
MethodInfo[] methods = consumerWebParts.GetType().GetMethods(
BindingFlags.Public | BindingFlags.Instance);
// Loop through each method and check if any
// are marked with ConnectConsumerInterfaceAttribute...
foreach (MethodInfo method in methods)
{
// Retrieve all the custom attributes of the method...
object[] attributes = method.GetCustomAttributes(
typeof(ConnectConsumerInterfaceAttribute),
false);
// Loop through until we find a valid ConnectConsumerInterfaceAttribute
// - there should only be 1...
foreach (ConnectConsumerInterfaceAttribute attribute in attributes)
{
// Check if the interface types are the same...
if (typeof(TInterfaceType) == attribute.InterfaceType)
{
// Now invoke the method pass in the data...
method.Invoke(consumerWebPart, new object[] { graph });
// Now need to loop through the rest...
break;
}
}
}
}
}
然后,开发了两个WebPart,分别命名为InputWebPart(提供者)和DisplayWebPart(消费者)。
public class InputWebPart : ProviderWebPart
{
private TextBox _txtInputText;
private ITextData _textData;
public InputWebPart()
{
}
protected ITextData TextData
{
get
{
if (_textData == null)
{
_textData = new TextDataProvider();
}
return _textData;
}
set
{
_textData = value;
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
// Add a new text box...
_txtInputText = new TextBox();
this.Controls.Add(_txtInputText);
// Add a new button...
Button btnSend = new Button();
btnSend.Text = "Send";
// Wire up the click event...
btnSend.Click += new EventHandler(btnSend_Click);
this.Controls.Add(btnSend);
}
private void btnSend_Click(object sender, EventArgs e)
{
// this is required to take care of the normal SharePoint behaviour...
this.TextData.Text = _txtInputText.Text;
// this is where all the magic happens...
this.InvokeConnectedConsumer<ITextData>(this.TextData);
}
// This is required for SharePoint to allow provider to connect to consumers...
[ConnectionProvider("Text Data")]
public ITextData SetTextData()
{
return _textData;
}
}
public class DisplayWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
private ITextData _textData;
public DisplayWebPart()
{
}
protected ITextData TextData
{
get { return _textData; }
set { _textData = value; }
}
protected override void CreateChildControls()
{
base.CreateChildControls();
if (this.TextData != null)
{
Label lblDisplayText = new Label();
lblDisplayText.Text = this.TextData.Text;
this.Controls.Add(lblDisplayText);
}
}
// Required for SharePoint to allow multiple connections...
[ConnectionConsumer("Text Data Consumer", AllowsMultipleConnections = true)]
// Required for the provider to invoke multiple connected consumers...
[ConnectConsumerInterface(typeof(ITextData))]
public void GetTextData(ITextData textData)
{
this.TextData = textData;
this.EnsureChildControls();
}
}