使用Table Storage
本文将会介绍如何使用Table Storage。Table Storage提供给我们一个云端的表格结构。我们可以把他想象为XML文件或者是一个轻量级的数据库(当然,不是通过SQL 语句进行数据的操作)。 使用Table Storage的方法依然是调用REST API。有关Table Storage REST API的详细信息: 为了方便.NET开发人员,我们在SDK中提供了Microsoft.WindowsAzure.StorageClient类来帮助发送REST请求。 在开始本教学之前,请确保你从Windows Azure 平台下载下载并安装了最新的Windows Azure开发工具。本教学使用Visual Studio 2010作为开发工具。 步骤一:创建解决方案和项目 由于我们要在本地模拟环境下测试Table Storage,首先,请确保Storage Emulator已经启动。我们可以找到管理器的进程手动启动或者让Visual Studio 2010帮助我们启动他。 右击工具栏中Windows Azure模拟器的图标,选择”Show Storage Emulator UI”。弹出如下图所示的窗口: 我们要关注的是Service management中Table所在的一行。要确保Status为Running。 确认完毕后启动Visual Studio 2010,并且新建一个Console项目。 步骤二:添加程序集引用 请在项目属性页里确认项目的Target framework的值是.NET Framework 4或.NET Framework 3.5。然后添加对C:\Program Files\Windows Azure SDK\v1.3\ref\Microsoft.WindowsAzure.StorageClient.dll的引用。该路径为SDK默认安装路径,如果你不能在这个路径中找到Microsoft.WindowsAzure.StorageClient.dll请从SDK安装路径中寻找。 接下来添加对System.Data.Services.Client程序集的引用。该程序集安装在GAC中。你能够在Add Reference窗口的.NET标签下找到他。 步骤三:添加代码 首先在项目中的Program.cs中引用命名空间: using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.StorageClient; 然后在Program.cs中添加如下代码: class Program { static void Main(string[] args) { var storageAccount = CloudStorageAccount.DevelopmentStorageAccount; var tableStorage = storageAccount.CreateCloudTableClient(); // 检查名为CustomerInfo的表格是否被创建,如果没有,创建它 tableStorage.CreateTableIfNotExist("CustomerInfo"); // 创建表格服务上下文 var context = new CustomerInfoContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials); // 插入两条客户信息数据,客户ID分别设置为0和1 CustomerInfo ci1 = new CustomerInfo() { CustomerAge = 25, CustomerID = "0", CustomerName = "Mike" }; context.AddObject("CustomerInfo", ci1); CustomerInfo ci2 = new CustomerInfo() { CustomerAge = 32, CustomerID = "1", CustomerName = "Peter" }; context.AddObject("CustomerInfo", ci2); context.SaveChanges(); // 查找CustomerID为1的客户数据并显示 Console.WriteLine("Retrieve information of a customer whose ID is 1"); var query = context.CreateQuery<CustomerInfo>("CustomerInfo").Where(c => c.CustomerID == "1").ToList(); var returnedcustomerinfo = query.FirstOrDefault(); Console.WriteLine(string.Format("Customer info retrieved: ID:{0},Name:{1},Age:{2}", returnedcustomerinfo.CustomerID, returnedcustomerinfo.CustomerName, returnedcustomerinfo.CustomerAge)); // 更新CustomerID为1的客户数据中的年龄 returnedcustomerinfo.CustomerAge = 33; context.UpdateObject(returnedcustomerinfo); Console.WriteLine("**Customer Info updated**"); // 重新查询,测试更新效果 Console.WriteLine("Retrieve information of a customer whose ID is 1"); var query2 = context.CreateQuery<CustomerInfo>("CustomerInfo").Where(c => c.CustomerID == "1").ToList(); var returnedcustomerinfo2 = query.FirstOrDefault(); Console.WriteLine(string.Format("Customer info retrieved: ID:{0},Name:{1},Age:{2}", returnedcustomerinfo2.CustomerID, returnedcustomerinfo2.CustomerName, returnedcustomerinfo2.CustomerAge)); // 删除插入的两条客户数据 context.DeleteObject(ci1); context.DeleteObject(ci2); context.SaveChanges(); Console.WriteLine("The records has been deleted"); Console.ReadLine(); } } public class CustomerInfo : TableServiceEntity { public string CustomerID { get { return this.RowKey; } set { this.RowKey = value; } } public string CustomerName { get; set; } public int CustomerAge { get; set; } public CustomerInfo() { this.PartitionKey = "mypartitionkey"; } } public class CustomerInfoContext : TableServiceContext { public CustomerInfoContext(string baseAddress, StorageCredentials credentials) : base(baseAddress, credentials) { } } 步骤四:观察并分析代码 步骤三中的代码中,首先我们通过CloudStorageAccount.DevelopmentStorageAccount来说明我们使用的本地的Development Storage自带账户而不是真正的云端存储服务账户。(如果要用真实账户可以使用 //DefaultEndpointsProtocol=https可以改成DefaultEndpointsProtocol=http表示用HTTP而不是HTTPS CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=[用户名];AccountKey=[密码]"); 来实例化对象)然后通过该账户类来实例化一个Table客户端类。这两步是使用SDK中StorageClient程序集来调用Table Storage服务的必要步骤。 然后我们需要关注System.Data.Services.Client程序集。该程序集是WCF Data Services的客户端程序集,能够帮助我们很方便地创建OData客户端程序。(Table Storage提供的REST API遵循OData规范,因此我们的客户端需要遵循OData规范向Table Storage服务发送消息) 我们需要创建上下文来调用服务,我们可以直接使用TableServiceContext。但是通常我们会通过写一个继承自TableServiceContext的类来实现,这样我们可以在该类中添加一些属性或者方法来更加方便地调用。上面的代码中我们继承了TableServiceContext但是并没有添加额外的代码。在实际应用中我们可以加上一些代码来更加方便地调用。比如我们可以在CustomerInfoContext 类中加入下面的属性: public IQueryable<CustomerInfo> CustomerInfo { get { return CreateQuery<CustomerInfo>("CustomerInfo"); } } 这样我们就可以通过调用context.CustomerInfo来更加代替context.CreateQuery<CustomerInfo>("CustomerInfo")。 继承自TableServiceEntity类的CustomerInfo 类定义了每条数据的模式。需要注意的是,与一般的关系型数据库不同,Table Storage并不要求一个表中的所有数据都遵循同一模式。举例来说,在一个表中,可以存储一条有三个字段的记录和一条只有两个字段的记录。这也是我们为什么说可以把Table Storage想象为XML文件的原因。当然在通常情况下我们都会需要在一个表中存储同一模式的数据。这时候我们就可以使用System.Data.Services.Client程序集来为Table Storage创建客户端程序。当我们需要在一个表中存储不同模式的数据时我们可以手动构建和发送REST请求。 还有需要注意的地方是PartitionKey和RowKey。这两项共同组合成表的主键。 代码逻辑包括了对Table Storage的插入,更新,删除和读取。请参考注释部分。 步骤五:运行程序 如果一切正常,你将会看到Console程序输出如下信息: 二使用REST API访问 本文将会介绍如何使用REST API来直接访问Storage Service。 在前三篇教学中,我们已经学习了使用Windows Azure SDK所提供的StorageClient来使用Blob Storage, Queue Storage以及Table Storage的基本方法。我们在前几篇教学中也提及最终StorageClient也是通过发送REST请求来与服务器端通信的。 在这篇教学中,我们会以Blob Storage为例,说明如何使用REST API直接与服务器进行通信。需要说明的是,这篇教学中使用的是C#语言。但是由于REST API实际上是通过HTTP发送的HTTP消息,使用其他语言的工程师同样可以参考代码逻辑了解如何构造HTTP消息以便在其他编程语言中使用。 步骤一:准备工作 该API的作用是返回给定的Container中的Blob信息。为了测试我们的代码我们首先需要有一个已经创建的Container并且向其中添加至少一个Blob。由于如何添加Container和Blob的方法我们已经在Windows Azure入门教学系列 (四):使用Blob Storage中提过,在此不赘述。读者可以按照Windows Azure入门教学系列 (四):使用Blob Storage中的代码创建名为helloworldcontainer的Container和名为myfile的Blob。(只需注释掉删除Blob的代码并运行程序即可) 步骤二:创建解决方案和项目 首先,请确保Storage Emulator已经启动。我们可以找到管理器的进程手动启动或者让Visual Studio 2010帮助我们启动他。 右击工具栏中Windows Azure模拟器的图标,选择”Show Storage Emulator UI”。弹出如下图所示的窗口: 我们要关注的是Service management中Blob所在的一行。要确保Status为Running。 确认完毕后启动Visual Studio 2010,并且新建一个Console项目。 步骤三:添加程序集引用 请在项目属性页里确认项目的Target framework的值是.NET Framework 4或.NET Framework 3.5。然后在Console项目中添加对System.Web程序集的引用。该程序集安装在GAC中。在.NET标签下能够找到该程序集。我们将使用该程序集来发送HTTP请求和接受HTTP消息。 步骤四:添加代码 首先在项目中的Program.cs中引用命名空间: using System.IO; using System.Collections.Specialized; using System.Collections; using System.Web; using System.Net; 然后在Program.cs中添加如下代码: class Program { internal class CanonicalizedString { private StringBuilder canonicalizedString = new StringBuilder(); internal CanonicalizedString(string initialElement) { this.canonicalizedString.Append(initialElement); } internal void AppendCanonicalizedElement(string element) { this.canonicalizedString.Append("\n"); this.canonicalizedString.Append(element); } internal string Value { get { return this.canonicalizedString.ToString(); } } } const string bloburi = @"http://127.0.0.1:10000/devstoreaccount1"; const string accountname = "devstoreaccount1"; const string key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; const string method = "GET"; static void Main(string[] args) { string AccountName = accountname; string AccountSharedKey = key; string Address = bloburi; string container = "helloworldcontainer"; // 创建请求字符串 string QueryString = "?restype=container&comp=list"; Uri requesturi = new Uri(Address + "/" + container + QueryString); string MessageSignature = ""; // 创建HttpWebRequest类 HttpWebRequest Request = (HttpWebRequest)HttpWebRequest.Create(requesturi.AbsoluteUri); Request.Method = method; Request.ContentLength = 0; Request.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R")); Request.Headers.Add("x-ms-version", "2009-09-19"); // 开始创建签名 MessageSignature += "GET\n"; // Verb MessageSignature += "\n"; // Content-Encoding MessageSignature += "\n"; // Content-Language MessageSignature += "\n"; // Content-Length MessageSignature += "\n"; // Content-MD5 MessageSignature += "\n"; // Content-Type MessageSignature += "\n"; // Date MessageSignature += "\n"; // If-Modified-Since MessageSignature += "\n"; // If-Match MessageSignature += "\n"; // If-None-Match MessageSignature += "\n"; // If-Unmodified-Since MessageSignature += "\n"; // Range // CanonicalizedHeaders ArrayList list = new ArrayList(); foreach (string str in Request.Headers.Keys) { if (str.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal)) { list.Add(str.ToLowerInvariant()); } } list.Sort(); foreach (string str2 in list) { StringBuilder builder = new StringBuilder(str2); string str3 = ":"; foreach (string str4 in GetHeaderValues(Request.Headers, str2)) { string str5 = str4.Replace("\r\n", string.Empty); builder.Append(str3); builder.Append(str5); str3 = ","; } MessageSignature += builder.ToString() + "\n"; } MessageSignature += GetCanonicalizedResourceVersion2(requesturi, AccountName); // 开始创建签名 byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature); System.Security.Cryptography.HMACSHA256 SHA256 = newSystem.Security.Cryptography.HMACSHA256(Convert.FromBase64String(AccountSharedKey)); // 创建Authorization HTTP消息头的值 String AuthorizationHeader = "SharedKey " + AccountName + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes)); // 把编码后的签名加入到Authorization HTTP消息头中 Request.Headers.Add("Authorization", AuthorizationHeader); // 获取返回消息 using (HttpWebResponse response = (HttpWebResponse)Request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK) { // 服务器返回成功消息 using (Stream stream = response.GetResponseStream()) { using (StreamReader sr = new StreamReader(stream)) { var s = sr.ReadToEnd(); // 输出返回消息 Console.WriteLine(s); } } } else { // 这里可以抛出异常信息 } } Console.ReadLine(); } static ArrayList GetHeaderValues(NameValueCollection headers, string headerName) { ArrayList list = new ArrayList(); string[] values = headers.GetValues(headerName); if (values != null) { foreach (string str in values) { list.Add(str.TrimStart(new char[0])); } } return list; } static string GetCanonicalizedResourceVersion2(Uri address, string accountName) { StringBuilder builder = new StringBuilder("/"); builder.Append(accountName); builder.Append(address.AbsolutePath); CanonicalizedString str = new CanonicalizedString(builder.ToString()); NameValueCollection values = HttpUtility.ParseQueryString(address.Query); NameValueCollection values2 = new NameValueCollection(); foreach (string str2 in values.Keys) { ArrayList list = new ArrayList(values.GetValues(str2)); list.Sort(); StringBuilder builder2 = new StringBuilder(); foreach (object obj2 in list) { if (builder2.Length > 0) { builder2.Append(","); } builder2.Append(obj2.ToString()); } values2.Add((str2 == null) ? str2 : str2.ToLowerInvariant(), builder2.ToString()); } ArrayList list2 = new ArrayList(values2.AllKeys); list2.Sort(); foreach (string str3 in list2) { StringBuilder builder3 = new StringBuilder(string.Empty); builder3.Append(str3); builder3.Append(":"); builder3.Append(values2[str3]); str.AppendCanonicalizedElement(builder3.ToString()); } return str.Value; } } 步骤五:观察并分析代码 我们首先观察下面三行代码: const string bloburi = @"http://127.0.0.1:10000/devstoreaccount1"; const string accountname = "devstoreaccount1"; const string key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; 这三行代码定义了Blob Storage服务端口,我们使用的账户名和密码。由于使用的是本地模拟的Storage,所以必须使用固定的端口地址,用户名以及密码。 在代码中,我们使用HttpWebRequest 构造并发送HTTP请求。由于例子中使用的是List Blobs API我们需要查阅List Blobs对该API的规定构造符合规定的HTTP请求。 在规定中说明了如果该container没有被设置为允许匿名访问,那么必须由于账户拥有者调用该API才能返回结果。要这样做必须添加Authorization HTTP消息头。必须严格按照Authentication Schemes规定的格式来生成该HTTP消息头。 步骤六:运行程序 如果一切正常,你将会看到Console程序输出如下信息,内容为名为helloworld的Container中所有Blob的信息: |