Hoy abrimos el primer post del año retomando el trabajo pendiente sobre la migración de DNN a la plataforma Windows Azure, esta vez con la utilización de Azure Storage para el almacenamiento de contenidos.
Partiendo del módulo "FileManager" que viene de serie con el portal, podemos observar que se pueden utilizar 3 tipos de soporte de archivos para los archivos -ver post de Mitchel Sellers para información ampliada:
- Sistema de archivos (estándar): la gestión de archivos se realiza sobre el sistema de ficheros tradicional, tratándose de una simple colección de carpetas y archivos en disco;
- Sistema de archivos (seguro): similar a la anterior salvo que utiliza internamente el "truco" de agregarles la extensión ".resources" a cada archivo con lo que no se puede tener acceso directo por URL a los mismos, sino que hay que utilizar un mecanismo de procesamiento de ficheros a través del archivo "LinkClick.aspx";
- Base de datos (seguro): este sistema lo que hace es almacenar el contenido de los ficheros en SQL Server, con lo que hay que utilizarlo con cuidado dependiendo del tamaño de los archivos que se suban al mismo o la base de datos crecerá demasiado -recordemos que el coste de disco es mucho más barato que el de SQL Server- además de tener un procesamiento adicional en servidor para la manipulación de los mismos.
Al llevar el portal a Azure, nos encontramos rápidamente con el dilema de dónde colocar los contenidos:
- Si usamos sistema de archivos -independientemente de si es en modo seguro o estándar- nos topamos con el problema de que al reciclarse el webrole ante cualquier eventualidad, los archivos desaparecen ya que se vuelve a desplegar una nueva imagen del webrole. Una solución podría ser reescribir el código para que utilice LocalStorage en este sistema de almacenamiento, aunque también habría que retocar toda la parte de sincronización de los sistemas de archivos entre las distintas instancias del webrole;
- Si usamos el sistema de base de datos, nos encontramos con los problemas descritos anteriormente. Imaginad almacenar un vídeo de varios Gb en SQL Azure, aparte de ser una burrada nos come el presupuesto :)
Azure Blob Storage al rescate
Una solución idónea, es la de utilizar Azure Blob Storage para el almacenamiento de contenidos. Si estos contenidos son públicos, no hace falta ni siquiera que el portal esté migrado a Azure, sino que se podría usar Blob Storage simplemente para almacenamiento en Azure de los contenidos -con todas los beneficios que aporta- mientras que el portal puede seguir estando alojado en tu hosting de siempre.
Echando un vistazo a la versión de DotNetNuke Community -recuerden que siempre trabajo con la versión completa con código fuente para mayor flexibilidad en los desarrollos- realizaremos una serie de modificaciones para que podamos interactuar con Azure desde el mismo portal.
Comenzando por los tipos de sistemas de archivos, nos encontramos con el enumerado "StorageLocationTypes" al que le vamos a agregar un nuevo valor, que será por el que discriminaremos más tarde en todas las operaciones:
Enum StorageLocationTypes
InsecureFileSystem = 0
SecureFileSystem = 1
DatabaseSecure = 2
AzureStorage = 3
End Enum
A continuación modificamos el módulo FileManager para agregar la opción de poder crear carpetas de este tipo:
Private Sub BindStorageLocationTypes()
...
ddlStorageLocation.Items.Add(New ListItem(Localization.GetString("AzureStorage", Me.LocalResourceFile), "3"))
End Sub
Con ello, agregamos a la lista desplegable el nuevo tipo de carpetas (los archivos creados en un tipo de carpeta "heredan" el tipo de almacenamiento) y podemos crear carpetas de este tipo.
Ahora toca modificar el código para que cuando se cree un tipo de carpetas no cree la estructura lógica en el sistema de archivos. Realmente para las carpetas no se debe hacer nada más, ya que en Blob Storage no existe el concepto de carpetas, sino que todos los archivos cuelgan de un mismo container. El concepto de directorios virtuales parte de la idea de utilizar en el nombre de los blobs el carácter "/", con lo que da la apariencia de estar organizados de tal modo.
La rutina a modificar es "AddFolder" de FileSystemUtilities.vb
Public Shared Sub AddFolder(ByVal _PortalSettings As PortalSettings, ByVal parentFolder As String, ByVal newFolder As String, ByVal StorageLocation As Integer, ByVal uniqueId As Guid)
.......
If StorageLocation <> FolderController.StorageLocationTypes.AzureStorage Then
Dim dinfo As New System.IO.DirectoryInfo(parentFolder)
Dim dinfoNew As System.IO.DirectoryInfo = New System.IO.DirectoryInfo(parentFolder & newFolder)
If Not dinfoNew.Exists Then
dinfoNew = dinfo.CreateSubdirectory(newFolder)
End If
FolderPath = dinfoNew.FullName.Substring(ParentFolderName.Length).Replace("\", "/")
Else
FolderPath = (parentFolder.Replace(ParentFolderName, "") & newFolder).Replace("\", "/")
End If
'Persist in Database
AddFolder(PortalId, FolderPath, StorageLocation, uniqueId)
End Sub
Una vez que podemos crear las carpetas de tipo Azure, todos los archivos que creemos dentro serán del mismo tipo. Al pulsar el botón "Subir archivo" nos redirige a la página de upload. Seleccionamos un archivo y para que este se suba correctamente a nuestro Blob Storage hay que realizar las siguientes modificaciones:
1) Añadir dos entradas en el web.config -o en el archivo de configuración del servicio si tienes migrado el portal a Azure :)- con los datos de configuración:
<add key="WindowsAzure.DataStore.ConnectionString" value="UseDevelopmentStorage=true"/>
<add key="WindowsAzure.DataStore.RootContainer" value="dotnetnuke"/>
2) Agregar una función para la creación del container de Blobs en Azure:
Public Shared Function GetAzureRootContainer() As CloudBlobContainer
' Setup the connection to Windows Azure Storage
Dim storageAccount As CloudStorageAccount = CloudStorageAccount.Parse(Config.GetSetting("WindowsAzure.DataStore.ConnectionString"))
Dim _BlobClient As CloudBlobClient = storageAccount.CreateCloudBlobClient()
' Create the root container
Dim _BlobContainer As CloudBlobContainer = _BlobClient.GetContainerReference(Config.GetSetting("WindowsAzure.DataStore.RootContainer").ToLower())
_BlobContainer.CreateIfNotExist()
' Setup the permissions on the container to be public
Dim permissions As New BlobContainerPermissions()
permissions.PublicAccess = BlobContainerPublicAccessType.Container
_BlobContainer.SetPermissions(permissions)
' Create the Portals container
Return _BlobContainer
End Function
3) Modificar la función "AddFile" de FileSystemUtilities.vb para subir el archivo a Azure:
Private Shared Function AddFile(ByVal PortalId As Integer, ByVal inStream As Stream, ByVal fileName As String, ByVal contentType As String, ByVal length As Long, ByVal folderName As String, ByVal closeInputStream As Boolean, ByVal clearCache As Boolean, ByVal synchronize As Boolean) As String
...
'Calculate hash/save file to db/save file to filesys
Select Case folder.StorageLocation
Case FolderController.StorageLocationTypes.InsecureFileSystem, FolderController.StorageLocationTypes.SecureFileSystem
'Save file to File Storage
...
Case FolderController.StorageLocationTypes.DatabaseSecure
'Save file to Secure Database
Case FolderController.StorageLocationTypes.AzureStorage
'Save file to Azure Storage
Dim blobContainer As CloudBlobContainer = GetAzureRootContainer()
Dim blobName As String = "Portals/" & PortalId & "/" & objFile.RelativePath
Dim blob As CloudBlob = blobContainer.GetBlockBlobReference(blobName)
blob.Properties.ContentType = contentType
blob.UploadFromStream(inStream)
'Add file to Database
intFileID = objFileController.AddFile(objFile)
End Select
End Function
Finalmente, modificando la función DownloadFile de FileSystemUtilities.vb, redireccionamos las peticiones de descarga del archivo al blob en Azure Storage:
Public Shared Function DownloadFile(ByVal PortalId As Integer, ByVal FileId As Integer, ByVal ClientCache As Boolean, ByVal ForceDownload As Boolean) As Boolean
Dim blnDownload As Boolean = False
' get file
Dim objFiles As New FileController
Dim objFile As DotNetNuke.Services.FileSystem.FileInfo = objFiles.GetFileById(FileId, PortalId)
If Not objFile Is Nothing Then
Dim filename As String = objFile.FileName
' check folder view permissions
If FolderPermissionController.CanViewFolder(objFolder) Then
Dim blnFileExists As Boolean = True
' download file
If objFile.StorageLocation = FolderController.StorageLocationTypes.AzureStorage Then
Dim objResponse As HttpResponse = HttpContext.Current.Response
Dim FileURL As String = objFiles.GetAzureBlobUri(objFile)
If FileURL = "" Then
objResponse.Write("Error : No se encontró el Blob en Azure Storage")
Else
objResponse.Redirect(FileURL)
blnDownload = True
End If
ElseIf blnFileExists Then
'Stream the file to the response
End If
End If
Return blnDownload
End Function
Viendo los resultados
En la imagen siguiente se puede observar cómo queda el módulo FileManager con soporte para Azure Blob Storage:
Usando una herramienta como Azure Storage Explorer podemos ver cómo se ha creado la estructura dentro del Blob Container:
Del mismo modo, al pulsar sobre el enlace en el módulo del FileManager, podemos comprobar que está funcionando correctamente la URL del blob:
El resumen de archivos modificados es:
- WebSite/DesktopModules/Admin/FileManager/FileManager.ascx.vb - Para agregar el item al dropdownlist
- WebSite/Web.config - Para añadir dos entradas de configuración
- Library\Services\FileSystem\FileController.vb - Controlador de ficheros
- Library\Services\FileSystem\FolderController.vb - Controlador de carpetas
- Library\Services\FileSystem\FileSystemUtilities.vb - Rutinas de manejo de archivos y carpetas
Conclusión
Agregar el soporte para Azure Blob Storage no es una tarea complicada gracias a que de partida el portal está bien diseñado para poder ampliarlo en este sentido. En este post he simplificado a la creación de carpetas y archivos en Azure para un mejor entendimiento, faltando los métodos de borrado, modificación de nombres, etc. Si alguien quiere que se los muestre que ponga un comentario, aunque como se ve no es muy complicado de realizar.
Quiero recalcar que estas modificaciones las he realizado sobre la versión original de DotNetNuke Community que no está migrada a Azure, con lo que este capítulo de la serie de posts sobre DotNetNuke puede aplicarse independientemente y cualquiera puede hacerlo.
Espero que haya sido de interés. Cualquier duda ya sabes, escribe un comentario.
Un saludo,
David Rodríguez
No hay comentarios:
Publicar un comentario