Mit XML erzeugtes Personenfeld fehlt in listdata.svc

Heute mal wieder die Lösung für ein kurioses Problem, das mich extrem viele Nerven gekostet hat. Ich hatte eine Liste, die u.a. auch über den zu SharePoint gehörenden REST-Service listdata.svc bearbeitet werden soll. Das hat alles problemlos funktioniert, bis auf die Tatsache, daß ein Personenfeld von dem Service nicht ausgegeben wurde. Die Standard-Personenfelder Erstellt von und Geändert von allerdings schon. Auch ein manuell neu angelegtes Personenfeld machte keine Probleme.

Lösung: die Liste wurde samt Inhaltstyp und allen Feldern deklarativ per XML aus einem eigenen Feature erzeugt. Der Vergleich der SchemaXml-Eigenschaft des problematischen Feldes mit derselben Eigenschaft des manuell angelegten Feldes, brachte dann die Lösung. Man muß dem Personenfeld im XML ein weiteres Attribut List="UserInfo" mitgeben, dann klappt’s auch mit dem REST.

SharePoint 2013 – Als anderer Benutzer anmelden

Jedem, der bereits SharePoint 2013 ausprobiert hat, ist wahrscheinlich aufgefallen, daß dort der Menüpunkt Als anderer Benutzer anmelden fehlt. Offenbar wurde er von Microsoft entfernt, weil er in der Vergangenheit im Produktivbetrieb öfter zu Problemen geführt hat (man meldet sich zwar im Browser als anderer Benutzer an, aber Dokumente werden in Office trotzdem mit den gespeicherten Anmeldedaten geöffnet).

Beim Entwickeln und Testen einer Anwendung ist dieser Menüpunkt aber enorm wichtig und deshalb soll hier gezeigt werden, wie man ihn auf einem Entwicklungssystem wiederbekommt. Die Lösung dafür läßt sich inzwischen sehr schnell im Web finden, weshalb dieser Beitrag mehr als Referenz für mich selbst dient.

Die eigentliche Funktion hinter dem Menüpunkt ist im Code immer noch vorhanden, einzig der Menüpunkt selbst wurde entfernt, so daß man ihn lediglich im Menücontrol wieder einfügen muß. Man öffnet dazu die Datei Welcome.ascx im Ordner 15\TEMPLATE\CONTROLTEMPLATES mit einem beliebigen Editor und fügt vor dem Menüpunkt mit der ID ID_RequestAccess folgendes ein:

<SharePoint:MenuItemTemplate runat="server" ID="ID_LoginAsDifferentUser"
  Text="<%$Resources:wss,personalactions_loginasdifferentuser%>"
  Description="<%$Resources:wss,personalactions_loginasdifferentuserdescription%>"
  MenuGroupId="100"
  Sequence="100"
  UseShortId="true"
  />

Nach dem Speichern und Neuladen einer Seite im Browser steht der Menüpunkt wieder zur Verfügung. Wenn es mehrere Server in der Farm gibt, muß die Änderung auf allen Webfrontend-Servern gemacht werden.

Achtung: die Änderungen können von Microsoft bei einem Update (Hotfix oder Service Pack) ohne weiteres überschrieben werden.

Nachtrag: Fabian Moritz hat hier eine bessere Methode beschrieben, die das Problem mit der Änderung einer SharePoint-eigenen Datei umgeht.

List-Joins und Projected Fields

In einem älteren Beitrag habe ich gezeigt, was mit CAML-Abfragen grundsätzlich möglich ist. Als Ergänzung dazu soll dieser Beitrag zeigen, wie man mit solchen Abfragen zwei oder mehr Listen verbinden kann, analog zum SQL JOIN-Statement. Man erhält dadurch mit einer einzigen Abfrage eine Ergebnisliste, die Daten aus mehreren Listen enthält. Der Mechanismus funktioniert allerdings nur, wenn die Listen durch Nachschlagefelder miteinander verbunden sind.

Motivation

Bei einem Nachschlagefeld kann man zwar weitere Felder der Nachschlageliste auswählen, die dann ebenfalls in Ansichten der Liste dargestellt werden, aber das funktioniert nur mit einer Ebene. Es funktioniert nicht, wenn Daten aus mehr als zwei Listen benötigt werden.

Und es funktioniert nur mit Standard-Nachschlagespalten. Es gibt im Web mehrere Implementierungen eigener Feldtypen, die Nachschlagefelder erweitern, z.B. um ein gefiltertes oder kaskadierendes Nachschlagen zu ermöglichen oder um das Nachschlagefeld mit einem Picker-Dialog auszustatten. Die meisten dieser Implementierungen sind vom Standard-Nachschlagefeld abgeleitet und bieten von Haus aus nicht mehr die Möglichkeit weitere Felder darzustellen. Weil die Daten aber genau wie bei den Standardfeldern gespeichert werden, können diese Felder für CAML List-Joins herangezogen werden.

Umgebung

Für die Demonstration habe ich folgende Umgebung eingerichtet: eine Liste mit Projekten und den Feldern Projektname, Projektnummer und geplantes Projektende. Und eine weitere Liste Projektzeiten, in der die Benutzer ihre Arbeitszeiten eintragen können, die sie jeweils für ein Projekt aufgewendet haben. Diese Liste hat die Felder Datum, Stunden, ein Bemerkungsfeld und natürlich ein Nachschlagefeld auf die Projekte.

Das ist die Projektliste:

Und das die Liste mit den Projektzeiten:

Für eine Übersicht sollen jetzt alle erfaßten Zeiten mit zusätzlichen Informationen aus der Projektliste dargestellt werden.

So geht’s

Man legt für die Liste Projektzeiten eine neue Ansicht an und öffnet die Website in SharePoint Designer. Wenn man über Listen und Bibliotheken auf die Liste navigiert, findet man rechts die Ansichten dieser Liste und kann diese neue Ansicht öffnen. Man markiert in der Entwurfsansicht die Tabelle und schaltet in die Codeansicht um. Jetzt sieht man die CAML-Abfrage, die dieser Ansicht zugrunde liegt. Sie sieht leicht gekürzt ungefähr so aus:

<View>
  <Query>
    <OrderBy>
      <FieldRef Name=“Projekt“/>
    </OrderBy>
  </Query>
  <ViewFields>
    <FieldRef Name=“Projekt“/>
    <FieldRef Name=“Datum“/>
    <FieldRef Name=“Stunden“/>
    <FieldRef Name=“Author“/>
  </ViewFields>
  <RowLimit Paged=“TRUE“>100</RowLimit>
  <Aggregations Value=“Off“/>
  <Toolbar Type=“Standard“/>
</View>

Diese Abfrage wird jetzt editiert. Zunächst verbindet man die Listen durch ein Join-Element, das direkt als Kind des View-Elements notiert wird:

<Joins>
<Join Type=“INNER“ ListAlias=“Projekte“>
    <Eq>
      <FieldRef Name=“Projekt“ RefType=“Id“/>
      <FieldRef List=“Projekte“ Name=“ID“/>
    </Eq>
  </Join>
</Joins>

Als Join-Type kann dabei INNER oder LEFT angegeben werden. Das Ganze funktioniert analog zum bekannten JOIN-Statement in SQL.

Als ListAlias kann ein beliebiger Name gewählt werden. Dieser Name wird später bei den ProjectedFields verwendet (s.u.). Der ListAlias ist außerdem zur Unterscheidung wichtig, falls eine Liste mehrere Nachschlagefelder auf dieselbe Liste hat. Man denke z.B. an eine Liste mit Adressen, aus der sowohl eine Rechnungs- als auch eine Lieferadresse ausgewählt werden soll. Die Liste selbst muß nicht explizit angegeben werden. Sie wird über die Definition der verknüpfenden Felder identifiziert (s.u.).

Als Vergleichsoperator muß immer Eq angegeben werden.

Der Vergleichsoperator Eq enthält immer zwei FieldRef-Elemente, die die Verknüpfung definieren. Das erste stellt dabei immer das Nachschlagefeld der primären Liste dar, das (wie immer in CAML) durch seinen internen Namen referenziert wird. Als RefType wird immer Id angegeben. Falls die primäre Liste der Verknüpfung nicht die eigentliche Liste ist, für die diese Ansicht erstellt wird, z.B. weil es eine weitere Verbindung der Projekte zu einer Kundenliste gibt, muß in einem zusätzlichen List-Attribut auch die Liste angegeben werden. Das zweite FieldRef-Element gibt die Liste an, auf die nachgeschlagen wird. Als Name wird immer ID angegeben.

Damit ist die Verbindung der Listen grundsätzlich definiert. Als nächstes müssen alle Felder der verknüpften Listen deklariert werden, die in irgendeiner Form in der Abfrage verwendet werden sollen, egal ob zur Anzeige, Sortierung oder als Filter. Das geschieht in einem ProjectedFields-Element:

<ProjectedFields>
<Field Name=“Projektname“ Type=“Lookup“ List=“Projekte“ ShowField=“Title“/>
  <Field Name=“Projektnummer“ Type=“Lookup“ List=“Projekte“ ShowField=“Projektnr“/>
  <Field Name=“Projektende“ Type=“Lookup“ List=“Projekte“ ShowField=“GeplEnde“/>
</ProjectedFields>

Jedes Field-Element gibt im Name-Attribut einen Namen an, unter dem das Feld an anderen Stellen der Abfrage identifiziert wird. Der Name wird außerdem bei angezeigten Feldern als Spaltenüberschrift verwendet. Leider muß dieser Name XML-konform sein, so daß das einzige verwendbare Sonderzeichen der Unterstrich ist. Alle anderen nicht-alphanumerischen Zeichen wie Leerzeichen, Punkt oder Komma sind nicht erlaubt.

Das Type-Attribut wird immer mit Lookup belegt.

Das List-Attribut enthält den ListAlias, der oben beim Join (s.o.) angegeben wurde.

Das ShowField-Attribut enthält wiederum den internen Namen des Feldes aus der Nachschlageliste.

Damit stehen die Felder aus der Nachschlageliste in der Abfrage zur Verfügung und können z.B. angezeigt werden. Man kann sie auch problemlos zum Sortieren oder als Filter in der Where-Bedingung verwenden. Sie werden dabei wie gewohnt als FieldRef-Elemente notiert. Als Name wird dabei der oben bei den ProjectedFields angegebene Name verwendet.

Zur Anzeige der Felder werden diese bei den ViewFields notiert:

<ViewFields>
  <FieldRef Name=“Projekt“/>
  <FieldRef Name=“Projektnummer“/>
  <FieldRef Name=“Projektende“/>
  <FieldRef Name=“Datum“/>
  <FieldRef Name=“Stunden“/>
  <FieldRef Name=“Author“/>
</ViewFields>

Die Ansicht der Projektzeiten sieht dann so aus:

Erweiterungen

Wie oben bereits angedeutet, ist dieser Mechanismus nicht auf zwei Listen beschränkt. Es lassen sich damit Daten aus vielen Listen in einem Rutsch abfragen, solange die Listen über Nachschlagefelder verbunden sind.

In unserem Beispiel wäre es denkbar, daß die Projektliste ein Nachschlagefeld auf eine weitere Liste Kunden enthält, mit dem jedes Projekt einem Kunden zugeordnet wird. Man kann dann in einer Ansicht sowohl die erfaßten Projektzeiten als auch die Daten des Projekts und des zugehörigen Kunden (z.B. seine Adresse) anzeigen.