Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1909, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

Windows Azure: interagire con il Cloud Storage da Visual Basic 2010 - seconda parte

Riprendiamo il discorso iniziato nel post precedente sulle possibilità di interazione con il Cloud Storage di Windows Azure da codice Visual Basic 2010, nel quale avevamo iniziato a ricostruire parte del percorso che ho personalmente seguito nel creare il mio progetto Azure Blob Studio 2011 disponibile su CodePlex pertanto vi rimando alla precedente lettura se ve la siete persa.

Piccola nota: amandola molto come caratteristica, faccio largo uso della local type inference. Se non vi piace che il tipo non sia esplicito, usate il puntatore del mouse per determinare il tipo restituito dalle espressioni.

In particolare la volta scorsa abbiamo visto come utilizzare metodi per la creazione/lettura/eliminazione di container, assimilabili al concetto di folder.

Questa volta iniziamo a lavorare sui blob, assimilabili al concetto di file. Un blob è quindi un insieme di dati binari che possono essere memorizzati all'interno di container per il successivo utilizzo. Se ad esempio ho un'applicazione che riproduce dei video in streaming, tali file si possono caricare nello Storage ed essere recuperati tramite un url che viene automaticamente assegnato al caricamento nello Storage stesso.

Questa è solo una possibilità. Ad esempio potrebbe interessarmi usare lo Storage come disco virtuale nel quale vado a memorizzare semplicemente un elenco di documenti che vado poi a consumare tramite diversi tipi di client.

Un blob è rappresentato da un'istanza della classe CloudBlob. In linea di massima si ottiene un riferimento al container che contiene il blob e poi si invocano alcuni metodi di istanza della classe CloudBlobContainer. Per esempio ipotizziamo di voler ottenere l'elenco di blob che risiedono in un dato container. Questo si fa tramite il seguente codice:

    Public Overridable Function ListBlobs(ByVal ContainerName As StringAs IEnumerable(Of CloudBlob)
        Dim container = blobStorage.GetContainerReference(ContainerName)
        Dim temp = container.ListBlobs
        Dim lst As New List(Of CloudBlob)

        For Each item In temp
            lst.Add(CType(item, CloudBlob))
        Next
        Return lst
    End Function

Il metodo ListBlobs sopra esposto riceve come argomento il nome del container e si propone di restituire una IEnumerable(Of CloudBlob). La scelta del tipo IEnumerable è che questo e i suoi derivati facilitano il data-binding in molti scenari client come ad esempio WPF e Silverlight. Prima di dare altre spiegazioni, notiamo che:

  • si ottiene un riferimento al container tramite il metodo CloudBlobClient.GetContainerReference
  • si ottiene l'elenco di blob nel container tramite il metodo CloudBlobContainer.ListBlobs

Qui nasce un problema, seppur minimale. Questo ListBlobs restituisce una IEnumerable(Of IListBlobItem). IListBlobItem è un'interfaccia che viene implementata da CloudBlob, ma chiaramente non ne completa le caratteristiche. CloudBlob, infatti, ha molte altre proprietà; per tale ragione il metodo di cui sopra si propone di restituire una diversa IEnumerable e per tale ragione all'interno del blocco di codice viene creata una lista di CloudBlob effettivi a partire dall'elenco ottenuto. Questo diventa importante allorquando vogliamo ottenere le proprietà dei blob ottenuti.

Iniziamo ora a vedere come caricare file nel Cloud Storage. La classe CloudBlobContainer offre molti metodi per fare l'upload, come ad esempio UploadBlob o UploadFromStream. Questi, però, lavorano in modo sincrono e quindi c'è il serio rischio di bloccare il famigerato UI thread. Fortunatamente ci sono metodi che lavorano in modalità asincrona e questo consente di mantenere un'applicazione client molto più responsiva.

Consideriamo il seguente metodo:

    Public Sub UploadBlobs(ByVal ContainerName As StringByVal BlobList As String())
        Try
            Dim container = blobStorage.GetContainerReference(ContainerName)

            For Each blob In BlobList
                Dim blobRef = container.GetBlobReference(IO.Path.GetFileName(blob))
                blobRef.Properties.ContentType = getMimeType(blob)
                blobRef.Properties.ContentLanguage = My.Computer.Info.InstalledUICulture.EnglishName
                Dim fs As New FileStream(blob, FileMode.Open)
                blobRef.BeginUploadFromStream(fs, AddressOf UploadAsyncCallBack, blobRef)
            Next
        Catch ex As Exception
            Throw
        End Try
    End Sub

Questo riceve il nome del container destinatario dei blob e un elenco di file (come array) quali argomenti. Dapprima ottiene il riferimento al container quindi, per ciascun nome di blob, va ad ottenere il riferimento al blob stesso nello storage tramite GetBlobReference. Ovviamente a questo punto il blob non esiste ma è come se stessimo prenotando uno spazio per lui. Successivamente vengono impostate alcune proprietà che saranno utili per individuare il blob come ad esempio la proprietà ContentType che indica il tipo MIME del file e la proprietà ContentLanguage che può essere assegnata con una delle Culture disponibili. Il tipo MIME viene determinato da un altro metodo che illustro tra breve, qui è importante sottolineare come la proprietà Properties sia di tipo BlobProperties e consente, per l'appunto, di settare alcune proprietà per ciascun blob.

Successivamente si istanzia uno stream in lettura che punta al file fisico in locale, dopodiché si invoca un metodo chiamato BeginUploadFromStream che carica in modalità asincrona il file selezionato sullo storage. Come argomenti riceve lo stream aperto, un delegate che viene invocato al termine dell'operazione asincrona e il riferimento all'istanza del blob. Il perché di questo lo vediamo proprio nel codice del delegate:

    Private Sub UploadAsyncCallBack(ByVal result As IAsyncResult)
        If result.IsCompleted Then
            Dim blobref = CType(result.AsyncState, CloudBlob)
            blobref.SetProperties()
            blobref.EndUploadFromStream(result)
        End If
    End Sub

Se il risultato dell'operazione è che questa è stata completata (IAsyncResult.IsCompleted), si ottiene il riferimento al blob appena caricato. Poi si invocano i metodi CloudBlob.SetProperties e CloudBlob.EndUploadFromStream che, rispettivamente, finalizzano l'assegnazione delle proprietà del blob e indicano al runtime che l'upload è terminato e di rilasciare, quindi, le risorse. Ora vediamo il metodo che determina il tipo MIME di un file:

    Private Shared Function getMimeType(ByVal fileName As StringAs String

        'in case the extension is not registered, then return a default value
        Dim mimeType As String = "application/unknown"

        Dim fileExtension = System.IO.Path.GetExtension(fileName).ToLower()
        Dim registryKey = Registry.ClassesRoot.OpenSubKey(fileExtension)

        If registryKey IsNot Nothing And registryKey.GetValue("Content Type"IsNot Nothing Then

            mimeType = registryKey.GetValue("Content Type").ToString
        End If

        Return mimeType
    End Function

Niente si speciale, il codice cerca nel registro l'estensione del file e ne determina il tipo MIME oppure restituisce un valore di default se questa non è presente.

Chiaramente è anche possibile scaricare blob dallo storage sul proprio computer. Per quelle che erano le finalità nel mio client, ho scritto due overload di un metodo che ho chiamato DownloadBlobs. Entrambi usano il metodo CloudBlob.BeginDownloadToStream che si occupa del download asincrono di file dal cloud storage. Ecco i due overload:

    Public Overridable Function DownloadBlobs(ByVal containerName As StringByVal targetDirectory As StringAs IEnumerable(Of String)

        Try
            Dim container = blobStorage.GetContainerReference(containerName)

            Dim blobNames As New List(Of String)
            Dim tempStream As IO.FileStream

            For Each blob In container.ListBlobs.ToCloudBlobCollection
                Dim tempPath = targetDirectory + "\" + IO.Path.GetFileName(blob.Uri.AbsolutePath)
                tempStream = New IO.FileStream(tempPath, IO.FileMode.Create)
                blobNames.Add(tempPath)

                blob.BeginDownloadToStream(tempStream, AddressOf DownloadAsyncCallBack,
                                           New KeyValuePair(Of CloudBlobFileStream)(blob, tempStream))

            Next
            Return blobNames.AsEnumerable
        Catch ex As Exception
            Throw
        End Try
    End Function

    Public Overridable Sub DownloadBlobs(ByVal containerName As String,
                                         ByVal targetDirectory As String,
                                         ByVal blobsToDownload As IEnumerable(Of CloudBlob))
        Dim container = blobStorage.GetContainerReference(containerName)

        Dim tempStream As IO.FileStream

        For Each blob In blobsToDownload
            Dim tempPath = targetDirectory + "\" + IO.Path.GetFileName(blob.Uri.AbsolutePath)
            tempStream = New IO.FileStream(tempPath, IO.FileMode.Create)

            blob.BeginDownloadToStream(tempStream, AddressOf DownloadAsyncCallBack,
                                       New KeyValuePair(Of CloudBlobFileStream)(blob, tempStream))

        Next

    End Sub

Entrambi ricevono come argomenti sia il nome del container dal quale scaricare i file sia la directory di destinazione. In particolare il primo overload:

  • scarica tutti i file presenti nel container specificato dallo storage al pc
  • restituisce l'elenco di file sotto forma di insieme di stringhe, nel caso possa servire per l'analisi dei file scaricati
  • cicla l'insieme e inserisce ciascun blob in una lista generica. Per farlo il codice si avvale di un metodo extension chiamato ToCloudBlobCollection, che vi spiego tra poco, che permette di convertire ciascun IListBlobItem in CloudBlob e questo serve per determinare la posizione del blob attraverso la sua proprietà Uri, diversamente non disponibile

Il secondo overload ha più o meno le stesse caratteristiche, solo che scarica solo i blob specificati attraverso un terzo argomento di tipo IEnumerable(Of CloudBlob) e, per tale ragione, non restituisce alcun elenco.

Entrambi i metodi scaricano i blob in modo asincrono sfruttando il metodo BeginDownloadToStream (don't block the UI thread....). Qui ho dovuto usare un piccolo trucco, in barba alla documentazione. Come vedete nell'invocare questo metodo passo l'istanza dello stream, un delegate che viene invocato al termine dell'operazione e un oggetto KeyValuePair(Of CloudBlob, Stream). Il fatto è che, a differenza dell'upload, nel download è necessario chiudere esplicitamente lo stream altrimenti il file scaricato è inutilizzabile finché bloccato.

La documentazione non tiene conto di questo aspetto, l'ho segnalato al team di Azure che sicuramente farà le sue verifiche. Infatti il delegate si presenta così:

    Private Sub DownloadAsyncCallBack(ByVal result As IAsyncResult)
        Dim currentResult = CType(result.AsyncState, KeyValuePair(Of CloudBlobFileStream))
        Dim blob = currentResult.Key
        blob.EndDownloadToStream(result)
        currentResult.Value.Close()
    End Sub

In sostanza prendiamo la chiave della coppia chiave/valore, che corrisponde al blob e ne confermiamo la chiusura tramite EndDownloadToStream. Poi riprendiamo il valore nella coppia, che corrisponde allo stream, e lo chiudiamo esplicitamente. Ecco, poi, il metodo extension ToCloudBlobCollection:

    <Extension()> Public Function ToCloudBlobCollection(ByVal blobItemList _
                         As IEnumerable(Of IListBlobItem)) As IList(Of CloudBlob)
        Dim c As New List(Of CloudBlob)
        For Each element In blobItemList
            c.Add(CType(element, CloudBlob))
        Next
        Return c
    End Function

In questo modo convertiamo un insieme di IListBlobItem in un qualcosa di più specifico per i blob. Da ultimo possiamo scrivere un metodo che elimini i blob. Ci sono anche qui diverse possibilità, sincrone (CloudBlob.DeleteIfExists) o asincrone (CloudBlob.BeginDeleteIfExists). Scegliamo ancora la via asincrona, ma siccome siamo dei fighi :-) e ci piace sguinzagliare Visual Basic 2010 sfruttiamo anche una statement lambda al posto del delegate, perché in realtà siamo anche pigri e non ci va di scrivere un metodo a parte :-) Eccolo:

    Public Sub DeleteBlobs(ByVal Blobs As IEnumerable(Of CloudBlob))

        For Each blob In Blobs
            Dim blobRef = Me.blobStorage.GetBlobReference(blob.Uri.ToString)
            blobRef.BeginDeleteIfExists(Sub(result As IAsyncResult)
                                            CType(result, CloudBlob).EndDeleteIfExists(result)
                                        End SubblobRef.Uri)
        Next
    End Sub

 

Anche qui andiamo ad eliminare l'elenco di blob passato attraverso una IEnumerable(Of CloudBlob) anche se questa è solo una possibile alternativa.

Abbiamo così visto come sia possibile elencare, caricare, scaricare ed eliminare blob sul proprio storage su Windows Azure. In realtà ci sono ancora due cosettine da fare, oggetto di altrettanti post:

  • ragionare per eventi, affinché eventuali client siano notificati dell'inizio/fine delle operazioni
  • predisporre un esempietto pratico

Per oggi ci fermiamo qui, nel prossimo post vedremo come inserire la gestione di eventi personalizzati all'interno del codice proposto in questo e nel precedente post.

Alessandro

Print | posted on domenica 16 gennaio 2011 18:26 | Filed Under [ Visual Basic Visual Studio 2010 Windows Azure and SQL Azure ]

Powered by:
Powered By Subtext Powered By ASP.NET