Per EventReceiver in einer Dokumentbibliothek auf das Feld Titel zugreifen

Wenn man in einem synchronen EventReceiver (-ing) einer Dokumentbibliothek auf die Standard-Titelspalte (Title) zugreifen möchte, wird man beim ersten Versuch sehr wahrscheinlich eine Überraschung erleben. Man bekommt weder einen neu eingegebenen Wert, noch kann man dieses Feld ändern. Die Lösung für dieses Problem ist ganz einfach (wenn man es weiß): man muß dort das Feld "vti_title" verwenden.

Der Effekt tritt nur in synchronen Ereignissen wie ItemAdding oder ItemUpdating und nur in Dokumentbibliotheken auf. Normalerweise kann man bei diesen Ereignissen über

properties.AfterProperties["InternerFeldname"]

auf die neuen Werte zugreifen. Ebenso kann man die Werte setzen, indem man den gewünschten Wert als string in die AfterProperties schreibt:

properties.AfterProperties["InternerFeldname"] = "Neuer Wert"

Wenn man das mit dem Standardfeld Titel versucht, dessen interner Name immer Title ist, wird man feststellen, daß man falsche Werte erhält und daß Änderungen nicht übernommen werden. Lösung: bei den genannten Umständen (synchrones Ereignis einer Dokumentbibliothek) muß als Feldname immer "vti_title" verwendet werden.

Falls jemand eine Begründung für dieses mehr als seltsame Verhalten kennt, möge er mir diese bitte mitteilen. Würde mich brennend interessieren.

WCF-Dienst aus einem asynchronen Ereignis aufrufen

Beim Versuch einen WCF-Dienst in SharePoint aus einem asynchronen EventHandler wie z.B. ItemUpdated aufzurufen, erhält man eine Meldung darüber, daß die Authentifizierung fehlgeschlagen ist. Die Lösung ist einfach: man darf den Dienst nicht als Service Reference in Visual Studio einbinden, sondern als Web Reference.

Der Weg zum Einbinden einer Web Reference ist in Visual Studio 2010 allerdings etwas versteckt. In den Vorversionen reichte ein Rechtsklick auf das Projekt und im Kontextmenü gab es einen Menüpunkt Add Web Reference. In Visual Studio 2010 gibt es nur noch Reference und Service Reference.

Für eine Web Reference klickt man also auf Add Service Reference und es öffnet sich dieser Dialog:

Man trägt hier jetzt nichts sein und klickt stattdessen unten links auf den Button Advanced. Es öffnet sich dieser Dialog:

Auch hier wird nichts eingetragen, aber man findet unten links den gesuchten Button Add Web Reference. Ein Klick darauf öffnet den altbekannten Dialog:

Man beachte wie immer bei WCF-Diensten, daß an die URL MEX angehängt werden muß um die Metadaten zu erhalten, die Visual Studio für die Referenz braucht.

Ein auf diese Art eingebundener WCF-Dienst wird jetzt auch beim Aufruf aus einem asynchronen EventHandler richtig authentifiziert.

EventReceiver werden bei Site-Scoped Features an ALLE Listen gebunden

Wenn man EventReceiver für Listen über ein Feature mit Scope Site für eine bestimmte Liste bereitstellen möchte, werden diese an alle Listen gebunden. Das Beste daran ist aber, daß diese EventReceiver über das SharePoint-Objektmodell nicht zugänglich sind. Sie werden also von Tools wie dem SharePoint Manager nicht angezeigt. Dieses Problem hat bei uns im Kollegenkreis für sehr viel Verwirrung und einige graue Haare gesorgt.

Features mit Scope Site werden normalerweise verwendet um Websitespalten, Inhaltstypen, Webparts, Masterpages und ähnliches bereitzustellen. Also für Dinge, die in der gesamten Websitesammlung nutzbar sein sollen. Manchmal ist es aber nützlich, darüber auch Listeninstanzen und zugehörige EventReceiver bereitzustellen, damit diese bei Aktivierung im Rootweb der Websitesammlung erscheinen. Laut MSDN: Elements by Scope sollte das problemlos möglich sein, führt aber zum genannten Fehler. Exakt derselbe Code funktioniert bei einem Feature mit Scope Web reibungslos.

Vorgehensweise:

Man erstellt eine eigene Listendefinition

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <ListTemplate Name="MyList"

                Type="10001"

                BaseType="0"

                SecurityBits="11"

                Sequence="410"

                DisplayName="My List"

                Description="My List Definition"/>

</Elements>

und die zugehörige schema.xml, die sich in einem Ordner MyList befinden muß, d.h. der Ordnername muß dem Name-Attribut entsprechen. Zu beachten ist auch das Type-Attribut, das hier mit 10001 belegt wurde, um die eigene Vorlage eindeutig zu identifizieren.

Eine Instanz dieser Liste kann man dann so bereitstellen:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <ListInstance Title="My List Instance"

                OnQuickLaunch="TRUE"

                TemplateType="10001"

                Url="Lists/MyList"

                Description="My List Instance">

  </ListInstance>

</Elements>

Möchte man an diese Instanz einen EventReceiver binden, geht man normalerweise so vor (wie gesagt, bei einem Site-Scoped Feature geht das nicht):

<Receivers ListTemplateId="10001">

  <Receiver>

    <Name>MyAddingEventReceiverg</Name>

    <Type>ItemAdding</Type>

    <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>

    <Class>MyNamespace.EventReceiver</Class>

    <SequenceNumber>10000</SequenceNumber>

  </Receiver>

</Receivers>

Man beachte auch hier wieder das Attribut ListTemplateId, mit dem der EventReceiver an eine ganz bestimmte Liste gebunden werden soll. Leider wird das bei Site-Scoped Features komplett ignoriert und der EventReceiver hängt dann an ausnahmslos allen Listen und Bibliotheken. Auch die anderen Attribute des Receivers-Elements (Beschreibung) ändern daran nichts.

Lösungen:

Man kann die EventReceiver in ein Feature mit Scope Web aufnehmen. Wenn man dieses Web-Feature vom Site-Feature, das die Inhaltstypen und Listendefinitionen bereitstellt, abhängig macht, dann spielen die einzelnen Bestandteile auch reibungslos zusammen. Nachteil für den Benutzer ist aber, daß er jetzt zwei Features aktivieren muß. Außerdem kann das Web-Feature auch in beliebigen Subwebs aktiviert werden, was u.U. nicht gewünscht ist.

Die bessere Lösung ist in diesem Fall, den EventReceiver an einen Inhaltstyp zu binden. Für die eigene Listendefinition verwendet man dann auch einen eigenen Inhaltstyp (was ohnehin Best Practice entspricht) und bindet den EventReceiver einfach daran. Dieses Vorgehen funktioniert auch bei Site-Features wunderbar.

Und so wird ein EventReceiver an einen Inhaltstyp gebunden:

<ContentType ID="0x01005CFD4072A1BD472AADB25F383E4E888A"

             Name="My ContentType"

             Group="My ContentTypes">

  <FieldRefs>

    …

  </FieldRefs>

  <XmlDocuments>

    <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">

      <Receivers ListTemplateId="12004">

        <Receiver>

          <Name>MyAddingEventReceiverg</Name>

          <Type>ItemAdding</Type>

          <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>

          <Class>MyNamespace.EventReceiver</Class>

          <SequenceNumber>10000</SequenceNumber>

        </Receiver>

      </Receivers>

    </XmlDocument>

  </XmlDocuments>

</ContentType>

BeforeProperties und AfterProperties bei verschiedenen Ereignissen

Dieser Beitrag soll nicht zeigen, wie man EventReceiver für SharePoint-Listen und –Bibliotheken entwickelt. Dafür gibt es meiner Meinung nach bereits ausreichend Anleitungen (z.B. hier von Microsoft). Hier soll gezeigt werden, wie sich die BeforeProperties und AfterProperties bei verschiedenen Ereignissen verhalten.

Es geht hier auch nur um die verschiedenen Ereignisse rund um Listenelemente. SharePoint bietet weit mehr Ereignisse, die von eigenem Programmcode behandelt werden können, z.B. für Listen und für Webs.

Um Ereignisse eines Listenelements behandeln zu können, schreibt man eine Klasse, die von SPItemEventReceiver abgeleitet ist und überschreibt dort die angebotenen Methoden der Basisklasse. All diese Methoden erhalten einen Parameter vom Typ SPItemEventProperties. Über diesen Parameter lassen sich sehr viele nützliche Dinge über die Umgebung herausfinden wie z.B. die GUID der Liste, in der das Ereignis auftrat und die ID des Elements, das das Ereignis ausgelöst hat. Mit Hilfe der BeforeProperties und AfterProperties läßt sich herausfinden, welches Feld oder welche Felder des Elements sich geändert haben. Außerdem gibt es eine Eigenschaft ListItem, die Zugriff auf das zugehörige Listenelement liefert.

Leider sind diese Eigenschaften der SPItemEventProperties in den verschiedenen Ereignissen unterschiedlich belegt und obendrein unterscheiden sie sich auch noch zwischen Listen und Bibliotheken. Um einen Überblick zu schaffen und etwas zum Nachschlagen zu haben, habe ich diese Liste zusammengestellt, die die unterschiedlichen Werte von ListItem, BeforeProperties und AfterProperties bei unterschiedlichen Ereignissen gegenüberstellt.

So verhält es sich bei Listen:

Ereignis

ListItem

BeforeProperties

AfterProperties

ItemAdding

 null leer  neue Werte 

ItemAdded

neue Werte   leer  neue Werte

ItemUpdating

 alte Werte  leer  neue Werte

ItemUpdated

 neue Werte  leer  neue Werte

ItemDeleting

 allte Werte  leer  leer

ItemDeleted

 null  leer  leer

 

So verhält es sich bei Bibliotheken:

Ereignis

ListItem

BeforeProperties

AfterProperties

ItemAdding

null  leer  leer 

ItemAdded

 neue Werte  leer  leer

ItemUpdating

 alte Werte  alte Werte  neue Werte

ItemUpdated

 neue Werte  alte Werte  neue Werte

ItemDeleting

 alte Werte  leer  leer

ItemDeleted

 null  leer  leer