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>

Externe Inhaltstypen – mehrere Filter im SharePoint Designer

Seit SharePoint 2010 lassen sich externe Daten relativ einfach per SharePoint Designer einbinden. Man kann dabei auch sehr einfach Filter definieren, so daß die Benutzer später die externen Daten gezielt nach einzelnen Feldern durchsuchen können. Problematisch wird es erst, wenn mehrere Filter definiert werden sollen.

Da ich gerade ziemlich lange dazu gebraucht habe, Filter für mehrere Felder so zu definieren, daß man später gezielt Datensätze nach bestimmten Kriterien durchsuchen kann, hier die korrekte Filterdefinition (leider habe ich mal wieder nur einen englischen SharePoint Designer für die Screenshots).

Ausgangspunkt ist eine Tabelle im SQL Server mit Gerätedaten. Diese sollen später in SharePoint über ein Nachschlagefeld mit einer normalen SharePoint-Liste verknüpft werden. Dabei soll es möglich sein, daß die Benutzer ein Gerät nach Gerätenummer, Typ, Modell usw. suchen können. Zu Testzwecken habe ich dazu in der Tabelle 100.000 Datensätze mit Zufallsdaten erzeugt.

Zur Konfiguration von Filtern öffnet man den externen Inhaltstyp in SharePoint Designer. Filter werden auf den Read List Operationen definiert, die man einfach per Doppelklick öffnet:

Es öffnet sich ein Dialog, bei dem man einfach auf Weiter klickt, um zur Definition der Filterparameter zu gelangen. Ein Limit-Filter sollte dort bereits vorhanden sein (Best practice). Limit-Filter begrenzen die Anzahl der zurückgegebenen Datensätze und verhindern so, daß "versehentlich" tausende von Datensätzen verarbeitet werden müssen.

Wir wollen jetzt zusätzlich mehrere Wildcard-Filter definieren. Mit einem Wildcard-Filter kann in einem Feld der Datenquelle nach Teilzeichenfolgen gesucht werden. D.h. es werden alle Datensätze gefunden, bei denen der gesuchte Text in einem Feld vorkommt (am Anfang, in der Mitte oder am Ende).

Um einen neuen Filter zu erstellen, klickt man unterhalb der Filterliste auf den Button Add Filter Parameter. Es wird ein neuer Filter angelegt, der über die Eigenschaften am rechten Rand konfiguriert werden kann:

Im oberen DropDown wird das Feld ausgewählt, auf das sich dieser Filter beziehen soll. Im unteren Feld kann ein Standardwert für den Filter angegeben werden. Bei einer SQL-Datenquelle und einem Wildcard-Filter gibt man hier ein Prozentzeichen ein. Durch Klick auf den Link über dem Default Value öffnet sich ein Dialog, mit dem weitere Einstellungen festgelegt werden:

Ganz oben wird ein Name für den Filter angegeben (der später nicht mehr geändert werden kann). Diesen Namen sehen die Benutzer später, wenn sie nach einem bestimmten Datensatz suchen (s.u.). Als Filtertyp gibt man Wildcard an und wählt nochmal das Feld aus, auf das sich der Filter beziehen soll. Weitere Einstellungen sind nicht notwendig und dürfen auch nicht gemacht werden, damit mehrere Filter später zusammenarbeiten.

Auf diese Weise kann man Filter für beliebige Felder der Datenquelle definieren. Die einzelnen Filter werden alle mit UND verknüpft, so daß sich dieses Gesamtbild ergibt:

Bei meinen Versuchen war das die einzige Konfiguration, bei der wirklich alle Filter korrekt funktionieren. Bei anderen Einstellungen ergaben sich seltsame Nebeneffekte, wie z.B. daß im falschen Feld gesucht wurde oder daß der Suchtext in allen Feldern enthalten sein mußte.

Für den Benutzer stellt sich die Auswahl dann so dar:

Über das DropDown kann ein Filter ausgewählt werden. In der Textbox rechts davon wird der zu suchende Text eingegeben und man bekommt nur noch Datensätze angezeigt, bei denen der Suchtext im entsprechenden Feld vorkommt.

Datumsvergleiche für bedingte Formatierungen in Datenansichten

Leider mußte ich eben feststellen, daß bedingte Formatierungen mit Datumsvergleichen in einer Datenansicht mit dem XsltListViewWebPart bei deutschen Ländereinstellungen nicht funktionieren.

Wenn man zum Beispiel die Zeilen einer Aufgabenliste je nach Status (abgeschlossen oder nicht) und Fälligkeitsdatum unterschiedlich einfärben möchte, dann funktioniert das nur wenn in der entsprechenden Website ein englisches Datumsformat (MM/DD/YYYY) eingestellt ist. Abweichungen davon, z.B. beim deutschen Format DD.MM.YYYY, sorgen dafür, daß die automatisch erzeugten Datumsvergleiche manchmal fehlschlagen. Ja, richtig: manchmal.

Update: das Problem wurde durch Service Pack 1 behoben.

Wenn man sich im SharePoint Designer 2010 eine bedingte Formatierung "zusammenklickt"…

…dann wird dieser XSL-Code erzeugt:

<xsl:if test="ddwrt:DateTimeTick(ddwrt:GenDisplayName(string($thisNode/@DueDate))) &lt; ddwrt:DateTimeTick(ddwrt:GenDisplayName(string(‚Today‘)))" …

Wie man sieht wird dabei mit der Funktion ddwrt:DateTimeTick gearbeitet (sie liefert die Anzahl Tage seit 01.01.1900). Ein paar Versuche haben gezeigt, daß diese Funktion nur für ein englisches Datum das richtige Ergebnis liefert. Wenn sich ein deutsches Datum englisch interpretieren läßt (z.B. der 07.04. als 4. Juli), wird ein falsches Ergebnis geliefert. Wenn sich ein deutsches Datum gar nicht englisch interpretieren läßt (z.B. der 13.04. als 4. Des dreizehnten Monats), wird einfach 0 (Null) geliefert. Das ist die Erklärung dafür, daß es eben manchmal funktioniert.

Ich habe dann Versuche mit den Funktionen ddwrt:FormatDate und ddwrt:FormatDateTime angestellt. Eigentlich wollte ich damit ein Datum im deutschen Format ins englische Format übertragen und dadurch erreichen, daß die Datumsvergleiche immer korrekt funktionieren. Leider wurde es von Microsoft bis heute nicht für notwendig befunden, die ddwrt-Erweiterungen zu dokumentieren. Die einzige Dokumentation dazu ist vom Oktober 2005 und bezieht sich auf SharePoint 2003. Sehr schade, da diese Erweiterungsfunktionen durchaus nützlich sind bzw. sein könnten.

Wie auch immer: es ist mir nicht gelungen, ein deutsches Datum ins englische Format zu übertragen. Nach ein paar weiteren Versuchen habe ich mich für folgende Lösung entschieden:

<xsl:if test="number(concat(substring(string($thisNode/@DueDate),7,4),substring(string($thisNode/@DueDate),4,2),substring-before(string($thisNode/@DueDate),‘.‘))) &lt; translate(substring-before(ddwrt:TodayIso(),’T‘),‘-‚,“) …

Dieser Vergleich macht genau dasselbe wie der oben gezeigte, funktioniert aber für das deutsche Datumsformat (allerdings nur für das).

Erklärung:

Beide Daten (Fälligkeitsdatum und aktuelles Datum) werden ins Format YYYYMMDD gebracht und das dann verglichen. Mit kleinen Änderungen, sollte sich dieses Vorgehen auf jedes beliebige Datumsformat umbauen lassen. Das Fälligkeitsdatum wird mit Stringfunktionen in seine Bestandteile zerlegt und in der richtigen Reihenfolge ohne Trennzeichen zusammengesetzt. Das aktuelle Datum erhält man von der ddwrt:TodayIso-Funktion bereits in der richtigen Reihenfolge. Es wird einfach alles nach dem ‚T‘ abgeschnitten und das Trennzeichen-Minus entfernt.