The method AddWebPartToPage() that was in one of the code fragments in the last post does 2 things: it creates a web part, then adds it to the page into the target web part zone. All manipulations with a web part on a page are governed by the SPLimitedWebPartManager class, which is a scaled down version of SPWebPartManager class designed to do the same thing but without HTTP context available. I think that indeed Microsoft did well here: thanks to this type being available a lot of configuration tasks can be automated, including adding web parts to a page, and connecting web parts to each other. The AddWebPartToPage() method is shown below:
The CreateWebPart() method first constructs a CAML query, which will look up the web part in a web part gallery. Certainly this means that for the method to work the web part must already be in the web part gallery. Well this is easy to do - the site creation feature within the context of which we are operating should depend on a feature installing the web part we intend to use. The dependency can be specified in the feature manifest file using ActivationDependency element. When I was specifying the web parts in our XML configuration file (see my last post) I chose to use the web part definition files (the ones with .dwp or .webpart extensions). These files are created for custom web parts manually or automatically if you have Visual Studio WSS 3.0 Extensions installed, or for the out-of-the-box web parts, which are a part of SharePoint installation they can be found here: %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES. Getting back to the CAML query though, it returns the web part gallery items whose definition files match the passed parameter. The field used to filter the query is FileLeafRef. Next the type and assembly of the web part are determined from the other fields of the gallery item - the WebPartTypeName and WebPartAssembly and the instance of the web part is created through reflection. Here is complete implementation of the method:
public string AddWebPartToPage(
SPWeb web,
string pageUrl,
string webPartName,
string zoneID,
int zoneIndex)
{
using (System.Web.UI.WebControls.WebParts.WebPart webPart =
CreateWebPart(web, webPartName))
{
using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(
pageUrl, PersonalizationScope.Shared))
{
manager.AddWebPart(webPart, zoneID, zoneIndex);
return webPart.ID;
}
}
}
On to SetWebPartProperty() method. In the XML configuration file the property element only has name and value attributes. When setting a property programmatically the property to be set is looked up by its name using reflection, then its value obtained from the value attribute is converted to the type of the property and assigned to it. I do not list the implementation of ConvertValue() method here - it can be either trivial or quite complex, depending on the need. I have opted to switch between the expected property types and throw an exception for unexpected ones. This solution easily handles properties with the intrinsic value types and enumeration types. The SetWebPartProperty() method implementation is shown in the code fragment below:
private System.Web.UI.WebControls.WebParts.WebPart
CreateWebPart(SPWeb web, string webPartName)
{
SPQuery query = new SPQuery();
query.Query = String.Format(
"<Where><Eq><FieldRef Name='FileLeafRef'/><Value Type='File'>{0}</Value></Eq></Where>",
webPartName);
SPList webPartGallery = null;
if (null == web.ParentWeb)
{
// This is the root web.
webPartGallery = web.GetCatalog(
SPListTemplateType.WebPartCatalog);
}
else
{
// This is a sub-web.
webPartGallery = web.ParentWeb.GetCatalog(
SPListTemplateType.WebPartCatalog);
}
SPListItemCollection webParts = webPartGallery.GetItems(query);
string typeName = webParts[0].GetFormattedValue("WebPartTypeName");
string assemblyName = webParts[0].GetFormattedValue("WebPartAssembly");
ObjectHandle webPartHandle = Activator.CreateInstance(
assemblyName, typeName);
System.Web.UI.WebControls.WebParts.WebPart webPart =
(System.Web.UI.WebControls.WebParts.WebPart)webPartHandle.Unwrap();
return webPart;
}
Finally all what's left is connecting the web parts. Again SPLimitedWebPartManager comes at help here. The method AddWebPartConnection() first locates consumer and provider web parts by their unique IDs, which were captured earlier at a time of placing the web parts on a page; next it iterates through provider and consumer connection points to find the ones specified in the XML configuration file. By the way, these are the same as the constructor parameters used with ConnectionProviderAttribute and ConnectionConsumerAttribute types, which are used to decorate methods participating in a web part connection. When both connection points are determined we can call the SPConnectWebParts() method of SPLimitedWebPartManager to create the connection. The method implementation is shown below:
public void SetWebPartProperty(
SPWeb web,
string pageUrl,
string webPartID,
string propertyName,
string propertyValue)
{
using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(
pageUrl, PersonalizationScope.Shared))
{
System.Web.UI.WebControls.WebParts.WebPart part =
manager.WebParts[webPartID];
Type runtimeType = part.GetType();
PropertyInfo property =
runtimeType.GetProperty(propertyName);
object value = ConvertValue(propertyValue, property.PropertyType);
property.SetValue(part, value, null);
manager.SaveChanges(part);
}
}
One last note: when putting the code fragments together I was removing the "defensive programming logic" such as checking for nulls or throwing exceptions on unexpected values - this way the examples became less cluttered. Any practical implementation of the ideas described in this and previous posts therefore would require creative refactoring of these examples. I hope though I managed to save you some time that I spent looking through SDK documentation, blogs and decompiling the source code...
public void AddWebPartConnection(
SPWeb web,
string pageUrl,
string providerWebPartID,
string consumerWebPartID,
string providerConnectionPointName,
string consumerConnectionPointName)
{
using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(
pageUrl, PersonalizationScope.Shared))
{
System.Web.UI.WebControls.WebParts.WebPart provider =
manager.WebParts[providerWebPartID];
System.Web.UI.WebControls.WebParts.WebPart consumer =
manager.WebParts[consumerWebPartID];
ProviderConnectionPointCollection providerPoints =
manager.GetProviderConnectionPoints(provider);
ConsumerConnectionPointCollection consumerPoints =
manager.GetConsumerConnectionPoints(consumer);
ProviderConnectionPoint providerPoint = null;
foreach (ProviderConnectionPoint point in providerPoints)
{
if (String.Equals(
providerConnectionPointName,
point.DisplayName,
StringComparison.OrdinalIgnoreCase))
{
providerPoint = point;
break;
}
}
ConsumerConnectionPoint consumerPoint = null;
foreach (ConsumerConnectionPoint point in consumerPoints)
{
if (String.Equals(
consumerConnectionPointName,
point.DisplayName,
StringComparison.OrdinalIgnoreCase))
{
consumerPoint = point;
break;
}
}
manager.SPConnectWebParts(
provider,
providerPoint,
consumer,
consumerPoint);
}
}