SharePoint Listenelement via Webservice aus Silverlight erzeugen

Wie versprochen möchte ich mich in diesem Post dem anlegen von Listeneinträgen über Webservices widmen. Im letzten Post haben wir uns ja das Abrufen von Daten mit den WSS Webservices angesehen, diesesmal betrachten wir die andere Richtung, das Schreiben von Daten.

Dazu sei zunächst auf die MSDN verwiesen, die uns beschreibt wie die XML-Nachricht auszusehen hat, die wir an den Lists.asmx Webservice senden und welche Methode dazu verwendet werden muss.

Um einen neuen Listeneintrag zu erzeugen, einen bestehenden zu löschen oder zu verändern wird die UpdateListItems-Methode des Lists.asmx verwendet. Als Eingangsparameter sind der Listenname als String und ein XML als XNode notwendig. Was beim Listennamen anzugeben ist ist denke ich offensichtlich. Interessanter ist das XML-Segment. Wir werden uns hier nur das Anlegen eines Eintrags ansehen. Die anderen Fälle verhalten sich aber genauso.

Das erforderliche XML könnte so aussehen:

<Batch OnError="Continue" ListVersion="1" ViewName="">
  <Method ID="1" Cmd="New">
    <Field Name='ID'>New</Field>
    <Field Name="Title">Dies ist ein neuer Eintrag</Field>
  </Method>
</Batch>

im Method-Element gibt es verschiedene Kommandos, die im Attribut „Cmd“ angegeben werden können. Werte für dieses Attribut sind: „New“, „Delete“ und „Update“.

Im Code muss man wieder ein XElement-Objekt erstellen:

XElement batch = new XElement("Batch",
     new XAttribute("OnError", "Continue"),
     new XAttribute("ListVersion", "1"),
     new XAttribute("ViewName", ""),
          new XElement("Method",
          new XAttribute("ID", "1"),
          new XAttribute("Cmd", "New"),
          new XElement("Field",
               new XAttribute("Name", "ID"), "New"),
               new XElement("Field",
                    new XAttribute("Name", "Title"), "Dies ist ein neuer Eintrag")
          )
     );

Das Ganze wird nun der Webservice-Methode übergeben und die Antwort asynchron verarbeitet.

wssListsProxy.UpdateListItemsAsync("ListenName", batch);

Die XML-Antwort des Webservices sieht dann übrigens ungefähr so aus:

<Results xmlns="http://schemas.microsoft.com/sharepoint/soap/">
     <Result ID="1,New">
          <ErrorCode>0x00000000</ErrorCode>
          <z:row
               ows_ID="76"
               ows_Title="Dies ist ein neuer Eintrag"
               ows_Modified="2009-08-19 10:11:01"
               ows_Created="2009-08-19 10:11:01"
               ows__ModerationStatus="0"
               ows_UniqueId="76;#{F198F027-B567-4F37-964B-6AB711EE3840D}"
               ...
               xmlns:z="#RowsetSchema" />
     </Result>
</Results>
void _wssListsProxy_GetListItemsCompleted(
                      object sender,
                      GetListItemsCompletedEventArgs e){
   if (e.Error == null){
      //Alles hat geklappt!
      //e.Result enthält alle Daten über das 
      //neu angelegte Item, wie ID, UniquID, FileLeafRef, ...
    }
   else{
     //Anhand des Fehlercodes behandeln
    }
 }

Mehr ist es nicht. Erstaunlich einfach, oder?

WSS Webservices mit Silverlight konsumieren

Nachdem wir nun wissen, dass der Silverlight-Client WSS Webservices abrufen kann ohne sich explizit an SharePoint authentifizieren zu müssen, wollen wir uns heute mal ansehen wie man gängige XML-Requests im Code formuliert und die Antworten verarbeitet.

Zunächsteinmal muss der Webservice-Proxy, den man sich über das Kontext-Menü des „References“-Ordners im Projekt erzeugen lassen kann, instanziiert werden.
Silverlight kennt nur asynchrone Webservice-Aufrufe. Daher muss auch gleich der Eventhandler für das „Completed“-Event verbunden werden.

String serviceWebsiteUrl = "http://server/Website";
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress endpointAddress =
        new EndpointAddress(serviceWebsiteUrl + "/_vti_bin/Lists.asmx");
ListsSoapClient wssListsProxy =
        new ListsSoapClient(binding, endpointAddress);
wssListsProxy.GetListItemsCompleted +=
        new EventHandler(
              wssListsProxy_GetListItemsCompleted);

Eine Übersicht über die Methoden dieses und aller anderen WSS Webservices findet man in der MSDN. Dort kann man sich über die Eingangsparameter (meist in XML oder als einfache Zeichenkette) und den Rückgabewert (immer XML) informieren.

Im .NET-Code muss XML in Form eines Objekt erzeugt und verwaltet werden. Gehen wir mal davon aus, dass Folgende Eingangsparameter gewünscht sind:
Es soll nur das ListItem angezeigt werden, dass die ID 3 hat:

Das CAML-Query:

<Query>
   <Where>
     <Lt>
       <FieldRef Name="ID" />
       <Value Type="Counter">3</Value>
     </Lt>
   </Where>
   <OrderBy>
      <FieldRef Name="ID" />
   </OrderBy>
</Query>

In der Antwort sollen nur der Titel und das Erstellungsdatum sein:
Die ViewFields:

<ViewFields>
   <FieldRef Name="Title" />
   <FieldRef Name="Created" />
</ViewFields>

Ausser den oben angegebenen sollen keine weiteren Spalten im Ergebnis der Anfrage sein. Die sogenannten MandatoryColumns sind Spalten mit Systeminformationen.
Die Query-Options:

<QueryOptions>
   <IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>
</QueryOptions>

Im C# Code würde das dann folgendermaßen aussehen:
Das CAML-Query:

XElement filteredQuery =
         new XElement("Query",
               new XElement("Where",
                      new XElement("Eq",
                             new XElement("FieldRef",
                                    new XAttribute("Name", "ID")),
                             new XElement("Value",
                                    new XAttribute("Type", "Integer"), 3))),
                             new XElement("OrderBy",
                                    new XElement("FieldRef",
                                            new XAttribute("Name", "ID"))));

Die ViewFields:

XElement requiredFields =
         new XElement("ViewFields",
               new XElement("FieldRef", new XAttribute("Name", "Title")),
               new XElement("FieldRef", new XAttribute("Name", "Created")),
               );

Die Query-Options:

XElement noMandatoryFields =
         new XElement("QueryOptions",
                    new XElement("IncludeMandatoryColumns", "FALSE"));

Nun kann der Webservice aufgerufen werden. Man kann dem Aufruf auch Infromationen zum serverseitigen Paging und der maximalen Anzahl zurückzugebender Datensätze übergeben.

wssListsProxy.GetListItemsAsync("ListenName",
                                String.Empty,
                                filteredQuery,
                                requiredFields,
                                String.Empty,
                                noMandatoryFields,
                                String.Empty);

Das ergebnis als XML sieht dann zum Beispiel so aus:
XML Antwort

<listitems xmlns:s="uuid:AD937F02-3D23-A971-AAA2-083FA0C14401"
           xmlns:dt="uuid:B3341A10-B613-D101-2A9F-14402E138A82"
           xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"
           xmlns="http://schemas.microsoft.com/sharepoint/soap/">
  <rs:data ItemCount="1">
    <z:row ows_Title="Es war einmal vor langer Zeit..."
           ows_Created="2003-06-18T03:41:09Z"
     />
  </rs:data>
</listitems>

Im Code kann man dies nun folgendermaßen verarbeiten:

void _wssListsProxy_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e){
  if (e.Error == null){
     var rows= (from item in e.Result.Elements().First().Elements()
                    select new {
                        Title = item.Attribute("ows_Title").Value;
                        Created = DateTime.Parse(row.Attribute("ows_Created").Value);
                        }).ToList();
   }
}

Soweit so gut. Das war das auslesen aus SharePoint. In meinem nächsten Eintrag sehen wir uns an, wie wir aus einem Silverlight-Client ein neues Listenelement in SharePoint erzeugen können.

Implizite Authentifizierung an WSS Webservices mit Silverlight 2.0 (!)

Seit meinem letzten Post zum Thema Silverlight ist schon einige Zeit vergangen. Heute geht es um Webservices, oder um genauer zu sein um WSS Webservices. Diese zu konsumieren ist unter Silverlight nämlich ein wenig anders als – zum Beispiel – aus ASP.NET Anwendungen oder Client-Applikationen.

Ein erstes Hindernis stellt sich einem in den Weg, wenn man versuchen möchte sich über den Authentication.asmx mit einem Benutzernamen und einem Passwort anzumelden. Für gewöhnlich klappt das recht problemlos: Man übergibt dem Authentication.asmx Webservice in seiner Login-Methode den Benutzernamen und das zugehörige Passwort und bekommt ein Authentifizierungs-Cookie zurück, welches man dann bei folgenden Webservice Aufrufen verwenden kann.

Leider klappt dies nur bei bestimmten Verbindungsmodi und nicht bei dem in Silverlight 2.0 (in Version 3.0 schon!) verfügbaren BasicHttpBinding. Glücklicherweise gibt es ein Konzept mit dem Namen „Implicit Authentication“, welches in einem Blogeintrag von Edin Kapic beschrieben wird. Dadurch wird es möglich, dass eine Silverlight-Applikation, die im Benutzerkontext eines an SharePoint authentifizierten Users läuft ohne Umwege auf die Webservices zugreifen kann. Das klappt auch bei FBA.

Toll!

Hier noch ein interessanter Link zu Allgemeinen Themen von Forms Based Authentication.