Erstaunlicherweise gibt es für Windows Azure Table Storage kaum leicht verdauliche Beispielanwendungen, die zeigen, wie man leicht auf diesen hoch-skalierbaren, hoch-verfügbaren Cloud Storage zugreifen kann. Diesem Mangel möchte ich mit diesem Blog-Artikel beseitigen.

In den folgenden Schritten soll eine kleine Beispielanwendung erstellt werden, mit deren Hilfe Einträge in Table Storage geschrieben, von dort ausgelesen, angezeigt und auch wieder gelöscht werden können. Der Sourcecode der fertigen Solution steht unten zum Download zur Verfügung. Des weiteren können auch Videos, die ich beim Erstellen der Anwendung gedreht habe heruntergeladen werden:

Die Beispielanwendung geht davon aus, dass Sie ein Cloud Projekt wie in meinem letzten Blog-Post beschrieben angelegt haben. In einem Ersten Schritt müssen in diesem Projekt die Zugriffsinformationen für den Storage, d.h. die Account-Informationen und der Name der Tabelle hinterlegt werden. Klicken Sie hierzu im Solution Explorer doppelt auf die WebRole im Konfigurationsprojekt. Es erschein die in Abbildung 1 gezeigte Eingabemaske.

image

Abbildung 1: Konfiguration des Storage Accounts (Development Storage)

Geben sie in dieser Maske die in Tabelle 1 aufgelisteten Parameter ein und speichern Sie die WebRole.

Parameter Wert Erläuterung

WAStorageConnectionString

UseDevelopmentStorage=true

Definiert den zu verwendenden Storage Account. Für Testzwecke kann auch (wie hier) der Development Storage verwendet werden.

WATableName

DemoTable

Name der Tabelle im Table Storage

Tabelle 1: Parameter für den Storage Account und die Tabelle

Fügen Sie nun noch eine Referenz zur Assembly System.Data.Service.Client dem Projekt hinzu. Klicken Sie hierzu mit der rechten Maustaste auf das WebRole-Projekt und wählen den Menüpunkt Add Reference. Es erscheint die in Abbildung 2‑4 gezeigte Eingabemaske.

image

Abbildung 2: Hinzufügen der Referenz auf die Assembly System.Data.Services.Client

Wählen Sie die Assembly System.Data.Services.Client aus und bestätigen Sie Ihre Auswahl mit OK.

Öffnen Sie nun die Datei Global.asax.cs. Fügen Sie am Anfang der Datei Referenzen auf folgende Namespaces hinzu:

...
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.ServiceRuntime;
...

Damit machen Sie dem Programm Klassen zum Zugriff auf die Service-Konfiguration sowie den Windows Azure Storage zugänglich.

Erweitern Sie nun die Methode Application_Start() wie im folgenden Listing angegeben.

void Application_Start(object sender, EventArgs e)
{
    CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
    {
        configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
    });
    var account =
        CloudStorageAccount.FromConfigurationSetting("WAStorageConnectionString");
    var tableName = RoleEnvironment.GetConfigurationSettingValue("WATableName");
    account.CreateCloudTableClient().CreateTableIfNotExist(tableName);
}

Erstellen Sie nun eine Klasse Klasse Contact.cs. Diese beschreibt letztlich den Aufbau der Einträge in der Table. Implementieren Sie diese Klasse wie folgt:

using System;
using Microsoft.WindowsAzure.StorageClient;
namespace WebRole
{
    public class Contact : TableServiceEntity
    {
        public Contact(string partitionKey, string rowKey)
            : base(partitionKey, rowKey)
        {
        }
        public Contact()
        {
            PartitionKey = "ABC";
            RowKey = string.Format("{0:10}_{1}", DateTime.Now.Ticks, Guid.NewGuid());
        }
        public string Name { get; set; }
        public string Vorname { get; set; }
        public string Email { get; set; }
    }
}

In jeder Tabellenzeile warden demnach Name, Vorname und E-Mail-Adresse der Kontakte abgelegt. Hinzu kommen die Pflicht-Propertyes PartitionKey und RowKey. Der PartitionKey wird hier mit einem festen Wert belegt. Damit hält Windows Azure alle Einträge der Tabelle in einer einzigen Partition. Für dieses Beispiel ist dies vertretbar, bei größeren Datenbeständen sollte der PartitionKey jedoch so belegt werden, dass Windows Azure die einzelnen Einträge sinnvoll auf Partitionen aufteilen kann.

Legen Sie jetzt eine Klasse ContactDataServiceContext.cs an und implementieren Sie diese wie folgt:

using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
namespace WebRole
{
    public class ContactDataServiceContext : TableServiceContext
    {
        internal string ContactTableName = 
            RoleEnvironment.GetConfigurationSettingValue("WATableName");
        internal ContactDataServiceContext(string baseAddress,
            StorageCredentials credentials)
            : base(baseAddress, credentials)
        {
        }
        public IQueryable<Contact> Contacts
        {
            get
            {
                return this.CreateQuery<Contact>(ContactTableName);
            }
        }
    }
}

Über die Property Contacts ermöglicht diese Klasse, die von TableServiceContext abgeleitet ist, den Zugriff auf den Table Storage. Die Speicherung der Kontaktdaten erfolgt in der über die Service-Konfiguration bestimmten Tabelle.

Die letzte Klasse, die benötigt wird, ist ContactDataSource.cs. Fügen Sie diese dem Projekt hinzu und implementieren Sie sie wie folgt:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace WebRole
{
    public class ContactDataSource
    {
        private ContactDataServiceContext context = null;
        public ContactDataSource()
        {
            var account =
                CloudStorageAccount.FromConfigurationSetting("WAStorageConnectionString");
            context = new ContactDataServiceContext(account.TableEndpoint.ToString(),
                                                    account.Credentials);
            context.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));
        }
        public IEnumerable<Contact> Select()
        {
            var results = from c in context.Contacts
                          select c;
            var query = results.AsTableServiceQuery<Contact>();
            var queryResults = query.Execute();
            return queryResults;
        }
        public Contact GetContactById(string PartitionKey, string RowKey)
        {
            return context.Contacts.Where(c => ((c.RowKey == RowKey) &&
                                               (c.PartitionKey == PartitionKey))).First();
        }
        public void Delete(Contact contact)
        {
            Contact item = GetContactById(contact.PartitionKey, contact.RowKey);
            context.DeleteObject(item);
            context.SaveChanges();
        }
        public void Insert(Contact contact)
        {
            context.AddObject(context.ContactTableName, contact);
            context.SaveChanges();
        }
    }
}

Die Klasse stellt also alle Methoden bereit, die zum Auslesen, Hinzufügen, Ändern und Löschen von Kontakten benötigt werden. Kompilieren Sie nun das Projekt. Dies ist wichtig, da andernfalls die Klasse später nicht Auswahl als Datenquelle für den GridView angeboten wird.

Öffnen Sie nun die Datei Default.aspx. Fügen Sie der Datei ein GridView hinzu, indem sie ein entsprechendes Element aus der Toolbox auf die Datei ziehen. Die Datei sollte dann wie in Abbildung 3 gezeigt aussehen.

image

Abbildung 3: Hinzufügen eines GridView-Controls und Auswahl der Datenquelle

Bestimmen Sie nun im Kontextmenü des GridViews (zu sehen in Abbildung 3) die Datenquelle. Wählen Sie den Menüpunkt Choose Data Source / New data source. Es erscheint die in Abbildung 4 gezeigte Eingabemaske.

image

Abbildung 4: Auswahl einer ObjectDataSource

Wählen Sie hier als Datenquelle Object und benennen die Datenquelle mit ObjectDataSource. Bestätigen Sie Ihre Eingabe mit OK. Es erscheint die in Abbildung 5 gezeigte Eingabemaske.

image

Abbildung 5: Auswahl des Business Objekts

Wählen Sie hier als Business-Objekt die Klasse WebRole.ContactDataSource und bestätigen Sie Ihre Eingabe mit Next.

image

Abbildung 6: Auswahl der Business Methoden für Auswahl, Einfügen und Löschen

Bestimmen Sie nun die Methoden für die einzelnen Datenoperationen, d.h. für die Operation Select die Methode Select(), für Insert die Methode Insert() usw. Schließen Sie Ihre Eingabe mit Finish ab. Mit diesen Eingaben stehen dem GridView alle Informationen für die Anzeige der Kontaktdaten zur Verfügung. Wenn Sie im Kontextmenü des GridViews noch die Option EnableDeleting auswählen, sollte der Zwischenstand wie in Abbildung 7 gezeigt aussehen.

image

Abbildung 7: Zwischenstand nach dem Einfügen der GridView

Nun wird noch eine Möglichkeit benötigt, um neue Kontakte anzulegen. Fügen Sie hierzu eine FormView in die Seite ein, indem Sie ein entsprechendes Element aus der Toolbox auf die Seite Default.aspx ziehen, und wählen Sie die gleiche Datenquelle wie beim GridView. Bearbeiten Sie im FormView nun das InsertItemTemplate. Wählen sie im Kontextmenü des FormViews den Eintrag Edit Templates und wählen das InsertItemTemplate. Löschen Sie aus der Ansicht nun die automatisch generierten Eingabefelder für PartitionKey, RowKey und Timestamp. Diese sollen ja später nicht vom Benutzer eingegeben sondern vom System erzeugt werden. Damit sollte das FormView wie in Abbildung 8 gezeigt aussehen.

image

Abbildung 8: Entfernung von Attributen aus dem InsertItemTemplate

Nun sind noch ein paar Änderungen im Code der Default.aspx erforderlich. Wechseln sie hierzu in die Code-Ansicht der Seite. Setzen Sie zunächst im GridView die Schlüsselattribute für die Kontakte. Ergänzen Sie das GridView-Element wie folgt.

...
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <asp:GridView
        ID="GridView1"
        runat="server"
        AutoGenerateColumns="False" 
        CellPadding="4"
        DataSourceID="ObjectDataSource"
        ForeColor="#333333" 
        GridLines="None"
        DataKeyNames="PartitionKey,RowKey">
...

PartitionKey und RowKey bilden zusammen den Primärschlüssel eines Kontakt-Eintrags. Diese Angabe ist erforderlich, damit bei Auswahl der vollständige Schlüssel übergeben werden kann.

Setzen Sie nun noch wie folgt den Eingabemodus als Standard für die Anzeige der FormView.

...
    <asp:FormView
        ID="FormView1"
        runat="server"
        DataSourceID="ObjectDataSource"
        DefaultMode="Insert">
...

Der Code der Datei Default.aspx sollte nun in etwa wie folgt aussehen.

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="WebRole._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <asp:GridView
        ID="GridView1"
        runat="server"
        AutoGenerateColumns="False" 
        CellPadding="4"
        DataSourceID="ObjectDataSource"
        ForeColor="#333333" 
        GridLines="None"
        DataKeyNames="PartitionKey,RowKey">
        <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
        <Columns>
            <asp:CommandField ShowDeleteButton="True" />
            <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
            <asp:BoundField DataField="Vorname" HeaderText="Vorname" 
                SortExpression="Vorname" />
            <asp:BoundField DataField="Email" HeaderText="Email" SortExpression="Email" />
            <asp:BoundField DataField="Timestamp" HeaderText="Timestamp" 
                SortExpression="Timestamp" />
            <asp:BoundField DataField="PartitionKey" HeaderText="PartitionKey" 
                SortExpression="PartitionKey" />
            <asp:BoundField DataField="RowKey" HeaderText="RowKey" 
                SortExpression="RowKey" />
        </Columns>
        <EditRowStyle BackColor="#999999" />
        <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
        <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
        <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
        <SortedAscendingCellStyle BackColor="#E9E7E2" />
        <SortedAscendingHeaderStyle BackColor="#506C8C" />
        <SortedDescendingCellStyle BackColor="#FFFDF8" />
        <SortedDescendingHeaderStyle BackColor="#6F8DAE" />
    </asp:GridView>
    <asp:FormView
        ID="FormView1"
        runat="server"
        DataSourceID="ObjectDataSource"
        DefaultMode="Insert">
        <InsertItemTemplate>
          Name:
          <asp:TextBox ID="NameTextBox" runat="server" Text='<%# Bind("Name") %>' />
          <br />
          Vorname:
          <asp:TextBox ID="VornameTextBox" runat="server" Text='<%# Bind("Vorname") %>' />
          <br />
          Email:
          <asp:TextBox ID="EmailTextBox" runat="server" Text='<%# Bind("Email") %>' />
          <br />
          <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" 
                CommandName="Insert" Text="Insert" />
        </InsertItemTemplate>
    </asp:FormView>
    <asp:ObjectDataSource ID="ObjectDataSource" runat="server" 
        DataObjectTypeName="WebRole.Contact" DeleteMethod="Delete" 
        InsertMethod="Insert" SelectMethod="Select" 
        TypeName="WebRole.ContactDataSource"></asp:ObjectDataSource>
</asp:Content>

Damit ist die Anwendung fertig. Starten Sie einen Testlauf, indem Sie den Menüpunkt Debug / Start Debugging auswählen. Nach einem kurzen Moment sollte die Startseite wie in Abbildung 9 zu sehen angezeigt werden.

image

Abbildung 9: Erstmalige Ausführung der Table Storage Anwendung

Da in der Tabelle noch keine Einträge vorhanden sind, wird die Tabelle nicht angezeigt. Es erscheint nur die Eingabemaske. Tragen Sie in diese einen Eintrag ein und bestätigen Sie Ihre Eingabe mit Insert. Anschließend sollte auch die GridView mit dem Tabelleneintrag wie in Abbildung 10 zu sehen angezeigt werden.

image

Abbildung 10: Table Storage Anwendung, nachdem ein Eintrag eingefügt wurde

Weitere Informationen