Michael Hofer

Michael Hofer
27.03.2010
0 Kommentar(e)

In diesem Beitrag zeige ich anhand eines Beispiels, wie SharePoint-Daten mit Hilfe des neuen SharePoint 2010 Client-Object Model in eine Silverlight 3 Applikation eingebunden werden können.

Eines, der für mich wertvollsten neuen Features von Sharepoint 2010, ist das Client Object Model. Gerade bei der Entwicklung von Silverlight Web Applikationen in Verbindung mit Sharepoint erleichtert es die Arbeit wesentlich, denn bisher mussten umständlich Serveranwendungen entwickelt werden oder über die (unvollständige und umständliche) Sharepoint Webservices zurückgegriffen werden. Das Client Object Model führt eine Abstraktionsschicht ein, welche Zugriffe auf SharePoint Objekte auf gewohnte Art und Weise erlaubt – bei gleichzeitiger Optimierung der Datenübertrag. Wie das geht s möchte ich im folgenden in einem Praxis-Beispiel aufzeigen, doch der Komplettheit halber seien hier noch alle Technologien des Client Object Models aufgeführt:
  • .NET-Clients:  Microsoft.SharePoint.Client.dll (14\ISAPI)
  • Silverlight-Clients:  Microsoft.SharePoint.Client.Silverlight.dll & Microsoft.SharePoint.Client.Silverlight.Runtime.dl l (14\LAYOUTS\ClientBin)
  • JavaScript: SP.js (14\LAYOUTS)
  • In meinem Beispiel möchte ich die Content-Types einer SharePoint Site auslesen und in 2 abhängigen DropDowns anzeigen:
SilverLight-SharePoint-Client-Object-Model.png
 
Zuerst erstelle ich im Visual Studio ein neues Projekt vom Typ „Silverlight“ Applikation. Danach erstelle ich die beiden DropDowns im Designer XAML wie folgt:
 

<UserControl x:Class="SilverlightApplication1.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d"

    d:DesignHeight="300" d:DesignWidth="400" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" Loaded="UserControl_Loaded">

    <StackPanel Orientation="Vertical">

        <Grid Name="grdContentTypes" Height="Auto">

            <Grid.RowDefinitions>

                <RowDefinition Height="10" />

                <RowDefinition Height="Auto" />

                <RowDefinition Height="Auto" />

                <RowDefinition Height="Auto" />

            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="0*" />

                <ColumnDefinition Width="103*" />

                <ColumnDefinition Width="210*" />

            </Grid.ColumnDefinitions>

            <dataInput:Label Height="Auto" HorizontalAlignment="Stretch" Margin="3" Name="lblContenTypeGroups" VerticalAlignment="Center" Width="Auto" Grid.Column="1" Content="Content-Type Group:" Grid.RowSpan="2" />

            <dataInput:Label Grid.Row="2" Height="Auto" HorizontalAlignment="Stretch" Margin="3" Name="lblContentTypes" VerticalAlignment="Center" Width="Auto" Grid.Column="1" Content="ContentType:" />

            <ComboBox Grid.Column="2" Height="Auto" HorizontalAlignment="Left" Margin="3" Name="cboContentTypeGroups" VerticalAlignment="Center" Width="250" Grid.RowSpan="2" SelectionChanged="OnContentTypesGroupChanged" ItemsSource="{Binding}">

                <ComboBox.ItemTemplate>

                    <DataTemplate>

                        <TextBlock Margin="0,0,50,0" Text="{Binding Name}" />

                    </DataTemplate>

                </ComboBox.ItemTemplate>

            </ComboBox>

            <ComboBox Grid.Column="2" Grid.Row="2" Height="Auto" HorizontalAlignment="Left" Margin="3" Name="cboContentTypes" VerticalAlignment="Center" Width="250" ItemsSource="{Binding}">

                <ComboBox.ItemTemplate>

                    <DataTemplate>

                        <TextBlock Text="{Binding Name}" Margin="0,0,50,0" />

                    </DataTemplate>

                </ComboBox.ItemTemplate>

            </ComboBox>

        </Grid>

    </StackPanel>

</UserControl>


 
Nun geht’s in den Code. Als Erstes benötigen wir den SharePoint Client-Context:

public MainPage()
{
    InitializeComponent();
 
    string webUrl = "http://localhost";
    ClientContext = new ClientContext(webUrl);
}
 
public ClientContext ClientContext { get; set; }
 
Weiter geht’s wir brauchen eine Methode um die Content Types auszulesen:
 
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    SetContentTypes();
}
 
In der Methode SetContentTypes wollen wir nun die Content Types des aktuellen Webs laden und diese gruppiert nach Gruppen-Name anzeigen.
 
Client.Context.Web.AvailableContentTypes funktioniert zwar im Intellisense, wir werden aber zur Laufzeit feststellen (respektive eine Fehlermeldung erhalten), das gar keine Content Types über diese Collection abgerufen werden können. Dies liegt daran, dass wir dem ClientContext explizit sagen müssen, dass er diese Daten vom Server abrufen soll! Und nicht nur das, wir können sogar angeben, welche Eigenschaften der Content Types geladen werden sollen:
 
ClientContext.Load(
    ClientContext.Web.AvailableContentTypes,
    contentTypes => contentTypes.Include(
        contentType => contentType.Id,
        contentType => contentType.Name,
        contentType => contentType.Group,
        contentType => contentType.Hidden)
    .Where(contentType => !contentType.Hidden)
   );
 
Über die Methode ClientContext.Load können wir sowohl angeben, welche ContentType-Collection wir laden wollen, wie auch die Eigenschaften und auch noch gleich eine Abfrage/einen Filter.
 
Es gibt 2 Methoden um die Daten zu laden: ClientContext.Query und ClientContext.Load. Beide haben ihre Vor- und Nachteile und ein guter Vergleich finden Sie hier: http://praveenbattula.blogspot.com/2010/03/sharepoint-2010-loadquery-vs-load.html
 
Nun aber weiter: Wir wollen die Daten nun ja auch laden. Hier kommen die Eigenheiten von Silverlight ins Spiel, welche es nicht zulässt, dass wir einfach ClientContext.ExecuteQuery() ausführen. Wir können dies nur in einem von der UI separaten Thread machen, oder aber wir wählen die asynchrone Variante zum Laden von Daten, welche uns die Threading-Arbeit erspart und dies gleich selber übernimmt:
 
ClientContext.ExecuteQueryAsync(delegate(object sender, ClientRequestSucceededEventArgs args)
{
}, delegate(object sender, ClientRequestFailedEventArgs args) {
});
 
Nun sind die Content Types geladen und wir können sie an unsere DropDowns hängen. Zuerst einmal wollen wir aber die ContentType-Gruppen bilden:
 
cboContentTypeGroups.DataContext = (
    from ct in ClientContext.Web.AvailableContentTypes.ToList<ContentType>()
 
    where ct.Group!="_Hidden"
    group ct by ct.Group into g
    orderby g.Key
    select new ContenTypeGroup { Name = g.Key.ToString(), ContentTypes = g });
 
Kleiner Typ hier: Das .ToList<ContentType>() erscheint unnötig, da ClientContext.Web.AvailableContentTypes bereits eine Collection zurückgibt. Leider gibt es aber zur Laufzeit eine Exception „Method not support“ wenn man gruppiert. Macht man aber aus der SPContentTypeCollection eine generische Liste, dann klappt auch dies.
 
Der Rest ist relativ einfach:

cboContentTypeGroups.IsEnabled = cboContentTypeGroups.Items.Count > 0;
cboContentTypeGroups.SelectedIndex = cboContentTypeGroups.IsEnabled ? 0 : -1;
 
Wenn wir nun das Ganze testen, werden wir eine Threading-Exception erhalten. Macht eigentlich Sinn, denn die Delegate, welche ClientContext.ExecuteQueryAsync aufruft wird ja in einem eigenen Thread ausgeführt. Hier benötigen wir den Dispatcher des SilverLight UserControls. Und so sieht die fertige Methode aus:
 
private void SetContentTypes()
{
    cboContentTypeGroups.IsEnabled = false;
   cboContentTypeGroups.IsEnabled = false;
 
    ClientContext.Load(
        ClientContext.Web.AvailableContentTypes,
        contentTypes => contentTypes
        .Include(
            contentType => contentType.Id,
            contentType => contentType.Name,
            contentType => contentType.Group,
            contentType => contentType.Hidden)
        .Where(contentType => !contentType.Hidden)
        );
 
    ClientContext.ExecuteQueryAsync(delegate(object sender, ClientRequestSucceededEventArgs args)
    {
        Dispatcher.BeginInvoke(() =>
        {
             cboContentTypeGroups.DataContext = (
                from ct in ClientContext.Web.AvailableContentTypes.ToList<ContentType>()
 
                where ct.Group!="_Hidden"
                group ct by ct.Group into g
                orderby g.Key
                select new ContenTypeGroup { Name = g.Key.ToString(), ContentTypes = g });
 
             cboContentTypeGroups.IsEnabled = cboContentTypeGroups.Items.Count > 0;
             cboContentTypeGroups.SelectedIndex = cboContentTypeGroups.IsEnabled ? 0 : -1;
        });
    }, delegate(object sender, ClientRequestFailedEventArgs args) {
        Dispatcher.BeginInvoke(() =>
        {
            cboContentTypeGroups.DataContext = null;
            // TODO ErrorHandling
        });
    });
}
 
Alles, was uns jetzt noch fehlt ist ein Event-Handler für die DropDown-Liste der ContentType-Gruppen OnContentTypesGroupChanged:
 
private void OnContentTypesGroupChanged(object sender, SelectionChangedEventArgs e)
{
    if (cboContentTypeGroups.SelectedItem != null)
    {
        cboContentTypes.DataContext = ((ContenTypeGroup)cboContentTypeGroups.SelectedItem).ContentTypes.OrderBy(ct => ct.Name);
        cboContentTypes.IsEnabled = cboContentTypeGroups.Items.Count > 0;
        cboContentTypes.SelectedIndex = cboContentTypeGroups.IsEnabled ? 0 : -1;
    }
}
 
Fazit: Dieses Beispiel zeigt auf, wie einfach in Silverlight 3 (oder anderen Client-Applikationen) dank des neuen SharePoint 2010 Client Object Model mit den gewohnten SharePoint Klassen wie Web, Site, List etc. gearbeitet werden kann. In Silverlight 3 gibt es allerdings für Server-Seitige-SharePoint-Entwicklung-Gewohnte noch ein paar Schwierigkeiten zu umschiffen – trotzdem: So einfach war client-seitige Integration von SharePoint sicherlich noch nie. Ein grosses Bravo an die Entwickler!
 

Blog-Archiv

Juli 2014 (7)
Juni 2014 (2)
Mai 2014 (2)


Das könnte Sie auch interessieren:
Offene Stellen

Kommentar hinterlassen




= two + one

Kommentar(e)

Noch keine Kommentare.