Die SharePoint-Web-Part-Architektur versetzt Web Parts in die Lage, Informationen zur Laufzeit untereinander auszutauschen. Dazu ein kleines Beispiel: Ein Web Part stellt Adressen in Form einer Liste dar. Wählt der Benutzer eine aus, stellt es die Adresse anderen Web Parts zur Verfügung, es fungiert als Provider. Auf der anderen Seite nimmt ein Consumer-Web Part diese Information entgegen und zeigt den vollständigen Adressdatensatz an.
Das Charmante daran ist, dass der Entwickler bei der Programmierung nicht berücksichtigten muss, welches Web Part mit welchem kommunizieren soll. Aus technischer Sicht implementieren sie bei Bedarf das Provider- und/oder Consumer-Interface. Im genannten Beispiel setzt das Adresslisten Web Part das Provider Interface um und bietet jedem die Information über die gerade selektierte Adresse. Durch die Implementierung eines Consumer Interface ist das andere Web Part in der Lage, Werte entgegenzunehmen: Liefere mir eine AddressNr und ich zeige alle Adressdaten an. Die tatsächliche Verbindung zwischen den Web Parts nimmt der Entwickler - oder fast schon treffender formuliert der Anwender - im Web-Browser oder in FrontPage vor. Dadurch lassen sich Web Parts zur Laufzeit ganz unterschiedlich miteinander kombinieren. Dieser Artikel setzt erste Erfahrungen in der Entwicklung von Web Parts voraus und bietet, darauf aufbauend, eine Einführung in verbundene Web Parts. Ein allgemeiner Einstieg in die SharePoint-Web-Part-Entwicklung ist in der Ausgabe vom Oktober 2004 des dot.net magazin [1] nachzulesen.
Schnittstellen zum Austausch
Verbundene Web Parts tauschen Daten in Form von Nachrichten (Events) untereinander aus. Dabei kann ein Web Part mehr als ein weiteres Web Part über seine Änderung informieren. Die Implementierung dieser Nachrichten ist Bestandteil der Schnittstellenvereinbarung. Dabei pictureen zwei Schnittstellen immer ein Paar, eine zum Senden und eine zum Empfangen. SharePoint bietet unterschiedliche Paare an, beispielsweise ICellProvider und ICellConsumer für den Austausch einzelner Werte oder IListProvider und IListConsumer zum Austausch ganzer Listen (Tabelle 1).| Schnittstellenpaar | Beschreibung |
| ICellProvider, ICellConsumer | Austausch einzelner Werte |
| IRowProvider, IRowConsumer | Austausch ein oder mehrer Datenzeilen |
| IListProvider, IListConsumer | Austausch ganzer Listen |
| IFilterProvider, IFilterConsumer | Austausch von Filter-Eigenschaften, wie es auch SharePoint-Listen implementieren |
| IParametersInProvider, IParametersInConsumer | Austausch von Parameter, Consumer an Provider |
| IParametersOutProvider, IParametersOutConsumer | Austausch von Parameter, Provider an Consumer |
Nur kompatible Schnittstellen lassen sich in FrontPage beziehungsweise im Browser durch den Anwender verbinden, im einfachsten Fall genau die genannten Paare. Doch die Web-Part-Infrastruktur erlaubt durch so genannte Transformatoren das Verbinden unterschiedlicher Schnittstellen, zum Beispiel IRowProvider mit ICellConsumer. Der Transformator übernimmt dabei die Zuordnung des spezifischen Wertes der Row, der an den ICellConsumer übermittelt werden soll. Dieses Mapping definiert der Anwender natürlich selbst. Wie so oft in der SharePoint-Entwicklung, spielt auch hier Frontpage 2003 eine besondere Rolle, denn nicht alle Transformatoren sind über die Weboberfläche administrierbar. In Tabelle 2 stehen die möglichen Kombinationen der Interfaces, die durch Transformatoren unterstützt werden, sowie ob sie per Browser und/oder Weboberfläche zu konfigurieren sind. Das FrontPage-Monopol erstreckt sich jedoch noch weiter, denn webseitenübergreifende Verbindungen sind ebenfalls nicht über den Browser einzurichten.
| Verbindung | per FrontPage | per Browser |
| IRowProvider an ICellConsumer | Ja | Ja |
| IRowProvider an IFilterConsumer | Ja | Ja |
| IParametersOutProvider an IParametersInConsumer | Ja | Nein |
| IRowProvider an IParametersInConsumer | Ja | Nein |
Ein weiterer Aspekt bei der Erstellung verbundener Web Parts ist die Frage, ob sie client- oder serverseitig laufen. Diese Einstellung legt der CanRunAt-Parameter fest. Webseitenübergreifende Verbindungen lassen sich beispielsweise nur realisieren, wenn sie serverseitig implementiert sind, sprich CanRunAt = ConnectionRunAt.Server beziehungsweise CanRunAt = ConnectionRunAt.ServerAndClient. Grundsätzlich ist man nur in der Lage, Web Parts zu verbinden, die auf der gemeinsamen Seite laufen. SharePoint überprüft sogar die gesamte Kette aller verbundenen Web Parts (Web Part A nutzt Web Part B und B nutzt Web Part C ) und versucht, einen gemeinsamen Nenner, client- oder serverseitig, zwischen den Web Parts auszuhandeln. Web Part-Verbindungen sind natürlich nicht per Knopfdruck clientseitig ausführbar. Um Server-Roundtrips zu vermeiden, bedarf es der Einarbeitung in das WPSC Framework (Web Part Page Services Component), mit dem SharePoint ein clientseitiges, JavaScript-basiertes Objektmodell zur Verfügung stellt [2].
Implementierung
Für die Web-Part-Entwicklung stehen im SharePoint-Bereich auf MSDN [3] Vorlagen für Visual Studio .NET zur Verfügung, mit denen sich auch eine neue Web-Part-Bibliothek anlegen lässt. Fügt man dieser eine neue Datei hinzu, stehen neben der einfachen Web-Part-Vorlage, auch Vorlagen für Consumer und Provider Web Parts zur Auswahl (Abpictureung 1). Die Entwicklung von Provider und Consumer Web Parts verläuft im Prinzip ganz analog zueinander, ebenso die Implementierung der unterschiedlichen Interface-Typen (Cell, Row, List ). Aus diesem Grund stellt der Artikel die Programmierung eines Cell-Providers exemplarisch vor. Dabei handelt es sich um ein Kalender-Web-Part, dass das aktuell ausgewählte Datum nach außen frei gibt. Listing 1 zeigt den dazugehörigen Quellcode, jedoch wurden die Kommentare aus Platzgründen auf ein Minimum reduziert. Der gesamte Quellcode basiert auf der Web-Part-Provider-Vorlage und wurde nur gezielt an wenigen Stellen angepasst. Das vollständige Beispiel, inklusive dem einfachsten aller Consumer wie es in der Abpictureung 2 zu sehen ist, steht jedoch als Download beziehungsweise auf der Heft-CD zur Verfügung.Listing 1
Quellcode Web Part Provider
[DefaultProperty("SelectedDate"),ToolboxData("<{0}:ProviderWebPart runat=server></{0}:ProviderWebPart>"),XmlRoot(Namespace="ConnectionTest")]// #1 - ICellProviderpublic class ProviderWebPart : Microsoft.SharePoint.WebPartPages.WebPart, ICellProvider {// #2 - Eventspublic event CellProviderInitEventHandler CellProviderInit;public event CellReadyEventHandler CellReady;private Calendar _calendar;// #3 - Interface registrierenpublic override void EnsureInterfaces() {try {// Register the ICellProvider interfaceRegisterInterface("CellProvider_WPQ_","ICellProvider",WebPart.UnlimitedConnections,ConnectionRunAt.Server,this,string.Empty,"Provides a cell to","Provides a cell of data");}catch(SecurityException) {}}// #4public override ConnectionRunAt CanRunAt() {return ConnectionRunAt.Server;}// #5public override void PartCommunicationConnect(string interfaceName,WebPart connectedPart, string connectedInterfaceName, ConnectionRunAt runAt) {EnsureChildControls();}// #6public override void PartCommunicationInit() {if (CellProviderInit != null) {CellProviderInitEventArgs cellProviderInitArgs = new CellProviderInitEventArgs();cellProviderInitArgs.FieldName = cellName;CellProviderInit(this, cellProviderInitArgs);}}// #7public override void PartCommunicationMain() {if (CellReady != null) {CellReadyEventArgs cellReadyArgs = new CellReadyEventArgs();cellReadyArgs.Cell = _calendar.SelectedDate;CellReady(this, cellReadyArgs);}}// #8public void CellConsumerInit(object sender, CellConsumerInitEventArgs cellConsumerInitArgs) {}// #9protected override void RenderWebPart(HtmlTextWriter output) {_calendar.RenderControl(output);}// #10protected override void CreateChildControls() {base.CreateChildControls ();_calendar = new Calendar();this.Controls.Add(_calendar);}}
Im Folgenden wird die fehlende Quellcodedokumentation durch erläuternde Sätze ersetzt, wobei sich der Text immer auf die markierten Stellen (#1, #2, ..) im Quellcode bezieht.
IcellProvider-Interface (#1): Für die Realisierung eines Providers leitet der Entwickler das Web Part wie üblich von der Web-Part-Basisklasse ab und implementiert darüber hinaus das gewünschte Interface. Es können natürlich mehrere Schnittstellen parallel angegeben werden, denn ein Web Part kann sowohl Provider als auch Consumer sein (es ist jedoch nicht möglich, dass ein Web Part sich selbst konsumiert). Die Web-Part-Infrastruktur schließt zirkuläre Referenzen durch einer Überprüfung der gesamten Part-Verkettung aus. Der Klasse vorangestellt sind die Attribute der Web-Part-Infrastruktur, DefaultProperty, ToolboxData und XmlRoot. Hier gibt es keine Besonderheiten für verbundene Web Parts.
Events definieren (#2): Das ICellProvider-Interface erwartet an dieser Stelle die Events CellProviderInit und CellReady. CellProviderInit wird in der überschriebenen Methode PartCommunicationInit() (#6) ausgelöst und signalisiert den Initialisierungsprozess des Web Part. Das CellReady-Event wird ausgelöst, wenn der zu publizierende Wert bereitsteht. Das geschieht in PartCommunicationMain() (#7). Zur Darstellung des Kalenders erfolgt zusätzlich noch der Eintrag private Calendar _calendar;. Die Instanziierung geschieht, wie in der Web-Part- und ASP.NET-Steuerelemententwicklung üblich, erst in CreateChildControls() (#10).
EnsureInterfaces (#3): Die abgeleitete Web-Part-Basisklasse stellt die Methode EnsureInterfaces() bereit, deren Aufruf vor dem Rendern erfolgt. An dieser Stelle folgt die Schnittstellenregistrierung, wodurch das Web Part erst verbindbar wird. Die Registrierung geschieht durch den Aufruf von RegisterInterface(), ebenfalls eine Methode der Basisklasse. Die Signatur der Methode zeigt Listing 2.
Listing 2
protected InterfaceProperties RegisterInterface(string interfaceName,string interfaceType,int maxConnections,ConnectionRunAt runAtOptions,object interfaceObject,string interfaceClientReference,string menuLabel,string description)
RegisterInterface() ist einmal überladen und bietet optional noch den Parameter bool allowCrossPageConnection, mit dem der Entwickler angibt, ob seitenübergreifende Verbindungen zulässig sind. Verzichtet er auf diese Angabe, setzt die Web-Part-Infrastruktur sie automatisch auf true, sofern es für das implementierte Interface zulässig ist.
InterfaceName gibt einen Namen für die Schnittstelle an. Registriert das Web Part mehrere, so müssen die Bezeichner innerhalb des Web Part eindeutig sein. Der InterfaceType-Parameter erwartet den Namen der zu registrierenden Schnittstelle in Form eines String, in diesem Beispiel ist es ICellProvider. Obwohl es sich bei maxConnection um einen Integer handelt, hat man nur die Auswahl zwischen -1 und 1, repräsentiert durch die Konstanten WebPart.UnlimitedConnections und WebPart.LimitOneConnection. Damit lässt sich festlegen, ob beliebige oder nur eine Verbindung mit einem anderen Web Part zulässig ist. Das spielt besonders bei der Verbindung zwischen öffentlichen und privaten Web Parts eine Rolle, denn öffentliche lassen sich nur mit einem privaten verbinden, wenn maxConnections auf -1, sprich UnlimitedConnections, steht. Der Parameter runAtOptions definiert, ob das Interface grundsätzlich client- und/oder serverseitige Verbindungen zulässt. Weitere Informationen folgen im nächsten Abschnitt zu der Quellcodestelle #4. Mit interfaceObject muss der Entwickler einen Zeiger auf das Objekt übergeben, das dieses Interface implementiert. An dieser Stelle steht bis auf weiteres immer this, denn schließlich ist es gerade dieses Objekt, das die Schnittstelle realisiert. Falls das Web Part clientseitige Verbindungen unterstützt, erfolgt in interfaceClientReference ein Verweis auf das zugehörige, clientseitige Objekt. Das vorgestellte Beispiel ist jedoch nur für serverseitige Verbindungen ausgelegt - daher steht hier nur ein leerer String. Die letzten beiden Angaben, menuLabel und description, machen noch einmal deutlich, wie aufwändig die Umsetzung einer konsequenten Mehrsprachigkeit von SharePoint ist, denn damit wird der Menüeintrag und eine Kurzbeschreibung des Interface festgelegt. Der Menüeintrag ist für den Anwender gedacht, der in Frontpage oder im Browser die Verbindung zwischen Web Parts administriert. Für eine bessere Lesbarkeit sollte der Eintrag mit Provides beziehungsweise Consumes oder entsprechender deutscher Übersetzung beginnen (beispielsweise Provides selected date).
ConnectionRunAt (#4): Die Festlegung, ob ein Web Part client- und/oder serverseitige Verbindungen unterstützt, geschieht in der überschriebenen Methode ConnectionRunAt(). Abhängig von der Umgebung kann der Entwickler entscheiden, ob das Web Part jetzt in der Lage ist, beispielsweise eine clientseitige Verbindung einzugehen. Eventuell bedarf es einem installierten ActiveX-Control für die clientseitige Ausführung. Sollte dieses fehlen, wäre nur eine serverseitige Verbindungen möglich. Die Festlegung geschieht über einen Wert der Enumeration ConnectionRunAt, bestehend aus Client, Server, ClientAndServer und None. Bei der Angabe von None ist das Web Part derzeit überhaupt nicht in der Lage eine Verbindung einzugehen. In dem vorgestellten Beispiel ist die Entscheidung unabhängig von der Ausführungsumgebung, daher steht im Quellcode hart codiert ConnectionRunAt.Server.
PartCommunicationConnect (#5): Die PartCommunicationConnect-Methode muss jedes verbundene Web Part überschreiben. Die Infrastruktur ruft diese Methode auf, sobald es die Verbindung zu einem anderen Web Part initialisiert. Der Entwickler erhält über die Parameter Informationen zu dem gegenüberliegenden Web Part und kann entsprechend reagieren. InterfaceName gibt den Namen der (eigenen) Schnittstelle an, mit der sich das andere Web Part verbunden hat. Das verbundene Web Part selbst ist über connectedPart direkt erreichbar. Aufgrund der Transformatoren ist es nicht eindeutig, welches Interface die gegenüberliegende Stelle gerade verwendet. Diese Information ist daher im Parameter connectedInterfaceName festgeschrieben. Zu guter Letzt folgt noch eine Aussage, ob die Verbindung client- oder serverseitig besteht. Besteht eine clientseitige Verbindung, so sollte man sich dies merken, um später das benötigte Skript generieren zu können. Bei einer serverseitigen Verbindung, wie es in diesem Beispiel der Fall ist, folgt nur der Aufruf EnsureChildControls(). Die aus der ASP.NET Steuerelemententwicklung bekannte Methode stellt sicher, dass der eigene Control-Baum zum Aufrufzeitpunkt aufgebaut ist. Die Erstellung selbst erfolgt in der CreateChildControls()-Methode (#10).
PartCommunicationInit (#6): Obwohl das Überschreiben der Methode PartCommunicationInit() optional ist, hat Microsoft es in die Standardvorlage aufgenommen. Die Methode wird von der Web-Part-Infrastruktur aufgerufen und bietet dem Web Part die Chance, seine Initialisierungs-Events auszulösen. Damit stellt es anderen Web Parts Informationen über sich zur Verfügung. Der aufgelistete Quellcode entspricht unverändert dem des Templates. Es erstellt ein CellProviderInitEventArgs-Objekt, setzt einen Klartextbezeichner für den Feldnamen und löst das in #2 definierte Event CellProviderInit aus. Damit erhalten Konsumenten schon vorab Informationen über die auszutauschenden Daten. Clientseitige Web Parts müssen diesen Event-Handler clientseitig anbieten.
PartCommunicationMain (#7): Obwohl die Implementierung dieser Methode aus technischer Sicht ebenfalls optional ist, spielt sie eine zentrale Rolle. Lässt man sie weg, erhalten Consumer keine Benachrichtigung über das selektierte Datum. Die oft zitierte Web-Part-Infrastruktur ruft diese Methode auf, wenn es Zeit ist, seine Daten an andere Web Parts zu publizieren, beispielsweise bei einem Postback. Die ICellProvider-Schnittstelle erwartet dazu das Auslösen des CellReady-Events, welches in #2 definiert ist. Das Beispiel stellt dabei ein CellReadyEventArgs-Objekt zusammen und setzt das ausgewählte Datum. Genau hier wird festgelegt, welcher Wert nach außen geht. Das CellReadyEventArgs-Objekt wird anschließend über das CellReady-Event an die Empfänger gesendet. Für eine clientseitige Verbindung ist wiederum ein clientseitiger Handler notwendig.
CellCaonsumerInit (#8): Abhängig vom implementierten Interface bedarf es der Umsetzung gewisser Event-Handler - auch wenn sie leer sind. Die ICellProvider-Schnittstelle erwartet den Handler CellConsumerInit(). Denn diesen ruft der Consumer wiederum in seiner PartCommunicationInit()-Methode auf. Die Idee ist es, dem Provider Informationen über die gegenüberliegende Cell des Consumers anzubieten, dazu erhält er das CellConsumerInitEventArgs()-Objekt.
RenderWebPart und CreateChildControls (#9, #10): Die Methoden RenderWebPart und CreateChildControls sind unabhängig von der Problematik verbundener Web Parts und ein bekanntes Konstrukt der Web-Part- und ASP.NET-Entwicklung. In CreateChildControls gilt es, den Steuerelementbaum des Web Part aufzubauen. Da dieses Beispiel recht einfach gehalten ist, wird nur die KalenderControl-Instanz erstellt und der Controls-Aufzählung hinzugefügt. Erst dann ist die Zuordnung des Click-Events bei einem Postback möglich, andernfalls gäbe es keine Reaktion auf das Klicken des Anwenders. Das Zeichnen des Kalenders geschieht dagegen in RenderWebPart - nicht zu verwechseln mit RenderControl, wie es für ASP.NET-Steuerelemente der Fall ist.
Damit ist der Quellcode des Providers vollständig und in der Lage, über das ICellProvider-Interface das selektierte Datum in Form eines String an mögliche Konsumenten zu übergeben. Der Consumer aus Abpictureung 2 nimmt den Wert entgegen und stellt ihn dar. Deutlich interessanter wäre es jedoch, anhand des Kalenderdatums eine Liste zu filtern, beispielsweise eine Adressliste nach Geburtstagen oder eine Bestellliste nach Bestelldatum.
Der Tag danach
Zugegeben, anfangs sieht alles ein wenig verwirrend und kompliziert aus. Doch dieser Artikel hat alles besprochen, was man für den Einstieg in die verbundenen Web Parts wissen muss. Am besten gibt man sich einen Ruck und springt ins kalte - inzwischen eigentlich lauwarme - Wasser: Visual Studio öffnen, Projekt erstellen, Provider und Consumer Web Part erstellen, kompilieren, deployen und in SharePoint anschauen. Die Visual-Studio-.NET-Vorlagen sind bereits vollständig lauffähig. Alternativ steht das hier vorgestellte Beispiel, wie schon erwähnt, zum Download, beziehungsweise auf der Heft-CD, zur Verfügung. Viel Erfolg!Sebastian Weber ist Software Engineer bei der Platinion GmbH und ist dort auf die Konzeption und die Implementierung von .NET-Applikationen spezialisiert. Sie erreichen ihn per E-Mail unter: weber.sebastian@platinion.de.
Links und Literatur
[1] Sebastian Weber: Die etwas andere Tauschbörse. Einführung in die SharePoint-Programmierung, in: dot.net magazin 10.2004
[2] WPSC
[3] MSDN SharePoint






