Im ersten Teil und zweiten Teil dieser Blog Post Serie wurde eine Beispielapplikation für die Windows Azure Plattform skizziert. Bisherige Bestandteile sind eine ASP.NET MVC 2 Web Rolle und ein Repository für den Table Storage.
In diesem Teil wird der Windows Azure Blob Storage vorgestellt, wobei die Beispielapplikation mit einem Repository für den Blob Storage erweitert wird. Somit können jetzt auch Bilder an die Kontakte angehängt werden. Außerdem steht die Beispielapplikation nun als ASP.NET MVC 3 Version zum Download bereit.
Windows Azure Blob Storage
Neben dem, im zweiten Teil vorgestellten, Table Storage, bietet Windows Azure einen weiteren sicheren, skalierbaren und leicht zugänglichen Speicherdienst – Den Blob Storage.
Der Windows Azure Blob Storage bietet einen dauerhaften Speicher für große Text- oder Binärdateien (sogenannte Binary Large OBjects oder kurz BLOBs). Somit kann ein zentraler Dateizugriff, von mehreren Azure-Rollen bzw. Nicht-Azure Endpunkten aus, erfolgen.
Auch bei diesem Speicherdienst wird eine hohe Fehlertoleranz, durch die Dreifach-Replizierung der Dateien in verschiedene Fehlerdomänen, erzielt.
Der Zugriff kann über das Windows Azure Storage Services REST API oder die Windows Azure Managed Library (Namensraum Microsoft.WindowsAzure.StorageClient) des Windows Azure SDKs erfolgen.
Speicherkonzepte im Blob Storage
Blob Container
Neben dem eigentlichen Speicherkonto (Storage Account), muss, für das Ablegen der Blobs, ein Container erstellt werden. Pro Speicherkonto können beliebig viele Container erstellt werden.
Zu einem Container können bis zu 8KB Zusatzinformationen (Metadaten in Form von Schlüssel / Wert Paaren) gespeichert werden.
Bei der Benennung von Containern gelten folgende Regeln:
- Containernamen müssen mindestens 3, maximal 63 Zeichen lang sein.
-
Containernamen müssen mit einem Buchstaben oder einer Zahl beginnen.
Danach können Buchstaben, Zahlen oder der Bindestrich (-) verwendet werden. - Vor und hinter jedem Bindestrich (-) muss mindestens ein Buchstabe oder eine Zahlen stehen.
- Containernamen dürfen nur Kleinbuchstaben enthalten.
Auf der Container-Ebene kann dann auch die erste Zugriffssicherung erfolgen.
Standardmäßig sind Container privat, d.h. es kann nur mittels Zugriffsschlüssel darauf zugegriffen werden.
Mit der SetPermissions Methode der CloudBlobContainer Klasse, kann jedoch ein Container auch für anonyme Zugriffe freigegeben werden:
// Client für das Speicherkonto erstellen // Die Zugangsdaten werden hierzu // aus der Rolleneinstellung // "AzureStorageConnectionString" gelesen. var storageAccount = CloudStorageAccount .FromConfigurationSetting("AzureStorageConnectionString"); var storageClient = storageAccount.CreateCloudBlobClient(); // Referenz auf den Container erzeugen. var containerOeffentlich = storageClient .GetContainerReference("http://demo.blob.core.windows.net/bilder"); // Erstelle den Container, falls dieser nicht vorhanden ist. containerOeffentlich.CreateIfNotExist(); // Container-Rechte für vollen anonymen Zugriff setzen var permissions = containerOeffentlich.GetPermissions(); permissions.PublicAccess = BlobContainerPublicAccessType.Container; containerOeffentlich.SetPermissions(permissions);
Mögliche Optionen für die PublicAccess Eigenschaft sind:
-
BlobContainerPublicAccessType.Off
Verhindert anonyme Zugriffe. (Standardwert) -
BlobContainerPublicAccessType.Blob
Erlaubt anonymen Lesezugriff auf die Blobs, aber nicht auf den Container selbst.
Somit können weder Container-Metadaten ausgelesen, noch eine Liste der Blobs abgefragt werden. -
BlobContainerPublicAccessType.Container
Erlaubt anonymen Lesezugriff auf die Blobs, die Container-Metadaten und die Auflistung der Blobs im Container.
Verzeichnisstruktur
Um einen Blob im Windows Azure Storage ansprechen zu können, muss eine URL nach folgendem Schema benutzt werden:
http(s)://<AccountName>.blob.core.windows.net/<ContainerName>/<BlobName>
Um also das Wolken.mpg Blob (siehe Abbildung oben) verwenden zu können, muss folgende URL verwendet werden:
http://demo.blob.core.windows.net/videos/Wolken.mpg
Unterverzeichnisse
Der Container stellt die einzige Ebene der Verzeichnisstruktur des Blob Storages dar.
Allerdings kann man mit einem kleinen Trick weitere Verzeichnisebenen simulieren.
Das Slash (/) ist ein zulässiges Zeichen in Blobnamen.
Somit können Unterverzeichnisse durch entsprechende Blobnamen "erzeugt" werden.
Beispiel:
Storage Account: demo
Container: videos
Blobname: tiere/pferde.mpg
Resultierende URL: http://demo.blob.core.windows.net/videos/tiere/pferde.mpg
Root Container
In manchen Fällen, wie z.B. bei Silverlight Anwendungen, muss eine Blob im Stammverzeichnis angelegt werden.
Hierzu gibt es einen speziellen Container mit dem Namen $root.
Wenn ein Blob unter folgender Adresse gespeichert wird:
http://demo.blob.core.windows.net/$root/clientaccesspolicy.xml
Kann dieser, wie folgt, gelesen werden:
http://demo.blob.core.windows.net/clientaccesspolicy.xml
Blobs
Im Windows Azure Blob Storage wird zwischen 2 verschiedenen BLOB-Typen bzw. Zugriffsartenunterschieden:
- Block Blob
-
Page Blob
-
Windows Azure Drives
Windows Azure Drives verwenden intern einen Page Blob.
(Dazu mehr in einem separaten Blog Post)
-
Windows Azure Drives
Über welche Zugriffsart ein Blob angesprochen werden kann, wird bei der Erstellung des Blobs festgelegt.
Wenn keine Zugriffsart explizit angegeben wird, wird automatisch der Blob als Block Blob erstellt.
Unabhängig von der Zugriffsart, gilt für Blobs folgendes:
- Der Name eines Blobs muss mindestens 1 Zeichen, maximal 1024 Zeichen, lang sein.
- Bei der Erstellung und jeder Veränderung eines Blobs, erhält dieser eine eindeutige Kennzeichnung (Etag)
-
Um exklusiven Schreibzugriff zu erhalten, kann ein Blob Leasing durchgeführt werden.
(Dazu mehr in einem separaten Blog Post) - Über die Basisklasse CloudBlob, können Operationen, wie z.B. Kopieren, Löschen, Snapshots erzeugen oder diverse allgemeine Up- / Download-Methoden, ausgeführt werden.
Block-Blobs
Block Blobs sind besonders für Streaming Szenarien geeignet.
Sie bestehen aus einer Sequenz an Speicherblöcken und haben eine Maximalgröße von 200 MB:
Wenn ein Blob nicht größer als 64MB ist, kann dieser mit nur einer Schreiboperation hochgeladen werden. (Im Storage Client ist dies standardmäßig auf 32 MB beschränkt, kann aber über die SingleBlobUploadThresholdInBytes Eigenschaft verändert werden)
Unabhängig von der Größe, können Blobs blockweise hochgeladen werden:
- Jeder dieser Blöcke wird über eine eindeutige, 64 Byte große, Block ID identifiziert.
- Jeder dieser Blöcke kann bis zu 4MB groß sein.
- Die einzelnen Blöcke können unterschiedlich groß sein.
- Ein Block Blob kann aus maximal 50.000 bestätigten Blöcken bzw. 100.000 unbestätigten Blöcken bestehen.
- Mehrere Blöcke können gleichzeitig hochgeladen werden.
- Einzelne Blöcke können auch mehrfach übertragen werden, wie z.B. bei fehlerhaften Übertragen.
Sobald alle Blöcke übertragen wurden, kann der Vorgang durch einen Commit abgeschlossen werden. Dieser Übertragungsvorgang kann jederzeit unterbrochen, und zu einem späteren Zeitpunkt fortgeführt, werden. Nach einer Woche werden unbestätigte Blöcke automatisch verworfen.
Um die Beispielanwendung dieser Blog Post Serie nicht unnötig komplex werde zu lassen, beschränke ich mich im BlobFotoRepository auf die Verarbeitung von ganzen Blobs (<32MB).
Wie man blockweise Blobs verarbeiten kann, werde ich in einem der nächsten Blog Posts gesondert vorstellen.
Page-Blobs
Page Blobs sind besonders für flexible Schreib- und Lesezugriffe geeignet.
Sie bestehen aus einem Array von 512 Byte großen Pages und können maximal 1TB groß sein:
Jede Page wird hierbei über den Offset vom Blob-Anfang identifiziert.
Auch hier können bis zu 4MB Daten pro Schreiboperation übertragen werden.
Im Gegensatz zu Block Blobs, müssen Schreiboperationen bei Page Blobs nicht gesondert bestätigt werden. Die Daten stehen somit sofort zu Verfügung.
Wie man Page Blobs verarbeiten kann, werde ich in einem der nächsten Blog Posts gesondert vorstellen.
Snapshots erzeugen
Snapshots sind eine Read-Only Version von Blobs.
Nachdem ein Snapshot erzeugt wurde, kann dieser, mittels CloudBlob Objekt bzw. dessen Ableitungen, gelesen, kopiert oder gelöscht werden.
Ein Snapshot kann u.a. dazu verwendet werden, eine frühere Version eines Blobs wiederherzustellen.
Bei der Erstellung eines Snapshots, wird eine CloudBlob Objekt zurückgegeben.
Die Snapshot Eigenschaft dieses CloudBlob Objekts, enthält einen DateTime Wert, der den Snapshot eindeutig, gegenüber dem Basis-Blob, identifiziert.
Hier ein kleines Code-Beispiel:
// Speicherkonto Zugangsdaten string blobEndpunkt = "https://demo.blob.core.windows.net"; string accountName = "demo"; string accountKey = "..."; // Client für das Speicherkonto erstellen // Die Zugangsdaten werden hierzu // aus den Variablen "accountName", // "blobEndpunkt" und "accountKey" gelesen. var storageClient = new CloudBlobClient( blobEndpunkt, new StorageCredentialsAccountAndKey( accountName, accountKey)); // Referenz auf den Blob erzeugen. var blob = storageClient .GetBlobReference("bilder/Koala.jpg"); // Snapshot erzeugen. var snapshot = blob.CreateSnapshot(); // Zeitstempel des Snapshots lesen. var zeitstempel = snapshot.SnapshotTime.Value; // Neue Refrerenz auf den Snapshot erzeugen. var snapshot2 = new CloudBlob( "bilder/Koala.jpg", zeitstempel, storageClient); var snapshot2Url = snapshot2.Uri.ToString() + "?snapshot=" + snapshot2.SnapshotTime.Value.ToString("o");
Um einen Snapshot via HTTP GET zu lesen, kann folgende URL Syntax verwendet werden:
http(s)://<Account>.blob.core.windows.net/<Container>/<Blob>?snapshot=<DateTime>
Beispiel:
https://demo.blob.core.windows.net/bilder/Koala.jpg?snapshot=2011-12-31T20:35:09.9493764Z
Shared Access Signatures
Mit Hilfe von Shares Access Signatures können, auf Blob- oder Container-Ebene, zeitlich begrenzte Rechte für Benutzer vergeben werden, wie z.B. diverse Lese- und Schreibrechte oder das Löschen von Blobs.
Das Foto-Repository der Beispielapplikation
Um besser verstehen zu können, wie man den Blob Storage benutzt, habe ich die Beispielapplikation mit einem Repository für die Fotos erweitert.
Die BlobFotoRepository Klasse liegt zentral in der Adressverwaltung.Data Klassenbibliothek, um sie später von weiteren Rollen, wie z.B. Worker Roles, verwenden zu können.
Bei der ersten Instanziierung des Objektes wird geprüft, ob der Blob Container bereits vorhanden ist.
Ist dies nicht der Fall, wird dieser als öffentlicher Container erstellt:
private const string BlobNameBilder = "bilder"; private static bool _initialisiert; private static readonly object Sperre = new Object(); private static CloudBlobClient _blobStorage; static BlobFotoRepository() { Speicherplatzinitialisierung(); } static void Speicherplatzinitialisierung() { if (_initialisiert) { return; } lock (Sperre) { if (_initialisiert) { return; } try { // Client für das Speicherkonto erstellen // Die Zugangsdaten werden hierzu // aus der Rolleneinstellung // "AzureStorageConnectionString" gelesen. var storageAccount = CloudStorageAccount .FromConfigurationSetting("AzureStorageConnectionString"); var storageClient = storageAccount.CreateCloudBlobClient(); // Referenz auf den Container erzeugen. var containerOeffentlich = storageClient .GetContainerReference(BlobNameBilder); // Erstelle den Container, falls dieser nicht vorhanden ist. containerOeffentlich.CreateIfNotExist(); // Container-Rechte für vollen anonymen Zugriff setzen var permissions = containerOeffentlich.GetPermissions(); permissions.PublicAccess = BlobContainerPublicAccessType.Container; containerOeffentlich.SetPermissions(permissions); } catch (WebException) { throw new WebException( "Speicherplatzinitialisierung fehlgeschlagen. " + "Bitte überprüfen Sie die Konfiguration."); } _initialisiert = true; } }
Die Kernfunktionalität des Foto-Repositories ist KontaktBildHinzufuegen Methode.
Diese verkleinert ein, in die Web Rolle hochgeladenes, Bild und speichert es unter der Kontakt ID ab:
public void KontaktBildHinzufuegen( Stream bild, string dateiName, string contentType, Kontakt kontakt) { // Bild hochladen var bildBlobName = string.Format("{0}/{1}{2}", BlobNameBilder, kontakt.KontaktId, Path.GetExtension(dateiName)); var blob = _blobStorage.GetBlockBlobReference(bildBlobName); using (var output = blob.OpenWrite()) { VerkleinereBild(bild, output); // Blob Speichervorgang abschließen und Eigenschaften setzen output.Commit(); blob.Properties.ContentType = "image/jpeg"; blob.SetProperties(); // Kontakt Bild-URL speichern. _repository.UpdateKontaktFotoUrl( kontakt.AdressbuchId, kontakt.KontaktId, blob.Uri.ToString()); } } private static void VerkleinereBild(Stream input, Stream output) { if (input == null) throw new ArgumentNullException("input"); if (output == null) throw new ArgumentNullException("output"); // Neue Höhe und Breite berechnen var originalBild = new Bitmap(input); var breite = originalBild.Width > originalBild.Height ? MaxBildGroesse : (MaxBildGroesse * originalBild.Width / originalBild.Height); var hoehe = originalBild.Width > originalBild.Height ? (MaxBildGroesse * originalBild.Height / originalBild.Width) : MaxBildGroesse; // Bild verkleinern var miniaturBild = new Bitmap(breite, hoehe); using (var graphics = Graphics.FromImage(miniaturBild)) { graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.DrawImage(originalBild, 0, 0, breite, hoehe); } // Bild speichern miniaturBild.Save(output, ImageFormat.Jpeg); }
Änderungen am Repository für den Table Storage
Das Table Storage Repository wurden um die UpdateKontaktFotoUrl Methode erweitert:
public void UpdateKontaktFotoUrl( Guid adressbuchId, Guid kontaktId, string fotoUrl) { var aktuellerKontakt = LoadKontakt(adressbuchId, kontaktId); if (aktuellerKontakt.FotoUrl == fotoUrl) return; aktuellerKontakt.FotoUrl = fotoUrl; _context.UpdateObject(aktuellerKontakt); _context.SaveChanges(); }
Änderungen an der Kontakt-Klasse
Um die Foto URL speichern zu können wurde auch die Kontakt-Klasse erweitert:
public sealed class Kontakt : TableServiceEntity { public Kontakt() { RowKey = Guid.NewGuid().ToString(); PartitionKey = Guid.Empty.ToString(); } public Guid KontaktId { get { return String.IsNullOrEmpty(RowKey) ? Guid.Empty : new Guid(RowKey); } } public Guid AdressbuchId { get { return String.IsNullOrEmpty(PartitionKey) ? Guid.Empty : new Guid(PartitionKey); } } public string Vorname { get; set; } public string Nachname { get; set; } public string Firma { get; set; } public string Strasse { get; set; } public string PLZ { get; set; } public string Ort { get; set; } public string Telefon { get; set; } public string Mobil { get; set; } public string Webseite { get; set; } public string FotoUrl { get; set; } }
Download der Beispielanwendung:
Weitere Informationen |
Hallo Jan,
über das [b]CloudBlobClient[/b] Objekt eines Storage Accounts kannst Du mit der [b]ListContainers[/b] Methode die Container anzeigen (http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storageclient.cloudblobclient.listcontainers.aspx) und die Blobs erhälst Du über die [b]ListBlobs[/b] Methode des [b]CloudBlobContainer[/b] Objektes (http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storageclient.cloudblobcontainer.listblobs.aspx).
Falls Du dazu noch Fragen hast, lass es mich wissen.
Viele Grüße,
Sascha