利用Azure Blob存储来保存LightSwitch应用程序中的图像

[原文发表地址] Storing Images in Azure Blob Storage in a LightSwitch Application

[原文发表时间] 5/1/2014 10:30 AM

LightSwitch 一直支持通过其"图像"业务类型将图片存储在数据库中。然而,由于大小和/或访问等的因素,往往将图像存储在数据库中是不切实际的。在这篇文章中,我会给你展示如何利用 Azure blob 来存储您的 HTML 客户端应用程序中所使用的图像。这是一个很好的体系结构,尤其是当您的应用程序是寄宿在 Azure 网站上的。而且利用Azure Blob来存储图像也是很容易就能做到的。
设置Azure Blob 存储

设置Azure Blob存储器很容易,只需要按照这个指导: 创建一个 Azure 存储帐户

上面这篇文章还介绍如何以.NET编程方式访问存储 以及如何在您 web.config 中保存设置信息,所以我鼓励你通读整篇文章。对于这篇文章的目的,我将专注于将这些内容集成到你的 LightSwitch HTML 应用程序中。

当你完成创建&命名你的存储帐户后,单击“管理访问密钥”来获取的访问密钥,因为你的链接字符串中将会使用到这个密钥。

一旦你已经设置好存储帐户,您可以以编程方式从 LightSwitch .NET 服务端项目创建的Container并向其中存储一些 blob。让我们来看一个例子。

建立数据模型

要使它在LightSwitch中完美的工作,我们要利用一对业务类型: Image和 Web Address。Image类型仅用于为我们“传输”字节到Azure Blob存储中。我们将使用 Web Address来查看图像。我们可以创建Blog容器,以便我们能够直接通过URL找到blob,而且你会看到这个URL很短。

对于此示例,假设一个用户可以有很多图片。下面是我们的数据模型。注意图片实体具有三个重要属性: Image, ImageUrl 和 ImageGuid。

ImageGuid 用来生成一个唯一的 Id,这个ID将成为可以定位到存放在Azure Blob存储中的图像的URL的一部分。这对于我们可以找到正确的 blob来说是非常必要。当然你可以构建你自己独特的命名方式,你甚至可以按照自己的想法将它们存储在不同的容器中。

创建界面

当你创建你的界面时,请确保 ImageUrl 被用于显示图像而不是图像的属性。一个 Web Address业务类型允许您选择一个图像控件来显示图像。例如,我会为我的用户表创建一个很普通的界面集,其中包括图片。

然后打开ViewUser 界面,在Picture选项卡上,删除图像,然后用显示图像控件来替代ImageUrl。

然后在图片上向viewSelected添加一个平铺列表上的敲击行为。选择一个新的界面,选中显示详情,然后选择图片作为界面数据。在此界面上,你可以使用如上相同的技术来让图片按照实际大小进行显示,只需要在属性窗口中的外观区域将ImageUrl 图像控件设置为"适合于当前内容大小"。你也可以把标签位置设置为"无"。

最后,我们就会想让我们的应用程序能够添加和编辑图片。在 ViewUser 界面上,添加一个按钮到命令栏中,并把敲击行为设置为 addAndEditNew。创建一个新的界面,一个新的添加,编辑,详细信息界面并将界面数据设置为图片。在显示图片界面上,添加按钮并把敲击行为设置为对图片的编辑,并且使用相同得添加/编辑图片界面。

自定义JavaScript控件来上传文件

现在,我们的界面已经准备完毕,它们能够并按照我们希望的那样显示ImageUrl,现在是时候纳入一个自定义的JavaScript控件来上传那些图片。这是利用图像属性在客户端上为我们存储图像的字节信息。在一分钟以内,我会告诉你我们怎么样在服务器上截获这个文件信息并将其发送到 Azure 存储而不是数据库,但首先我们需要一个控件以获取我们用户上传的图片字节。

幸运的是,你不需要亲自写这些代码。这里有一个LightSwitch 团队前一阵子写的控件,它既能够很顺畅的工作在最新的浏览器上,也能够工作在那些旧得连使用HTML5方法来读取文件都不支持的浏览器上。这是下面这个教程的一部分,但你可以直接访问的文件和复制所需要的代码。

image-uploader.js
image-uploader-base64-encoder.aspx

把它们放在您的HTMLClient\Scripts 文件夹中。然后在你 default.htm 文件中添加对 javascript 代码的引用。

 <script type="text/javascript" src="Scripts/image-uploader.js"></script>

最后,添加自定义控件到添加/编辑图片界面。这一次,请确保您使用的是Image的属性,而不是 ImageUrl。我们将设置此属性的字节为图像上传控件。

当选中自定义控件后,从界面设计器顶部的下拉列表中点击“编写代码”并在 _render 方法中的输入如下代码:

 myapp.AddEditPicture.Image_render = function (element, contentItem) {
     // Write code here.
     createImageUploader(element, contentItem, "max-width: 300px; max-height: 300px");
 };

现在到了最好玩的一部分。从你 LightSwitch 服务器层保存图像数据到 Azure blob存储。

从你 LightSwitch 更新通道保存图像数据到 Azure blob Storage

以上是所有我们需要在客户端做的 - 现在是时候该使用.Net做一些服务器端的编码工作了。首先,我们将添加一些.NET Azure 存储的客户端的库引用。你可以通过NuGet获取到程序集。用鼠标右键单击您的服务器项目并选择“管理 NuGet 程序包”。安装 Windows.Azure.Storage 3.1包。

现在诀窍就是拦截通过 LightSwitch 更新管道的数据,获取图像数据并将它保存到Blob存储,然后将图像数据设置为空。同时,LightSwitch 会负责将其余的数据发送到数据库。

打开图片实体数据设计器,下拉“写代码”按钮来重写 ApplicationDataService 类的几个方法“Picture_Inserting”和“Picture_Updating”。(我们也可以支持删除,但我会把这作为一个练习留给读者 - 或者我们后续的一些帖子)。

在插入图片的时候,我们首先需要创建一个新的 Guid,并在URL 中使用它。然后我们才可以保存到Blob存储。在更新的时候,我们在保存之前只需检查Guid的值是否已更改。

VB:

 Private Sub Pictures_Inserting(entity As Picture)
     'This guid becomes the name of the blob (image) in storage
     Dim guid = System.Guid.NewGuid.ToString()
     entity.ImageGuid = guid
     'We use this to display the image in our app.
     entity.ImageUrl = BlobContainerURL + guid
  
     'Save actual picture to blob storage 
     SaveImageToBlob(entity)
 End Sub
  
 Private Sub Pictures_Updating(entity As Picture)
     'Save actual picture to blob storage only if it's changed
     Dim prop As Microsoft.LightSwitch.Details.IEntityStorageProperty = 
         entity.Details.Properties("Image")


     If Not Object.Equals(prop.Value, prop.OriginalValue) Then
         SaveImageToBlob(entity)
     End If
 End Sub

C#:

 partial void Pictures_Inserting(Picture entity)
 {
     //This guid becomes the name of the blob (image) in storage
     string guid = System.Guid.NewGuid().ToString();
     entity.ImageGuid = guid;
     //We use this to display the image in our app.
     entity.ImageUrl = BlobContainerURL + guid;
  
     //Save actual picture to blob storage 
     SaveImageToBlob(entity);
 }
        
 partial void Pictures_Updating(Picture entity)
 {
     //Save actual picture to blob storage only if it's changed
     Microsoft.LightSwitch.Details.IEntityStorageProperty prop = 
     (Microsoft.LightSwitch.Details.IEntityStorageProperty)entity.Details.Properties["Image"];
     if(!Object.Equals(prop.Value, prop.OriginalValue)){
         SaveImageToBlob(entity);
     }
 }
使用Azure Blob 存储

在上面文章中我介绍了使用Blob 存储的流程,但在这里有一些基本的知识,即当容器不存在的时候我们如何去创建一个新的容器,并且向其中保存字节数据,首先,在 ApplicationDataService 文件的顶部添加 Azure 存储的引用。

VB:

 Imports Microsoft.WindowsAzure.Storage
 Imports Microsoft.WindowsAzure.Storage.Blob

C#:

 using Microsoft.WindowsAzure.Storage;
 using Microsoft.WindowsAzure.Storage.Blob;

下一步,我们可以通过web.config获取连接字符串,容器名字,以及容器的基础URL。但是对于这个例子,我直接将他们HardCoding 到ApplicationDataService类中的一些常量字段中。

VB:

 'TODO put in configuration file
 Const BlobConnStr = "DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=*****"
 Const BlobContainerName = "pics"
 Const BlobContainerURL = https://mystorage.blob.core.windows.net/pics/

C#:

 const string BlobConnStr = "DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=****";
 const string BlobContainerName = "pics";
 const string BlobContainerURL = "https://mystorage.blob.core.windows.net/pics/";

接下来,创建一个静态的构造方法来检查容器是否存在,如果不存在,就创建一个。它也会用于设置容器的中的Blob,这样它们就可以直接通过URLs来进行公开访问。这个方法只会在Web应用程序第一次启动(或者重启)的时候被执行一次。

VB:

 Shared Sub New()
  
     'Get our blob storage account
     Dim storageAccount As CloudStorageAccount = CloudStorageAccount.Parse(BlobConnStr)
     'Create the blob client.
     Dim blobClient As CloudBlobClient = storageAccount.CreateCloudBlobClient()
     'Retrieve reference to blob container. Create if it doesn't exist.
     Dim blobContainer = blobClient.GetContainerReference(BlobContainerName)
  
     If Not blobContainer.Exists Then
         blobContainer.Create()
  
         'Set public access to the blobs in the container so we can use the picture 
         '  URLs in the HTML client.
         blobContainer.SetPermissions(New BlobContainerPermissions With
                          {.PublicAccess = BlobContainerPublicAccessType.Blob})
     End If
 End Sub

C#:

 static ApplicationDataService() {
  
     //Get our blob storage account
     CloudStorageAccount storageAccount = CloudStorageAccount.Parse(BlobConnStr);
     //Create the blob client.
     CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
  
     //Retrieve reference to blob container. Create if it doesn't exist.
     CloudBlobContainer blobContainer = blobClient.GetContainerReference(BlobContainerName);
  
     if(!blobContainer.Exists())
     {
         blobContainer.Create();
  
         //Set public access to the blobs in the container so we can use the picture 
         //   URLs in the HTML client.
         blobContainer.SetPermissions(new BlobContainerPermissions { 
             PublicAccess = BlobContainerPublicAccessType.Blob });
     }
 }

我们现在已经在Storage账户中创建容器,保存是很容易的一件事情。请注意这里的“技巧”。当我们将图像保存到Blob存储后,设置图像属性为空,以便图像不会被保存到数据库中。

VB:

 Private Sub SaveImageToBlob(p As Picture)
     Dim blobContainer = GetBlobContainer()
  
     'Get reference to the picture blob or create if not exists. 
     Dim blockBlob As CloudBlockBlob = blobContainer.GetBlockBlobReference(p.ImageGuid)
  
     'Save to storage
     blockBlob.UploadFromByteArray(p.Image, 0, p.Image.Length)
  
     'Now that it's saved to storage, set the Image database property null
     p.Image = Nothing
 End Sub
  
 Private Function GetBlobContainer() As CloudBlobContainer
     'Get our blob storage account
     Dim storageAccount As CloudStorageAccount = CloudStorageAccount.Parse(BlobConnStr)
     'Create the blob client
     Dim blobClient As CloudBlobClient = storageAccount.CreateCloudBlobClient()
     'Retrieve reference to a previously created container.
     Return blobClient.GetContainerReference(BlobContainerName)
 End Function

C#:

 private void SaveImageToBlob(Picture p)
 {
     CloudBlobContainer blobContainer = GetBlobContainer();
  
     //Get reference to the picture blob or create if not exists. 
     CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(p.ImageGuid);
  
     //Save to storage
     blockBlob.UploadFromByteArray(p.Image, 0, p.Image.Length);
  
     //Now that it's saved to storage, set the Image database property null
     p.Image = null;
 }
  
 private CloudBlobContainer GetBlobContainer()
 {
     //Get our blob storage account
     CloudStorageAccount storageAccount = CloudStorageAccount.Parse(BlobConnStr);
     //Create the blob client
     CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
     //Retrieve reference to a previously created container.
     return blobClient.GetContainerReference(BlobContainerName);
 }

所有的事情就这些了。运行该应用程序并上传一张图片。

然后回到您的 Azure 管理门户网站,并检查您的存储帐户。您将在存储容器里看到你上传的图片。如果你检查你的数据库,图像数据将为空。

桌面客户端应用程序注意事项:从 LightSwitch 查询管道阅读 Blob 存储

如果你想要在桌面 (Silverlight) 客户端也能这样工作,您需要把图像字节从Blob存储中直接检索到的图像属性中。这是因为此客户端的内置 LightSwitch 控件是与字节打交道,而不是URL。您可以使用和上述相同的代码来保存图像,但你需要从 blob 存储中读取图像。当向数据库中发出任何一个图片的实体的请求时,你可以委托查询管道来达到这个目的。这里有一些代码,你可以把它们用于相同的 ApplicationDataService 类中。

VB:

 Private Sub Query_Executed(queryDescriptor As QueryExecutedDescriptor)
 'This would be necessary if using the Silverlight client.
     If queryDescriptor.SourceElementType.Name = "Picture" Then
  
         For Each p As Picture In queryDescriptor.Results
             ReadImageFromBlob(p)
         Next
     End If
 End Sub
  
 Private Sub ReadImageFromBlob(p As Picture)
     'Retrieve reference to the picture blob named after it's Guid.
     If p.ImageGuid IsNot Nothing Then
         Dim blobContainer = GetBlobContainer()
         Dim blockBlob As CloudBlockBlob = blobContainer.GetBlockBlobReference(p.ImageGuid)
  
         If blockBlob.Exists Then
             Dim buffer(blockBlob.StreamWriteSizeInBytes - 1) As Byte
             blockBlob.DownloadToByteArray(buffer, 0)
  
             p.Image = buffer
         End If
       End If
 End Sub

C#:

 partial void Query_Executed(QueryExecutedDescriptor queryDescriptor)
 {
     //This would be necessary if using the Silverlight client.
     if(queryDescriptor.SourceElementType.Name == "Picture")
     {
         foreach (Picture p in (IEnumerable<Picture>)queryDescriptor.Results)
         {
             ReadImageFromBlob(p);
         }
     }
 }
  
 private void ReadImageFromBlob(Picture p)
 {
     //Retrieve reference to the picture blob named after it's Guid.
     if (p.ImageGuid != null)
     {
         CloudBlobContainer blobContainer = GetBlobContainer();
         CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(p.ImageGuid);
  
         if (blockBlob.Exists())
         {
             byte[] buffer = new byte[blockBlob.StreamWriteSizeInBytes - 1];
             blockBlob.DownloadToByteArray(buffer, 0);
  
             p.Image = buffer;
         }
     }
 }
小结

使用Azure Blob 存储真的很容易。把这些代码变成适合自己所需的,使它的记录错误日志的能力更健壮,以及其他(类似的)的一些事情,祝你玩得开心。我鼓励你也试着存储其他类型的数据。利用 Azure blob 存储是非常有意义的,尤其是当您 LightSwitch 应用程序是部署在 Azure 上的时候。

享受吧 !