Welcome to MSDN Blogs Sign in | Join | Help

MIX 09- Data Services 1.5, Windows Azure Tools, Silverlight 3, Expression Blend 3,ASP.NET MVC 1.0 and .NET RIA Services

ADO.NET Data Services v1.5 CTP1
http://blogs.msdn.com/astoriateam/archive/2009/03/16/ado-net-data-services-v1-5-ctp1-now-available-for-download.aspx
download: http://www.microsoft.com/downloads/details.aspx?FamilyID=3e3d4eaf-227b-4ad3-ad0d-3613db8aa9df

March CTP of the Windows Azure Tools and SDK
http://blogs.msdn.com/jnak/archive/2009/03/18/now-available-march-ctp-of-the-windows-azure-tools-and-sdk.aspx
download: http://download.microsoft.com/download/8/4/2/84291A86-26A4-44AB-8024-5A6700324061/VSCloudService.exe

Hotfix for Windows Azure Tools and SDK
Hotfix: Native Debugging Improvements
http://go.microsoft.com/fwlink/?LinkId=145526

Hotfix: Support for FastCGI on the Development Fabric
http://support.microsoft.com/kb/967131

Silverlight 3 Beta Released!
http://blogs.msdn.com/silverlight_sdk/archive/2009/03/18/silverlight-3-beta-released.aspx
http://silverlight.net/getstarted/silverlight3/default.aspx
download:
VS Tools : http://go.microsoft.com/fwlink/?LinkID=143571
runtime: http://go.microsoft.com/fwlink/?LinkID=143433
SDK : http://go.microsoft.com/fwlink/?LinkID=143435

Microsoft Expression Blend 3 Preview
http://www.microsoft.com/expression/blendpreview
download: http://go.microsoft.com/fwlink/?LinkId=146762

ASP.NET MVC 1.0 RTM
http://www.asp.net
download:
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=53289097-73ce-43bf-b6a6-35e00103cb4b

Hotfix for VS Crash with ASP.Net MVC in a Windows Azure Cloud Service Project
http://blogs.msdn.com/jnak/archive/2009/02/26/fix-available-asp-net-mvc-rc-crash-in-a-windows-azure-cloud-service-project.aspx
download: https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=16827&wa=wsignin1.0

.NET RIA Services
download : http://go.microsoft.com/fwlink/?LinkId=144609

Azure Services探索—存储之Blobs存储

del.icio.us Tags: ,

上一篇是关于Azure Service 的表存储,本篇来主要是针对Azure中另外一类特殊的存储-二进制等非结构化的存储,如果我们的Azure服务中有需要保存非传统或结构化的数据,如图片、声音及视频媒体等信息,那么我们就需要使用到Blob存储了。而Windows Azure平台提供了一个很不错的托管平台和编程模型。

Blob存储和Azure 表存储不同的是在概念上,之前我们看到表存储最重要的概念是实体(Entity)概念,实体里面包含属性。对应到Blob存储,对应实体,比较重要的概念是容器(Container) .

规则1Blob数据是存储在容器中的,一个容器是一类Blob数据的集合,保存了一组Blob数据,容器本身有共享读取和私有不同的访问的策略;每个容器都关联一个元数据存储对象,用来记录Blob数据相关的属性;每个容器也有一个列表,列表记录了容器中所有的Blob数据。

Blob存储容器的概念如下图:

clip_image002

Pictures source: ES04.pptx of PDC 2008-Windows Azure Storage–Essential Cloud Storage Services

帐号Sally下面有两个Blob存储容器,一个容器叫pictures,表示它是用来保存图片的;另外一个容器叫movies,表示它是用来保存视频数据的。Pictures容器中包含了两个Blob数据,分别是IMG001.jpg和IMG002.jpg。

规则2Azure的Blob存储容器也是跟随和隶属于一个帐号的,一个账号下面可以建立多个Blob存储 容器,和表存储的表名一样容器名跟随在帐号名的范围内,你可以从访问的REST路径看出规律:http://<account>.blob.core.windows.net/<container>/<blobname>

如上图,容器pictures和movies分别隶属于sally这个帐户下。

规则3Blob数据 是保存在不同的Blob存储容器中,一个Blob数据最大容量是50G,每个Blob数据在容器中有一个唯一的字符标示作为Blob数据名。每个Blob数据都可以有和它关联的属性描述,每一个属性是一个名值对,这些属性保存在容器的元数据中,在一个容器中,Blob数据的关联属性大小不能超过8K

如上图,IMG001.jpg,IMG002.jpg和MOV1.AVI是分别保存在不同容器的Blob数据,IMG001.jpg就是它的Blob数据名。

综合规则1,2,3 ,对于: http://sally.blob.core.windows.net/music/rock/rush/xanadu.mp3 这样一个访问标示,帐户名(Account)是:sally,Blob存储容器名(Container) 是:music,Blob数据名(BlobName )是:rock/rush/xanadu.mp3

规则4:上传一个Blob数据到某个Blob存储容器,如果一次上传的大小超过64M,则要进行分块(Blocks)多次上传,比如一个10G的电影,你可以分成2500个块,每个块4M大小,进行连续上传。分块后,每一个块都有一个唯一的编号或分块名称标示这个块。

有关分块相关的规则和操作场景和设计要点比较繁多,在本篇文章中不做过多的讨论。

 

接下来,我们来看一下,如何定义Blob数据容器以及Blob数据存取和访问的编程技术。操作的练习,我选用了Jim Nakashima的文章Windows Azure Walkthrough: Simple Blob Storage Sample 作为体验内容。

这个练习主要是熟悉Blob数据的创建和访问编程模型,运行后的界面如下图:

clip_image004

这个应用类似一个下载网站,你可以上传文件到Azure Blob存储中,针对每个上传的文件,上传的同时,你可以添加和文件相关的属性,将其保存到容器的元数据中。上传之后,我们会查询Blob存储容器,也可以进行删除容器中Blob数据的操作处理。基本上,覆盖了规则1,规则2和规则3的所有相关概念和理念。

由于Jim Nakashima的文章有十分详细的操作步骤,我就不一一论述操作步骤了,而是介绍重点的处理过程。

Azure 服务的Blob存储存取API,依然是采用了SDK中的StorageClient 类库,这个类库在 Azure SDK的\StorageClient\Lib 目录下。之前系列的文章已经多次说明了增加StorageClient的方法。

 

使用GetBlobContainer 和CreateContainer查询和创建Blob存储容器

   1: StorageAccountInfo accountInfo = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
   2:  
   3: // Container names have the same restrictions as DNS names
   4:  
   5: BlobStorage blobStorage = BlobStorage.Create(accountInfo);
   6:  
   7: _Container = blobStorage.GetBlobContainer(RoleManager.GetConfigurationSetting("ContainerName"));
   8:  
   9: // returns false if the container already exists // Make the container public so that we can hit 
  10:  
  11: // the URLs from the web
  12:  
  13: _Container.CreateContainer(new NameValueCollection(), ContainerAccessControl.Public);
  14:  

 

上传时,增加Blob数据相关的属性(BlobProperties)

   1: // Make a unique blob name
   2:  
   3: string extension = System.IO.Path.GetExtension(fileUploadControl.FileName);
   4:  
   5: BlobProperties properties = new BlobProperties(Guid.NewGuid().ToString() + extension);
   6:  
   7: // Create metadata to be associated with the blob
   8:  
   9: NameValueCollection metadata = new NameValueCollection();
  10:  
  11: metadata["FileName"] = fileNameBox.Text;
  12:  
  13: metadata["Submitter"] = submitterBox.Text;
  14:  
  15: properties.Metadata = metadata;
  16:  
  17: properties.ContentType = fileUploadControl.PostedFile.ContentType;
  18:  
  19: // Create the blob
  20:  
  21: BlobContents fileBlob = new BlobContents(fileUploadControl.FileBytes);
  22:  
  23: _Container.CreateBlob(properties, fileBlob, true);
  24:  

 

使用ListBlobs方法读取容器中的Blob数据,以及枚举相关的属性(BlobProperties)

   1: IEnumerable<object> blobs = _Container.ListBlobs(string.Empty, false);
   2:  
   3: List<FileEntry> filesList = new List<FileEntry>();
   4:  
   5: foreach (object o in blobs)
   6:  
   7: {
   8:  
   9: BlobProperties bp = o as BlobProperties;
  10:  
  11: if (bp != null)
  12:  
  13: {
  14:  
  15: BlobProperties p = _Container.GetBlobProperties(bp.Name);
  16:  
  17: NameValueCollection fileEntryProperties = p.Metadata;
  18:  
  19: filesList.Add(new FileEntry(p.Name, bp.Uri, fileEntryProperties["FileName"], fileEntryProperties["Submitter"]));
  20:  
  21: }
  22:  
  23: }
  24:  
  25: fileView.DataSource = filesList;
  26:  

 

使用DeleteBlob方法删除具体的Blob数据

   1: if (_Container.DoesBlobExist(blobName))
   2:  
   3: {
   4:  
   5: _Container.DeleteBlob(blobName);
   6:  
   7: }
   8:  

 

从上面的体验我们依然可以发现,当我们掌握了 Azure Blob的设计理念和规则,利用StorageClient类库作为Azure服务存储访问的客户端,我们就可以操作Azure服务的各种存储类型。

同时我们也发现Azure服务的各种存储非常灵活和贴近应用场景,Blob存储操作和表存储相比,Blob存储更专注于如何保存保存Blob数据和关联的属性,而不是让开发人员去如何考虑创建Blob数据表;同样相比表存储,Blob存储在查询方面就没有强调要专门的检索性能,而更多的提供了Blob存储特有的上传和下载的操作。

另外StorageClient类库作为Azure服务存储访问的客户端,也被众多的开发人员进行利用,创造出许多小工具。比如最新的AzureBlobSync 小工具

clip_image006

我们就可以利用这个工具将本地的文件上传到我们的云端的某个帐号的Blob存储容器中,同样也可以将云端的某个文件下载到本地的计算机目录里,非常的方便。上图就是,我们利用这个工具,就可以往刚刚我们制作的应用中上传和下载文件。

同样利用我们刚刚编写的应用的Web Role端,也可以查询到我们使用AzureBlobSync上传的文件。

clip_image008

当然因为AzureBlobSync没有针对Blob 数据的属性(元数据)进行定义和操作,所以我们上传的文件都是没有相关属性的。但我们依然可以操作它,因为它在Blob 存储容器中。

我们也可以从SQL Server数据库中,看到Blob 存储的存储格式

clip_image010

通过这些工具和SQL数据库的观察,我们对Blob存储的设计就更加清晰和熟悉,这样我就可以轻松的在Azure 服务编程中应用它了。

同样我们还可以利用另外一个小工具Azure Storage Explorer来查看Azure服务的各种存储类型,比如表存储,队列存储,Blob存储,并且对里面的数据进行查询,删除等操作。

clip_image012

 

 

综上所述,我们可以对Azure的各种存储类型特色和用途做一个小的总结:

· Azure Blob存储 –提供了二进制的图片、视频,文件以及大块数据的存储服务

· Azure 表存储-主要提供了结构化的存储,用于保存Azure应有相关的状态管理,业务数据,用户数据等等

· Azure队列存储-主要是提供一个可靠的消息存储和消息服务,用于异步任务的通知以及服务间的通信。

· Azure LocalStorage存储-主要是提供一个独立的文件系统的临时存储,用于记录和保存一些临时的数据。

 

相关的示范代码可以在我的MSDN代码库中下载获得。代码在在Azure SDK and Tools Jan 2009 CTP环境下测试通过。

Posted by ccBoy | 1 Comments
Filed under:

Azure Services探索—在Azure服务中增加WCF服务

这篇文章要从Azure Service探索--存储之LocalStorage那篇文章激发的需求,因为在文件系统存储中,我们讲述了LocalStorage的不可共享性,当时又为了学习体验Worker Role的用法,就产生了这样的一个想法:首先还是在Web Role中定义一个临时的文件系统,然后再由Web Role提供一个WCF的服务,让Worker Role来调用,从而达到对这个临时文件系统的共享和写入。

其实变换一下,就是要解决如何在Azure 服务中提供一个 WCF服务,然后这个WCF如何被配置,以及被其他角色(Worker Role,其它的Web role)或另外一个Azure服务调用。

 

基本的界面设计如下,唉没创意,还是用之前的那个例子进行修改

clip_image002

前台的Web Role 界面没有变化,还是向一个LocalStorage中写入消息,但同时也提供一个WCF的服务,也可以向这个LocalStorage写入消息,而后台的Worker Role就定期的调用这个WCF服务,写入Ping信息。

流程确定之后,就可以开始操作了。

首先还是创建一个Web Role + Worker Role的项目

clip_image004

接着在服务定义文件中定义文件存储,然后再Web Role中复制之前的代码。F5 运行,没有问题,然后我们开始增加WCF服务

clip_image006

 

点击Web Role项目,选择增加一个新的选项(Add New Item),如下图,

clip_image008

之后定义一个WCF的ServiceContract

   1: namespace HostingWCFServices_WebRole
   2:  
   3: {
   4:  
   5: [ServiceContract]
   6:  
   7: public interface IMessageLogger
   8:  
   9: {
  10:  
  11: [OperationContract]
  12:  
  13: void LogMessage(string message);
  14:  
  15: }
  16:  
  17: }
  18:  

 

然后在myMessageLogger.svc.cs中实现这个Interface,具体的实现非常简单,就是向文件存储中写消息。

之后我们需要检查和确认WCF的配置文件,并做一个的改动,将WSHttpBinding 修改成 BasicHttpBinding ,如下图:

clip_image010

关于BasicHttpBinding和WSHttpBinding的差异,可以参见文章最后一些链接。除了性能,对WS-*的协议支持,Session/事务等支持的不同,Security Mode and the Client Credential Type in WCF文章中给出了不错的建议: If broad reach across platforms is your goal, there's no better choice today than using the WS-I basic profile over SSL which of course would also mean using basicHttpBinding instead of wsHttpBinding. It's important to note that this particular binding doesn't provide CIA by default.

之后可以F5 启动我们的Azure 服务,然后访问WCF的地址http://localhost/myMessageLogger.svc,测试一下WCF服务是否能够正确地运行。

然后我在我的开发环境就出现了下面这个错误页面:

clip_image012

从这个界面可以发现一些有趣的现象,第一 , Azure 使用了另外一个端口 127.0.0.1:5100,第二,证明BasicHttpBinding的确默认没有带安全标示。

看到左边的Detail Error Information感觉.svc 文件好像根本没有进程来Handler,感觉IIS7 用一个静态文件的进程在处理,思考了两分钟,头脑中闪现了WAS,然后去控制面板检查,发现果然WAS (Windows Process Activation Service)没有安装.

然后退出Visual Studio 转为系统安装,我是运行在Windows Server 2008上,安装WAS的界面如下:

clip_image014

安装之后,都不用重新启动,继续F5 运行,这下WCF服务可以访问了

clip_image016

根据之前的设计,目前的任务已经完成了一半了,我们已经成功在一个Web Role中发布了一个记录消息的WCF服务,剩下的工作是要在Worker Role中调用这个WCF服务。

 

现在可以点击Worker Role项目,然后选择添加服务引用(Add Service Reference)

clip_image018

这里有个小小的技巧,因为现在在开发调试环境,WCF服务并没有运行,这时你可以先在地址框输入http://localhost/myMessageLogger.svc 然后点击Discover 按钮,然后地址框中的端口号自动变了,这时是本机的ASP.NET Web Development Server启动了产生的新的端口号,我们现在只是要增加一个引用,1030端口不是Azure运行WCF的端口,如果有端口应该是我们刚刚在错误页面看到的5100。增加好WCF服务引用之后,要记得修改Worker role 的app.config配置文件,去掉<Endpoint address>中地址的端口号,保持http://localhost/myMessageLogger.svc 的配置。

之后可以编写Worker Role的代码,通过WCF Proxy往Web Role的文件存储中增加消息了。

 

   1: public override void Start()
   2:  
   3: {
   4:  
   5: // This is a sample worker implementation. Replace with your logic.
   6:  
   7: RoleManager.WriteToLog("Information", "Worker Process entry point called");
   8:  
   9: string wcf_url = "http://localhost/myMessageLogger.svc";
  10:  
  11: while (true)
  12:  
  13: {
  14:  
  15: Thread.Sleep(10000);
  16:  
  17: BasicHttpBinding bind = new BasicHttpBinding();
  18:  
  19: EndpointAddress endpoint = new EndpointAddress(wcf_url);
  20:  
  21: HostingWCFServices.MessageLoggerClient client = new HostingWCFServices_WorkerRole.HostingWCFServices.MessageLoggerClient(bind, endpoint);
  22:  
  23: client.LogMessage("Ping from WorkerRole");
  24:  
  25: RoleManager.WriteToLog("Information", "Ping from WorkerRole-WCF MessageLoggerClient");
  26:  
  27: if (client.State == CommunicationState.Faulted)
  28:  
  29: client.Abort();
  30:  
  31: else
  32:  
  33: client.Close(); 
  34:  
  35: }
  36:  
  37: }
  38:  

 

这样后台Worker Role的代码也完成了,然后F5 运行。运行界面如下:

clip_image020

通过在Web Role中Hosting WCF Services可以让我们找到另外一个共享Azure能力或服务的途径,共享和暴露出来的WCF可以给Azure Services的其他角色使用,也可以提供给云端之外的应用和客户端使用,这也是Windows Azure平台提供应用组合的一个强有力的工具和途径。

另外最后的体验中,我们也尝试了使用Worker Role调用和消费这个WCF服务,同样这也向我们指明了另外一个应用整合的途径,我们的Azure Services本身也可以消费来自其他Windows Azure平台或Internet上的服务和能力。而Azure Services的WebRole和Worker Role的体系架构也让看到云端应用的特色和优美之处。

相关的示范代码可以在我的MSDN代码库中下载获得。代码在在Azure SDK and Tools Jan 2009 CTP环境下测试通过。

 

附录:有关BasicHttpBinding vs. WSHttpBinding 的相关信息

· Transport Security in WCF pre-defined bindings

· Security Mode and the Client Credential Type in WCF

· BasicHttpBinding vs. WSHttpBinding

· WCF : BasicHttpBinding compared to WSHttpBinding at SOAP packet level.

· IIS 7 : Support for non-HTTP Protocols

 

Posted by ccBoy | 1 Comments
Filed under:

Azure Services探索—存储之表(Table)存储

del.icio.us Tags: ,

本系列文章是一个有关Azure Services开发基础性的学习记录,由于时间有限,所以希望自己讨论和探索的过程是从零开始,到能够进行Azure Services基本的编程开发。相对于每个议题可能有非常深入的话题,我希望有时间能通过其他的文章来进行。本系列的定位基本上定位于,花20-30分钟时间,先下载代码,跟着阅读文章,运行获得相关的体验。

上一篇是关于Azure的队列存储,本篇是关于表存储的。理论上表存储应该是我们最熟悉的存储方式,它本身也是结构化的存储,类似于我们平时的关系型数据库的存储。不过这里首先要强调的还是编程思想的变化,Windows Azure 平台是云计算的平台,所以从设计开始,它的定位是托管方式的,全开放和分布式的。体现到Azure Service的表存储上也是一样,首先整个的表存储是租户式的设计,说到这里,一定有很多人会想起Saas中的多重租赁(Multi-tenancy)概念,并开始将Azure存储和其联系在一起联想翩翩了。Windows Azure 平台的存储设计是非常精巧的,我们先把焦点转回Azure Service是如何定义和操作表存储的。其中我们需要知道哪些理念。

规则1Azure的表存储也是跟随和隶属于一个帐号的,一个账号下面可以建立多个表存储 ,和队列一样表名跟随在帐号名的范围内,你可以从访问的REST路径看出规律: http://<Account>.table.core.windows.net/<TableName>

规则2数据是保存在表存储中的,一个表是一个实体的集合,保存了一组实体的信息,每一个实体记录是一个行记录;一个实体是该实体的关键属性和相关的属性的集合,保存了一组实体属性的信息,实体的每一个属性是一个名/值对,对应一列。Entities <->Rows ,Properties<-> Columns .

基本上Azure 表存储的概念如下图:

clip_image002

Pictures source: http://blogs.msdn.com/jnak/archive/2008/10/28/walkthrough-simple-table-storage.aspx

套用上面的规则2,你可以发现一个联系人表是一个联系人实体的集合,保存了一组联系人的信息,每一个联系人记录是一个行记录;一个联系人是该联系人的关键属性和相关的属性的集合,保存了一组联系人属性(Name,Address)的信息,实体的每一个属性是一个名/值对(名字/小王,地址/北京王府井),对应一列。

 

规则3一个实体固定有两个关键属性,这两个关键属性联合唯一标示一个实体。第一个关键属性是:PartitionKey,它是实体在数据库信息中的分区标示/区域关键字,第二个属性是RowKey,它是在某个分区内能够唯一标示该实体的关键字。

下图可以清晰地看到规则3的定义

clip_image004

Pictures source: ES07.pptx of PDC 2008-Windows Azure Tables:Programming Cloud Table Storage

 

文档(Document)是一个Azure的表存储,保存了一组文档实体。每一个文档对应一行记录,每一个文档包含关键属性(Document,Version) 和一些相关属性 (ChangedOn, Description);每一个属性是一个名值对,对应到数据库的一列。

根据规则3,PartitionKey其实对文档的一个细分或说颗粒化,用来作为分类/分区的标示,这个文档是FAQ文档还是样板文档,而RowKey属性在一个分类中,可以唯一标示和定位到这个文档。比如v2.0.1加上分类就可以唯一定位到v2.0.1版本的Examples文档。

其实这如何细分来确定这个颗粒度,是由应用来决定的,从上图可以看到,你可以在一个表里定义多个不同的文档分类关键字,也可以将整个表的PartitionKey都定义成一个,一样的关键字,这样分区就扩大到整个一张表,版本号就成为真正的关键字了。从规则3可以看出Azure的表存储的设计原则,一是侧重于直接贴近业务场景和实体对象,第二是要方便查询。

规则4Azure运行环境会根据PartitionKey来对实体数据进行聚集和索引,PartitionKey是你进行实体查询的首要关键字,相当于你SQL 查询中最主要的Where条件。从伸缩性上来看,Azure运行环境会将不同分区的实体/数据放到不同的存储节点上,它尽力会将一个分区的数据(PartitionKey关键字相同的数据)放在一个存储节点上,而不是多个存储节点上,同时会对这个存储节点优先作分区的负载均衡(Automatically load balance partitions)

clip_image006

Pictures source: ES07.pptx of PDC 2008-Windows Azure Tables:Programming Cloud Table Storage

 

从上图看,我们根据规则4可以得出下面的结论:因为只要搜索单个分区,所以你查询所有FAQ Doc类的文档速度会很快;同样,如果你查询从截至到2008年5月30日之前所有的FAQ Doc类文档则速度会很慢,因为 这个查询需要检索多个分区,而如果每个分区落在不同的存储节点上,那就需要花费更多的时间。

 

现在我们假设你要为中国移动设计一个Azure 版本的应用,就表存储设计来说,你将整个中国移动的客户放在一个表里可能定是不现实的,你一定会考虑分区,PartitionKey可以是地域属性,比如你按省来分区;RowKey是手机号码;你也可以将PartitionKey设定成移动业务的品牌;RowKey是手机号码;也许这样你还决得大,你会将一个省的客户数据放到一个表里,实体变成了广东省移动客户,这样PartitionKey可以变成省下面的地州市,比如广州市,深圳….RowKey是手机号码,甚至你还是觉得不够细,你可以将广州市的用户放到一个表里,实体变成了广州市移动客户,PartitionKey可以变成市下面的区,比如白云区,天河区,RowKey可以换成业务类型;甚至你还想细分……所以,上面说了数据的细分或说颗粒化取决于应用,但你应该了解Windows Azure的设计和规则。

针对规则4,这也可能涉及到实体的单表扩展还是多表扩展,也会涉及到一个实体对应多个表,多个实体对应一个表,一个实体在不同状态下有不同数量/动态的属性等等这样的设计难题和争议。我就不涉及太多。

 

规则5每个实体最多拥有255个属性,但自定义的只有253个甚至更少一些,因为所有的实体都有两个固定的关键属性PartitionKey和RowKey。属性的定义和定义脚本是相当灵活和没有限制的,比如可以将两个不同属性/不同属性个数的实体定义在一个表中。

了解了上面的这些规则,只完成了我们第一项工作,Azure表存储的概念和设计概念。真正的如何定义表的操作,以及访问表存储是我们接下来要做的。

Azure 表存储的访问技术是最眩和最新的技术,比如完全兼容ADO.NET data services,使用最新的.NET 3.5 SP1中的.NET Client 类库访问,编程查询语言使用LINQ……. Windows Azure平台的开发模型涵盖了微软最新的所有的先进技术,这样不枉费各位粉丝从.NET 1.0到.NET 4.0的追随和勤奋学习,这下总算有用武之地了。

而后我就转到一个最简单的表存储的Azure 的应用,来演练和探索一些Azure表存储的基本操作和编程技术。因为如果了解了上述的规则和设计要点,我认为基本是掌握Azure表存储技术的关键,后面的过程都是看图说话和一些体力活-操作键盘和鼠标。

 

接下来你可以选择按照Jim Nakashima的文章Windows Azure Walkthrough: Simple Table Storage来体验,也可以继续按下面的步骤体验,我的操作选自Azure Services Training Kit - PDC Preview的一个练习,两者的差别是Jim Nakashima的文章涉及更多的一些话题Create Tables Only Once这样有关高性能的示范,因为Jim Nakashima决定他应该提供高质量的示范代码J

这个练习主要是熟悉表存储的创建和访问,运行后的界面如下图:

clip_image008

对于这样一个简单的聊天留言板,数据表更是简单,基本上是一个叫聊天消息的实体,实体有两个属性/字段,聊天人的名字(Name)和聊天内容(Body),当然根据上面的规则,还有ParitionKey 和RowKey这两个关键和固定字段。

首先依然是向上一篇访问队列那样,进行Azure服务的定义和配置。分别定义TableStorageEndpoint,AccountName和AccountSharedKey的属性

 

服务定义文件ServiceDefinition.csdef, 这个例子还没有用到Worker Role ,也可不配置

<?xml version="1.0" encoding="utf-8"?>
 
<ServiceDefinition name="WorkingWithTables" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
 
<WebRole name="WebRole">
 
<ConfigurationSettings>
 
<Setting name="TableStorageEndpoint"/>
 
<Setting name="AccountName"/>
 
<Setting name="AccountSharedKey"/>
 
</ConfigurationSettings>
 
<InputEndpoints>
 
<!-- Must use port 80 for http and port 443 for https when running in the cloud -->
 
<InputEndpoint name="HttpIn" protocol="http" port="80" />
 
</InputEndpoints>
 
</WebRole>
 
<WorkerRole name="WorkerRole">
 
<ConfigurationSettings>
 
<Setting name="TableStorageEndpoint"/>
 
<Setting name="AccountName"/>
 
<Setting name="AccountSharedKey"/>
 
</ConfigurationSettings>
 
</WorkerRole>
 
</ServiceDefinition>
 

 

服务配置文件ServiceConfiguration.cscfg中,定义在服务定义文件中各个参数的值

<?xml version="1.0"?>
 
<ServiceConfiguration serviceName="WorkingWithTables" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
 
<Role name="WebRole">
 
<Instances count="1"/>
 
<ConfigurationSettings>
 
<Setting name="TableStorageEndpoint" value="http://127.0.0.1:10002"/>
 
<Setting name="AccountName" value="devstoreaccount1"/>
 
<Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
 
</ConfigurationSettings>
 
</Role>
 
<Role name="WorkerRole">
 
<Instances count="1"/>
 
<ConfigurationSettings>
 
<Setting name="TableStorageEndpoint" value="http://127.0.0.1:10002"/>
 
<Setting name="AccountName" value="devstoreaccount1"/>
 
<Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
 
</ConfigurationSettings>
 
</Role>
 
</ServiceConfiguration>
 

Azure 服务的表存储存取API,依然是采用了SDK中的StorageClient 类库,然后分别在Web Role项目中添加StorageClient的引用

clip_image010

接下来的步骤是关于表存储种中比较重要的一个工作,那就是建立一个实体类与表模型的脚本

进一步的说,我们不用写数据库的建表脚本来建立在Azure存储系统中建了表存储,而是根据上述的规则,我们告诉Windows Azure我们的实体是什么,ParitionKey 和RowKey分别是什么,怎么赋值,即满足规则2和规则3的要求,Windows Azure自动就会帮你建立表,建立索引甚至存储节点。之后你通过公开的API就可以访问这些表了。

 

具体的操作是,我们需要从Microsoft.Samples.ServiceHosting.StorageClient. TableStorageEntity 继承派生出我们的聊天消息实体类,这个类的定义和描述,其实就是实体类与表模型的脚本,之后我们可以通过开发工具或代码执行,告诉Windows Azure建立这个实体的表存储。差别是如果通过开发工具表存储是预先建立的,代码建立表可以在运行时,访问表存储之前建立。这也就是上面说的Create Tables Only Once的问题了,因为这存在一个什么是创建表的最好时机的问题,本练习时通过开发工具来预先建立表存储。

 

聊天消息实体类的定义如下:

   1: using Microsoft.Samples.ServiceHosting.StorageClient; 
   2:  
   3: namespace WorkingWithTables_WebRole
   4:  
   5: {
   6:  
   7: public class myMessages : TableStorageEntity 
   8:  
   9: {
  10:  
  11: public myMessages()
  12:  
  13: {
  14:  
  15: PartitionKey = "mymessages";
  16:  
  17: RowKey = string.Format("{0:10}_{1}", DateTime.MaxValue.Ticks - DateTime.Now.Ticks, Guid.NewGuid()); 
  18:  
  19: // TimeSpan 
  20:  
  21: }
  22:  
  23: public string Name { get; set; }
  24:  
  25: public string Body { get; set; }
  26:  
  27: }
  28:  

在构造函数里,我们定义了最关键的ParitionKey 和RowKey是如何定义,因为这个表非常简单,所以ParitionKey的赋值我给的是一个固定的值,没有按照规则4和规则5的要求来优化,之后是我们的自定义的属性Name和Body。

之后顺手再定义一个表的访问类,非常像DAL,因为上面的只是一个表定义类,我们的应用最终是需要访问这个表存储中的数据。这个我们需要从Microsoft.Samples.ServiceHosting.StorageClient. TableStorageDataServiceContext 继承派生一个类,就可以创建自己的数据查询和访问的类:

   1: using Microsoft.Samples.ServiceHosting.StorageClient; 
   2:  
   3: public class MessageDataServiceContext : TableStorageDataServiceContext
   4:  
   5: {
   6:  
   7: public MessageDataServiceContext(StorageAccountInfo accountinfo)
   8:  
   9: : base(accountinfo)
  10:  
  11: { 
  12:  
  13: }
  14:  
  15: public IQueryable<myMessages> Messages
  16:  
  17: {
  18:  
  19: get
  20:  
  21: {
  22:  
  23: return this.CreateQuery<myMessages>("Messages");
  24:  
  25: }
  26:  
  27: }
  28:  
  29: public int AddMessage(string name, string body)
  30:  
  31: {
  32:  
  33: int ret = -1;
  34:  
  35: try
  36:  
  37: {
  38:  
  39: this.AddObject("Messages", new myMessages { Name = name, Body = body });
  40:  
  41: this.SaveChanges();
  42:  
  43: ret = 0; 
  44:  
  45: }
  46:  
  47: catch (Exception e)
  48:  
  49: {
  50:  
  51: throw e;
  52:  
  53: }
  54:  
  55: finally
  56:  
  57: {
  58:  
  59: }
  60:  
  61: return ret; 
  62:  
  63: }
  64:  
  65: }
  66:  

 

之后在Azure的开发环境中预先建立表存储

操作如下:在Visual Studio中选中的你的项目,然后右键,点击”Create Test Storage Tables”

如下图:

clip_image012

之后会显示下面的提示框表示表已经成功创建了。

clip_image014

你可以查看Visual Studio的输出窗口:

clip_image016

你会发现Visual Studio使用了SDK中的命令行工具DevtableGen.exe,执行了下面的命令:

C:\Program Files\Windows Azure SDK\v1.0\bin\DevtableGen.exe" /forceCreate "/server:localhost\SQLExpress" "/database:WorkingWithTables" "obj\Debug\WorkingWithTables_WebRole\bin\StorageClient.dll;obj\Debug\WorkingWithTables_WebRole\bin\WorkingWithTables_WebRole.dll;C:\myProject\WorkingWithTables\WorkingWithTables\WorkingWithTables_WorkerRole\bin\Debug\StorageClient.dll;C:\myProject\WorkingWithTables\WorkingWithTables\WorkingWithTables_WorkerRole\bin\Debug\WorkingWithTables_WorkerRole.dll"

 

我们不妨到数据库中看一下建立的这个数据库和表

clip_image018

最后我们在WebRole中增加一些代码,来存取访问表存储的简单代码

查询和数据绑定

   1: protected void Page_Load(object sender, EventArgs e)
   2:  
   3: {
   4:  
   5: string statusMessage = String.Empty;
   6:  
   7: try
   8:  
   9: {
  10:  
  11: StorageAccountInfo accountInfo = StorageAccountInfo.GetAccountInfoFromConfiguration("TableStorageEndpoint");
  12:  
  13: // dynamically create the tables
  14:  
  15: TableStorage.CreateTablesFromModel(typeof(MessageDataServiceContext), accountInfo);
  16:  
  17: MessageDataServiceContext context = new MessageDataServiceContext(accountInfo);
  18:  
  19: this.messageList.DataSource = context.Messages.Take(10);
  20:  
  21: this.messageList.DataBind();
  22:  
  23: }
  24:  
  25: catch (DataServiceRequestException ex)
  26:  
  27: {
  28:  
  29: statusMessage = "Unable to connect to the table storage server. Please check that the service is running.<br>"
  30:  
  31: + ex.Message;
  32:  

 

向表存储中增加记录

   1: protected void SubmitButton_Click(object sender, EventArgs e)
   2:  
   3: {
   4:  
   5: StorageAccountInfo accountInfo = StorageAccountInfo.GetAccountInfoFromConfiguration("TableStorageEndpoint");
   6:  
   7: MessageDataServiceContext context = new MessageDataServiceContext(accountInfo);
   8:  
   9: context.AddMessage(this.nameBox.Text, this.messageBox.Text);
  10:  
  11: }
  12:  

 

F5 运行后,先检查开发环境中的表存储服务是否启动。

clip_image020

运行成功之后,我们还可以看一下SQL Server 数据库中的情况,比如Azure 开发环境对ParitionKey 和RowKey的理解和赋值情况

clip_image022

通过上面的体验,我们发现Azure的表存储的定义和操作,与传统的表定义和操作还是有些不同,另外在编程体验上基本上完全整合了ADO.NET Data ServiceADO.NET Entry Framework的风格和技术。

不过我们需要有比较清晰的概念和理解,就是目前Windows Azure 可能实现的是ADO.NET Data ServiceADO.NET Entry Framework的一个子集,如果对ADO.NET Entry Framework比较熟悉的,可能非常想知道ADO.NET Entry Framework的特性有多少在Azure上实现了,这个需要我们在之后慢慢探索。

最后花一点时间,讨论一下Jim Nakashima的文章Windows Azure Walkthrough: Simple Table Storage中的要点,也就是在真正部署到正事Windows Azure运行环境时,比较流行的方法是使用上面讲的第二种方式,就是运行时刻创建表存储,而且最好是只创建一次。这个文章中讲了,主要是使用TableStorage.CreateTablesFromModel方法来实现。Jim解释说在运行环境下建表过程是程序化完成的,但是在Azure 的开发环境(Local Development Storage)使用工具预先建立表示必须的步骤,这是Azure 的开发环境的限制。 我想这非常可能是在Widnows Azure 的运行环境中,表存储不是简单理解成在某个一个SQL数据库上建立一个表这么简单,可能其过程是非常复杂和神奇的。 而Mark Seemann给出了使用 另外一方法,就是用PowerShell的方法在Azure 运行环境创建表,基本上就是用Shell 脚本调用TableStorage.CreateTablesFromModel方法。

 

Azure 服务的表存储的表的命名规则在上篇有关队列命名规则1中已有论述的,另外Azure 服务的表存储是保存在Azure运行环境的数据库中,所以也是可靠和持久性的存储(Durable Storage)。

 

相关的示范代码可以在我的MSDN代码库中下载获得。代码在在Azure SDK and Tools Jan 2009 CTP环境下测试通过。

Posted by ccBoy | 1 Comments
Filed under:

Azure Service探索—存储之队列(Queues)存储

del.icio.us Tags: ,

之前的一篇探索是有关Azure服务的文件系统存储的,我们看到了LocalStorage的不可共享性和临时性,同样也是在那篇文章中我们也涉及到了Azure编程模型中两个比较重要的模型和角色,Web Role和Worker Role,了解Web Role和Worker role之间的关系,是使用和掌握队列存储的关键。

根据MSDN的官方定义:

Web role: A web role is a Web application accessible via an HTTP and/or an HTTPS endpoint. A web role is hosted in an environment designed to support a subset of ASP.NET and Windows Communication Foundation (WCF) technologies.

Worker role: A worker role is a background processing application. A worker role may communicate with storage services and with other Internet-based services. It does not expose any external endpoints. A worker role can read requests from a queue defined in the Queue service.

简单的说,一个Web Role是一个能够通过HTTP/HTTS访问的Web应用,而Worker role 则是一个无法通过HTTP/HTTPS访问的后台处理应用。一般根据Azure的定义一个Azure服务至少有一个Web Role,而Worker Role则是可选的,不一定要有。 你也可以理解成Web Role是前台应用,比如一个你熟悉的网上商店,Web Role负责展示商店的商品,进行商品的搜索,以及接受用户的订购申请。而订购申请产生后,是否被接受和处理了,则是由Worker Role这样的后台应用在进行处理,比如检查库存是否有货,用户的信用情况,货物发送到物流公司的状态更新。

就像下面这幅图显示的, WebRole是负责卖咖啡的,而Worker Role就是那个负责制造咖啡的。在Windows Azure的设想里,其实这个卖咖啡的和这个制造咖啡的可以是处于不同生产环节上的两个厂商,他们分别将其应用托管到Windows Azure上,对于这连个应用之间的协作和消息通讯,Windows Azure可以提供一个很好的解决方案,比如.NET Services 中的Service Bus 和 Workflow Service,这是针对两个独立的应用(可能是坐落在不同云朵上的两个云应用)之间,对于我们单独的一个咖啡应用之间,卖咖啡的和做咖啡之间的消息传递就可以考虑使用队列服务或队列存储,这是一个非常典型的生产者-消费者的模型。

clip_image002

也就是相对复杂一些的应用场景,我们一个单独Azure服务会同时需要Web 角色和Worker角色,甚至可能是一个Web角色,多个后台Worker角色,队列存储其实解决的就是前台角色和后台Worker角色消息共享和传递的问题。解决消息共享,管理消息的就是消息存储负责的,至于消息传递,消息的可靠性,则是由消息服务来负责的。

除了一个Azure服务的内部各个组件和角色之间通讯外,还有应用组合的场景中也可以考虑使用Azure的队列存储和服务。比如下面的这个例子:

clip_image004

Pictures source: http://blog.smarx.com/posts/windows-azure-worker-role-to-deal-with-spam

Steve Marx举的这个例子场景就是,他现在想做一个云计算版本的Weblog应用,但他也需要解决垃圾评论的问题。而TypePad AntiSpam对于垃圾评论已经实现了一个很好的解决方案,而且TypePad AntiSpam也提供了一个公开的API和接口,那么Steve Marx的Weblog应用就完全可以将TypePad的垃圾评论能力聚合到自己的Weblog应用中。

大致的流程如下:

1. 某个读者访问Steve Marx的云版Weblog应用(Web Role),并且写了自己的评论。

2. Web Role 先将读者的评论保存在Azure 的表存储中,将其状态设置为审核中不显示。

3. 然后Web Role 再通过Azure队列,放一个消息,告知一个编号的评论需要审批,看是否是恶意或垃圾评论

4. Worker Role一般在Web Role启动后不久也会开始工作,它检查队列里的消息,一旦检查到了,它便可以通过消息里的编号,去Azure 的表存储中找到具体的评论信息,用户信息以及用户是针对哪篇文章发表的评论。

5. 之后Worker Role 带着这些信息(TypePad AntiSpam服务需要的信息),调用TypePad的垃圾评论检测服务的接口,从而确定这个用户的这个评论的有效性。

6. 获得TypePad AntiSpam返回的结果,Worker Role更新Azure 的表存储中这个评论的状态,比如设置成,审核通过可显示,或审核未通过不显示。

了解了设计层面和使用的场景,技术和代码层面就比较简单了。我修改了上篇文章的项目设计一个简单的场景来模拟,并探索Azure 队列的功能。

应用界面如下:

clip_image006

前台的Send,负责产生需求和输入,并将其放到队列中,Refresh则读取队列,看看队列中有哪些需求和输入没有处理,后台的Worker Role则读取队列中的消息,并且将其处理(从队列中删除)

这次我们就直接创建一个同时包含Web role和 Worker Role的项目:

clip_image008

创建项目后,在写代码之前,我们还需要做两件事,一是配置和定义Azure 服务,另外一个是确定访问/存取Azure 队列存储的组件。

在目前的Azure运行环境中,队列存储,表存储和Blob存储服务都是以REST的协议提供了访问接口,基本上你如果要使用这些服务,只要有一个访问/存取Azure 队列存储的组件/客户端,以及访问的用户和密码就可以了。

访问的用户和密码我们可以配置在Azure 服务的配置文件中,而一个访问/存取Azure 队列存储的组件/客户端,现在流行的是直接使用SDK中的StorageClient 类库,这个类库在 Azure SDK的\StorageClient\Lib 目录下,我喜欢将其编译后,放到单独的一个目录,以后再项目中直接添加文件引用。命名空间是:Microsoft.Samples.ServiceHosting.StorageClient

首先是要在服务定义文件ServiceDefinition.csdef中定义队列相关的用户名和密码,队列访问地址,因为Web role和Work role 都要存取和访问队列,所以都需要进行定义

<?xml version="1.0" encoding="utf-8"?>
 
<ServiceDefinition name="WorkingWithQueues" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
 
<WebRole name="WebRole">
 
<ConfigurationSettings>
 
<Setting name="QueueStorageEndpoint"/>
 
<Setting name="AccountName"/>
 
<Setting name="AccountSharedKey"/>
 
</ConfigurationSettings>
 
<InputEndpoints>
 
<!-- Must use port 80 for http and port 443 for https when running in the cloud -->
 
<InputEndpoint name="HttpIn" protocol="http" port="80" />
 
</InputEndpoints>
 
</WebRole>
 
<WorkerRole name="WorkerRole">
 
<ConfigurationSettings>
 
<Setting name="QueueStorageEndpoint"/>
 
<Setting name="AccountName"/>
 
<Setting name="AccountSharedKey"/>
 
</ConfigurationSettings>
 
</WorkerRole>
 
</ServiceDefinition>

之后在服务配置文件ServiceConfiguration.cscfg中,定义在服务定义文件中各个参数的值

<?xml version="1.0"?>
 
<ServiceConfiguration serviceName="WorkingWithQueues" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
 
<Role name="WebRole">
 
<Instances count="1"/>
 
<ConfigurationSettings>
 
<Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
 
<Setting name="AccountName" value="devstoreaccount1"/>
 
<Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
 
</ConfigurationSettings>
 
</Role>
 
<Role name="WorkerRole">
 
<Instances count="1"/>
 
<ConfigurationSettings>
 
<Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
 
<Setting name="AccountName" value="devstoreaccount1"/>
 
<Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
 
</ConfigurationSettings>
 
</Role>
 
</ServiceConfiguration>
 

然后分别在Web Role和Worker Role的项目中添加StorageClient的引用

clip_image010

这里可以看一下主要的几个代码

创建消息队列和添加到消息的代码:

   1: StorageAccountInfo account = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
   2:  
   3: QueueSvc = QueueStorage.Create(account);
   4:  
   5: queue = QueueSvc.GetQueue("mymessage090120");
   6:  
   7: if (!queue.DoesQueueExist())
   8:  
   9: {
  10:  
  11: queue.CreateQueue();
  12:  
  13: }
  14:  
  15: if (queue != null)
  16:  
  17: {
  18:  
  19: Message msg = new Message(InputBox.Text);
  20:  
  21: queue.PutMessage(msg); 
  22:  
  23: InputBox.Text = "";
  24:  
  25: }
  26:  

刷新功能,主要是使用PeekMessages方法来读取消息队列:

   1: MsgBox.Items.Clear();
   2:  
   3: IEnumerable<Message> msgs = queue.PeekMessages(10);
   4:  
   5: if (msgs != null)
   6:  
   7: {
   8:  
   9: foreach (Message item in msgs)
  10:  
  11: {
  12:  
  13: MsgBox.Items.Add(item.ContentAsString());
  14:  
  15: }
  16:  
  17: }

Worker Role的处理消息的代码:

   1: public override void Start()
   2:  
   3: {
   4:  
   5: // initialize the account information
   6:  
   7: Uri baseUri = new Uri(RoleManager.GetConfigurationSetting("QueueStorageEndpoint"));
   8:  
   9: string accountName = RoleManager.GetConfigurationSetting("AccountName");
  10:  
  11: string accountKey = RoleManager.GetConfigurationSetting("AccountSharedKey");
  12:  
  13: StorageAccountInfo account = new StorageAccountInfo(
  14:  
  15: baseUri,
  16:  
  17: null,
  18:  
  19: accountName,
  20:  
  21: accountKey);
  22:  
  23: //retrieve a reference to the messages queue.
  24:  
  25: QueueStorage service = QueueStorage.Create(account);
  26:  
  27: MessageQueue queue = service.GetQueue("");
  28:  
  29: while (true)
  30:  
  31: {
  32:  
  33: Thread.Sleep(10000);
  34:  
  35: if (queue.DoesQueueExist())
  36:  
  37: {
  38:  
  39: Message msg = queue.GetMessage();
  40:  
  41: if (msg != null)
  42:  
  43: {
  44:  
  45: RoleManager.WriteToLog("Information",
  46:  
  47: string.Format("Message '{0}' processed.", msg.ContentAsString()));
  48:  
  49: queue.DeleteMessage(msg);
  50:  
  51: }
  52:  
  53: }
  54:  
  55: }
  56:  
  57: }
  58:  

 

最后的有关队列的简单规则

看到“mymessage090120”这个队列名了吗? 这是我想告诉的另外一个注意事项,那就是Azure Container的命名规则,队列名,表名这些在Azure中都算是一个容器(Container)的标示,容器名必须是一个有效的DNS (Domain Name System)的名称,并遵守下面规则

 

规则1队列、Blob容器和表的名称的命名规则

· 名称必须以数字或字母开头,并且有字母,数字,. 符号(period)和-符号(dash)组成

· 所有的字母必须是小写

· 名称的长度在3-63个字符之间

· 在. 符号后面不能直接跟-符号

简单说来,就是必须符合这个正则表达式 ^([a-z]|\d){1}([a-z]|-|\d){1,61}([a-z]|\d){1}$,另外一个是表名的约束ValidTableNameRegex = @"^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$"

我一开始没有注意到这些,使用了大写字母,结果 Debug了折腾了一圈才找到错误。

 

规则2 一个队列能够包括无数个消息,每个消息的大小不能超过8K(MaxMessageSize = 8 * 1024),消息在队列中的最大存活时间为7天(MaxTimeToLive = 7 * 24 * 60 * 60)

相关的信息,可以在Queue.cs 和RESThelpers.cs 中找到一些。

 

规则3:队列存储是跟随和绑定在一个Azure平台的帐号下的,队列存储中包含无限量的消息。从REST的路径上可以看出http://<Account>.queue.core.windows.net/<QueueName>

规则4:Azure队列服务提供的是可靠的队列化的消息服务,它能确保消息至少被接收到一次,适合在异步的任务分发和查询场景中,并且只要Azure平台的帐号不变,队列存储本身是可靠和持久性的存储(Durable Storage)。

 

相关的示范代码可以在我的MSDN代码库中下载获得。代码在在Azure SDK and Tools Jan 2009 CTP环境下测试通过。

Posted by ccBoy | 2 Comments
Filed under:

Azure Service探索—存储之本地(LocalStorage)存储

del.icio.us Tags:

Azure Service将整个服务对存储的需求抽象成四个对象,它们分别是Table存储,Blob存储,队列(Queues)存储和本地临时文件存储(LocalStorage)。其中Table存储和Blob存储基本上应用最常用的存储对象,也是托管环境中的重要能力之一,可以理解为这两类存储Azure平台是要按流量或大小来计费和收费的。LocalStorage存储和队列存储也是能力,它往往是提供给某种应用场景的一种工具和服务,不会用来计费,因为从架构和设计上,它们的使用是受限,用途是特定的。

首先来看一下,Azure Service的本地临时文件存储--就是LocalStorage。

LocalStorage 是Azure Service能够自由使用的一种文件系统的存储,也许在某种场景中,你可以需要文件系统方式的存储,比如Jim Nakashima提到的Sitemap例子的场景,我认为这只是技术上的FAQ,比如,以前你的ASP.NET的编程环境你可以自由访问App_Data目录甚至任何目录,ASP.NET的编程环境在Azure中则对应到Web Role的服务或模型,你可以用一个新的函数….等等。

更深层次的地考虑是,Azure Service是被托管在Azure 运行环境中的,首先它必须遵循Windows Azure trust policy,其次还需要考虑的传统编程风格到云计算的变化,因为在Azure 运行环境中你面对的可能是一堆编号标示的虚拟机的运行实例,其次还有一个Load Balance的限制,你应该建立这样的观念,所有在Azure 运行环境中运行的Azure Service实例都是负载均衡的,如果是文件系统,那么在不同的节点上,这个文件是不能同步。所以LocalStorage的存在注定是受限的功能,受到上面说的信任策略和负载均衡的限制,后面的例子,我们会看到这一点。

首先使用在编写Azure Services时使用LocalStorage是很方便的。步骤如下:

首先在服务定义文件ServiceDefinition.csdef中,定义一个LocalStorage的引用,比如:

   1: <LocalStorage name="myFileStorage /> 

   1: <LocalStorage name="myFileStorage" sizeInMB="2"/>

 

sizeInMB是可选项,表示你要这个文件存储分配多大的空间。服务配置文件ServiceConfiguration.cscfg则不用配置了。

然后你就可以使用RoleManager.GetLocalResource()方法来获得Azure 运行环境分配成功的文件系统路径的引用,然后像操作文件一样(更像我们操作资源文件)操作它了。

   1: ILocalResource resource = RoleManager.GetLocalResource("myLocStorage");
   2:  
   3: string path = Path.Combine(resource.RootPath, "messagesLogs.txt");
   4:  
   5: using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
   6:  
   7: …………..

通过上面的代码,我们看到,Azure运行环境将根据服务配置为不同的角色创建需要的文件系统存储,由Azure运行环境来管理存储和文件引用间的映射,提供给开发人员的是一个相对路径,我们只用对相对路径进行操作。这样的理解就和Jim Nakashima举例一样了,似乎RoleManager.GetLocalResource取代了Server.MapPath的功能。技术上是这样的,但后面我们发现还是有些区别的。

有了上面的代码,我做了一个小的例子如下:

image

这个例子是从Azure Services Training Kit - PDC Preview中摘录出来的,其实很像一个留言本,你从Input框输入一些文字,Send之后就会被保存到LocalStorage的文件中,按refresh就会从LocalStorage的文件中将文件内容都读出来。

运行这个简单的例子的时候,你会发现当Web Role中的实例设置成2个以上的时候(目前最大也就2个,ServiceConfiguration.cscfg 文件下<Role name="WebRole">小节下的<Instances count="2" />),你就会发现LocalStorage在多个Tenats中是不共享的,你不断Send新的字符,然后refresh会发现文件的内容是变化/减少的,因为请求可能会被另外一个实例(Tenats)所接收,并保存在一个单独的文件中。

规则1:LocalStorage文件存储不能被多个服务实例/ Tenats共享,不同的服务实例保留自己的一个文件系统实例和存储,彼此之间不共享。 所以LocalStorage不适合保存请求(Request)相关的状态和信息

规则2:LocalStorage文件存储也不能在同一服务的不同角色间进行共享。比如两个Web role之间或是一个Web Role和一个Worker Role之间。

你可以下载到我示范中的代码,我重新设计了一下例子,我增加了一个Worker Role,设想除了在前台界面通过Send的方式增加记录外,还可以通过后台的Worker Role,定期的增加一些记录,这样每次一刷新就可以看见更多的记录了。

当然Worker Role也可以使用LocalStorage,只是配置的位置和上面的Web Role不同,

<WebRole name="WebRole">
 
<LocalStorage name="myLocStorage" />
 
</WebRole>
 
<WorkerRole name="Worker" >
 
<LocalStorage name="myLocStorage" />
 
</WorkerRole>

在项目中增加Worker Role也非常简单,只用选中你的解决方案,然后右键,像这样

image

然后在Worker Role项目中增加几乎和Web Role一样的代码,就可以运行了,当运行之后,你会发现在前台界面一刷新,两者的内容又不一致了,通过Worker Role后台日志,你发现Worker Role确实在工作,但是显然它保存到自己的文件中了。

image

规则3:LocalStorage是非持续(non-persisted)的存储,也就是说当Azure服务/WebRole/Worker Role启动的时候,这个LocalStorage存储才存在,当它们挂起,重新启动后,这些存储会被清空;停止时,这些文件不存在或不可用。

你下载我的例子,也能很快地发现这一点,所以从某种意义上讲,LocalStorage不仅是本地受限的文件存储,而且也是临时性的存储。

牢记LocalStorage的不可共享性和临时性,你就真正能将LocalStorage用得恰到好处。

示范的代码是从Azure Services Training Kit - PDC Preview中摘录出来的进行修改的,也可以单独从我的MSDN代码库下载修改后的这个小项目,在Azure SDK and Tools Jan 2009 CTP环境下测试通过。

Posted by ccBoy | 3 Comments
Filed under:

Hello Windows Azure--云端版本

之前有做过一个最简单的Windows Azure的应用-Hello Windows Azure,之后在2008年的圣诞节又获得了Invitation Code to Windows Azure‏,这样就可以测试一下云端版本的Windows Azure应用了,也就是真正的Windows Azure Service。

因为应用非常的简单,其实主要就是根据Jim Nakashima的文章Deploying a Service on Windows Azure,对整个Windows Azure Service的部署进行一个实践和演练。

你可以将这篇Blog作为Jim Nakashima文章的中文版或图形版,我会贴更多的图。

根据Jim Nakashima文章的规划,整个从开发阶段到部署到云端运行会分为三个阶段:

第一:完成一个开发环境中完整应用,开发,测试和运行完成。

第二:如果应用涉及到云端的Windows Azure存储,那么这个阶段可以将开发环境的应用配置以云端的数据库帐号,进行测试确保数据访问这部分功能完整,另外由于Windows Azure存储是比较分割和独立的,基本以一个唯一的标示代表一种数据存储的能力,所以需要另外一个用户标示或访问的帐号,这个标示需要配置在配置文件中,而配置文件也需要上传到Windows Azure管理门户上,所以这个阶段,可以理解为Windows Azure存储相关的测试、配置和部署准备。因为我的这个应用没有使用任何的Windows Azure存储,所以可以不考虑这一步。

第三:则是将应用部署到云端的Windows Azure运行环境中。所以这篇文章的着重在第三阶段,即如何将一个应用部署到Windows Azure运行环境中,并且将其运行起来。正如下图标明的Jim Nakashima给出的部署步骤图示。

clip_image002

搞清楚了之后,就可以开始进行了各项工作了

首先,需要访问Windows Azure的管理门户,创建项目,在Windows Azure的运行环境中创建你的应用标示,然后进行应用的上传和配置。

访问:http://go.microsoft.com/fwlink/?LinkID=128009

转化后可能是下面的地址,之后输入你的Windows Azure帐号和密码。

https://lx.azure.microsoft.com/Cloud/Provisioning/Templates.aspx

选择New Project选项。

clip_image004

由于Windows Azure的运行环境是共享的托管运行环境,所以Project Label你可以想象成一个应用标示,之后你会发现其实它未来也可以会作为一个二级的域名(云服务名称)标示,而对于Windows Azure的运行环境本身而言,这个应用会有一个唯一的代码和你的Project Label/应用标示进行对应。

clip_image006

之后Windows Azure管理门户会让你选择一个服务名,如果你的服务名和Project Label刚好一样,这个服务名作为一个二级域名的访问资源,它需要在整个Windows Azure的运行环境唯一,如果已经存在那么Windows Azure管理门户会提示你,让你重新设定一个

clip_image008

设定了Project Label和服务名之后,意味着你可以进行到下一步,应用上传和配置阶段。此时它需要你将你的应用上传到Windows Azure的云端和托管环境中,Windows Azure会来决定部署到那台服务器上面。而且Windows Azure非常好的是,它在部署的过程中又进行了精细化管理,明显的将其分为了另外两个阶段,云端的部署测试阶段,和云端的运行阶段。

当项目完成之后,Windows Azure管理平台进入到上传部署阶段,首先你会进入到云端的部署测试阶段(Staging), 你会看到下图,云端的运行阶段(Production) 什么都没有,云端的部署测试阶段等待应用上传。

clip_image010

不过你会说,我上传什么应用啊,是将我的aspx还是C#代码上传上去吗? 这里还有一个应用打包的过程。这个是通过Windows Azure SDK提供的工具来完成的。也就是说Windows Azure不接受aspx还是C#代码文件,你需要用SDK中的cspack.exe来打包你的应用。当然如果你使用先进而强大的Visual Studio 2008,那么你可以通过图形化的方式来完成这个打包的过程。具体步骤如下:

首先你从上图,可以看到Windows Azure运行环境给你分配的应用标示—Application ID,你复制下这个ID和标示,然后到Visual Studio 2008选中项目选项,设置这个代码,如下图:

clip_image012

然后保存项目,重新编译项目。

然后在Visual Studio 2008中选中你的项目,并右键选择发布(Publish…..) 如下图

clip_image014

如果顺利完成,其实一般这步都会十分顺利的,你会发现Visual Studio 2008自动打开发布的目录,甚至会启动IE,访问下面这个地址:http://go.microsoft.com/fwlink/?LinkID=127998&AppID=0000000040007F1A

而0000000040007F1A正是你的应用标示。我的开发环境在一个虚拟机环境中,所以我会复制出发布目录中的文件,因为项目比较小,打包的文件和配置文件都比较小。

令人有些奇怪的是,我的项目是Hello,但是最后的发布目录似乎在C:\myProject\Hello\Hello\Hello\bin\Debug\Publish 目录中。

clip_image016

这下你会发现Visual Studio 2008已经帮你将应用打包好了,其实你需要感谢Windows Azure Tools for Microsoft Visual Studio,因为工作是它调用Windows Azure SDK的cspack.exe完成的。

有了打包的应用,你可以部署了,单击 Deploy…… 按键

clip_image018

之后就比较完美了,根据下面这幅图的要求,你基本上是按图填空,很简单了。

clip_image020

填空完毕,然后按Deploy按键。

clip_image022

之后你会发现下面的这个提示和警告,因为目前的Windows Azure运行环境对应用运行的实例有限制,基本上Web Role最大两个实例,后台的WorkRole也是两个(Note: The current constraint for the number of instances you can run on Windows Azure as part of the CTP is a maximum of 2 Web Role instances and 2 Worker Role instances.),不知道未来是否会增加还是超过的部分就要收费了。

这里可以啰嗦一下,其实这里面的技术含量还是挺高的,这意味着Windows Azure运行环境会帮你完成实例的生命周期管理,也就意味着可用性的提高,最大两个,技术上要么会有四个(一对一互备)或三个(N+1方式容灾) 。而且你之前设置的二级域名和这两个实例的可靠性,Windows Azure运行环境都会管理起来。

clip_image024

之后你只需要修改一下ServiceConfiguration.cscfg 文件即可—如下图,这个不用重新打包或发布一次,而且我相信,你知道这个故事之后,以后会在发布之前,先修改一下这个配置文件。

clip_image026

然后你需要准照下面的图示,去喝杯茶或稍稍等候一下,此时Windows Azure运行环境正在为你寻找合适的服务器进行程序化和自动化的部署。

clip_image028

如果顺利,你会迎来胜利的曙光, 出现下面的图示:

clip_image030

这表示你已完成了云端的部署测试阶段(Staging),此时你的应用已经成功地上传并且配置成功,Windows Azure运行环境已经完成了相关资源的查找,分配和配置,你可以在部署测试环境启动它,--如果成功表示它降落和被托管在一块云朵上了,并且被照顾的很好,而且也可以被运行了。运行成功,表示你的应用功能都正常了,唯一的差别是,这个应用还不能”出街见人” –即外界不可见状态,也可以理解为还没有和相关的域名进行绑定。

此时你还可以进行配置的查看,再修改一些参数等等。。。。。

clip_image032

如果没有什么问题或需要配置的,你可以启动这个应用了。启动成功后,如下图:

clip_image034

之后你点击中间的蓝色按钮(提升[Promote]到生产状态),即进入到云端的运行阶段。如下图:

(其实你会发现Promote会是一个非常好的功能,未来它也能让你转换到部署测试阶段(Staging))

clip_image036

你也会发现Windows Azure运行环境已经将你服务的访问地址开通,应用有了访问的域名。比如我的这个叫:http://ccboyhello.cloudapp.net

可以通过浏览器直接访问这个地址,你会发现下面的结果。呵呵,很好玩啊

clip_image038

Windows Azure有一个应用超市-Windows Azure Gallery,这样你可以登记你的服务,同样你也可以去里面找找,看看是否有我的这个,或是你喜欢的Windows Azure应用。

云端的感觉还是不错的,我喜欢Windows Azure,希望你也一样。

Posted by ccBoy | 0 Comments
Filed under:

Windows Azure in PDC 08

Developing and Deploying Your First Windows Azure Service
http://channel9.msdn.com/pdc2008/ES01/

Windows Azure: Architecting & Managing Cloud Services
http://channel9.msdn.com/pdc2008/ES02/

Windows Azure: Cloud Service Development Best Practices
http://channel9.msdn.com/pdc2008/ES03/

 

Windows Azure: Essential Cloud Storage Services
http://channel9.msdn.com/pdc2008/ES04/

Windows Azure: Modeling Data for Efficient Access at Scale
http://channel9.msdn.com/pdc2008/ES07/

Posted by ccBoy | 0 Comments
Filed under:

Windows Azure Resource

Windows Azure SDK  -msdn
http://msdn.microsoft.com/en-us/library/dd179367.aspx

Microsoft Cloud Computing Tools--msdn
http://msdn.microsoft.com/en-us/vstudio/cc972640.aspx

Azure Services Developer Portal ---Azure upload
http://go.microsoft.com/fwlink/?LinkID=128009

Cloudy in seattle --Jim Nakashima
http://blogs.msdn.com/jnak/default.aspx

Windows Azure Tools for Visual Studio
http://blogs.msdn.com/gusperez/

F# on Windows Azure
http://blogs.msdn.com/lukeh/default.aspx

Run ASP.NET MVC on Windows Azure
http://www.aaronlerch.com/blog/2008/11/01/run-aspnet-mvc-on-windows-azure/

Using Windows Azure SDK with SQL Server 2005/2008
http://www.indiangeek.net/2008/10/29/using-windows-azure-with-sql-server-20052008/

Design Strategies to Prepare for Azure and SQL Data Services
http://www.shanmcarthur.net/cloud-services/design-strategies-for-Azure-and-SDS

Posted by ccBoy | 1 Comments
Filed under:

ADO.NET Data Services 的Self-Hosting例子(.NET 3.5 SP1)

之前有写一篇关于ADO.NET Data Services hosting在一个控制台程序中的做法。
详细见--ADO.NET Data Services 的Self-Hosting实例
http://blogs.msdn.com/ccboy/archive/2008/01/12/ado-net-data-services-self-hosting.aspx
今天找了一下,发现原来的代码找不到,于是重新尝试了一下,看看.NET 3.5 SP1之后是否有变化。

ADO.NET Data Services逐渐成为一个非常不错的工具和武器,它完美的和ADO.NET Entity Framework结合,可以说很大程度弥补了EF目前分布式开发的短板,同时Data Services本身也成功的削弱了WCF原来基于SOAP的组件通信的沉重,可以说利用了WCF最好的部分。

例子分为测试了两种情况,一种是使用WCF配置文件的方式;一种是不需要配置文件的方式。
当baseAddress 参数传空,系统会默认是使用配置文件的方式。

image

另外例子尝试使用自定义的数据源,即非ADO.NET EF和 SQL To LINQ的方式。

例子可以在这里下载:
http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=ccBoy&ReleaseId=1391

下面的信息是非常有用的
Data Service Host (ADO.NET Data Services Framework)
http://msdn.microsoft.com/en-us/library/cc668805.aspx

NET Framework Class Library--WebServiceHost Class
http://msdn.microsoft.com/en-us/library/system.servicemodel.web.webservicehost.aspx

more information ...about VS2008 SP1 and .NET FX 3.5 SP1

ScottGu's - Visual Studio 2008 and .NET Framework 3.5 Service Pack 1 Beta
http://weblogs.asp.net/scottgu/archive/2008/05/12/visual-studio-2008-and-net-framework-3-5-service-pack-1-beta.aspx

What's New in the SP1 Beta? - ADO.NET and Entity Framework
http://blogs.msdn.com/adonet/archive/2008/05/12/what-s-new-in-the-sp1-beta.aspx

ADO.NET Entity Framework --MSDN 文档
http://vs2008sp1docs.msdn.microsoft.com/en-us/ms439009.aspx

Visual Studio 2008 and .NET Framework 3.5 Service Pack 1 Beta
http://weblogs.asp.net/scottgu/archive/2008/05/12/visual-studio-2008-and-net-framework-3-5-service-pack-1-beta.aspx

ADO.NET Entity Framework Query Samples
http://code.msdn.microsoft.com/EFQuerySamples/Release/ProjectReleases.aspx?ReleaseId=1013

ADO.NET Data Services Framework Beta 1 is Live! (Project Astoria)
http://blogs.msdn.com/astoriateam/archive/2008/05/12/ado-net-data-services-framework-beta-1-is-live.aspx

ADO Data Service AJAX Client
http://www.codeplex.com/aspnet
http://www.codeplex.com/aspnet/Wiki/View.aspx?title=AJAX

Brad Abrams-Visual Studio 2008 and .NET Framework 3.5 "SP1" Beta
http://blogs.msdn.com/brada/archive/2008/05/05/visual-studio-2008-and-net-framework-3-5-sp1-beta.aspx

ASP.NET Dynamic Data Preview
http://code.msdn.microsoft.com/dynamicdata

.NET 3.5 Enhancements Training Kit --updated to work with Visual Studio 2008 SP1 & .NET 3.5 SP1 Beta 1!
http://download.microsoft.com/download/9/5/5/95586df3-dd73-4a90-9bdf-a10f3cd6a254/.NET%20Framework%203.5%20Enhancements%20Training%20Kit-20080511.exe

.NET FX 3.5 sp1 with ADO.NET Entity Framework

Microsoft .NET Framework 3.5 Service pack 1 Beta
http://www.microsoft.com/downloads/details.aspx?FamilyId=23516C63-2DB2-4E7F-AABA-32B12D6E025C&displaylang=en

Microsoft Visual Studio 2008 Service Pack 1 Beta
http://www.microsoft.com/downloads/details.aspx?FamilyId=CF99C752-1391-4BC3-BABC-86BC0B9E8E5A&displaylang=en

NET Framework 3.5 Service Pack 1 Beta and Expression Blend
http://blogs.msdn.com/expression/archive/2008/04/18/vs2008sp1.aspx

Posted by ccBoy | 0 Comments
Filed under:

PowerCommands 1.1

如果你已经习惯了Coderush或Refactor! Pro的复杂和庞大,到时可以尝试一下 PowerCommands 1.1 for Visual Studio 2008
1.1版本包括下面的功能:

Enable/Disable PowerCommands in Options dialog
This feature allows you to select which commands to enable in the Visual Studio IDE. Point to the Tools menu, then click Options. Expand the PowerCommands options, then click Commands. Check the commands you would like to enable.
Note: All power commands are initially defaulted Enabled.
Format document on save / Remove and Sort Usings on save
The Format document on save option formats the tabs, spaces, and so on of the document being saved. It is equivalent to pointing to the Edit menu, clicking Advanced, and then clicking Format Document. The Remove and sort usings option removes unused using statements and sorts the remaining using statements in the document being saved.
Note: The Remove and sort usings option is only available for C# documents.
Note: Format document on save and Remove and sort usings both are initially defaulted OFF.
Clear All Panes
This command clears all output panes. It can be executed from the button on the toolbar of the Output window.
Copy Path
This command copies the full path of the currently selected item to the clipboard. It can be executed by right-clicking one of these nodes in the Solution Explorer:
The solution node; A project node; Any project item node; Any folder.
Email CodeSnippet
To email the lines of text you select in the code editor, right-click anywhere in the editor and then click Email CodeSnippet.
Insert Guid Attribute
This command adds a Guid attribute to a selected class. From the code editor, right-click anywhere within the class definition, then click Insert Guid Attribute.
Show All Files
This command shows the hidden files in all projects displayed in the Solution Explorer when the solution node is selected. It enhances the Show All Files button, which normally shows only the hidden files in the selected project node.
Undo Close
This command reopens a closed document , returning the cursor to its last position. To reopen the most recently closed document, point to the Edit menu, then click Undo Close. Alternately, you can use the CtrlShiftZ shortcut.
To reopen any other recently closed document, point to the View menu, click Other Windows, and then click Undo Close Window. The Undo Close window appears, typically next to the Output window. Double-click any document in the list to reopen it.
Collapse Projects
This command collapses a project or projects in the Solution Explorer starting from the root selected node. Collapsing a project can increase the readability of the solution. This command can be executed from three different places: solution, solution folders and project nodes respectively.
Copy Class
This command copies a selected class entire content to the clipboard, renaming the class. This command is normally followed by a Paste Class command, which renames the class to avoid a compilation error. It can be executed from a single project item or a project item with dependent sub items.
Paste Class
This command pastes a class entire content from the clipboard, renaming the class to avoid a compilation error. This command is normally preceded by a Copy Class command. It can be executed from a project or folder node.
Copy References
This command copies a reference or set of references to the clipboard. It can be executed from the references node, a single reference node or set of reference nodes.
Paste References
This command pastes a reference or set of references from the clipboard. It can be executed from different places depending on the type of project. For CSharp projects it can be executed from the references node. For Visual Basic and Website projects it can be executed from the project node.
Copy As Project Reference
This command copies a project as a project reference to the clipboard. It can be executed from a project node.
Edit Project File
This command opens the MSBuild project file for a selected project inside Visual Studio. It combines the existing Unload Project and Edit Project commands.
Open Containing Folder
This command opens a Windows Explorer window pointing to the physical path of a selected item. It can be executed from a project item node
Open Command Prompt
This command opens a Visual Studio command prompt pointing to the physical path of a selected item. It can be executed from four different places: solution, project, folder and project item nodes respectively.
Unload Projects
This command unloads all projects in a solution. This can be useful in MSBuild scenarios when multiple projects are being edited. This command can be executed from the solution node.
Reload Projects
This command reloads all unloaded projects in a solution. It can be executed from the solution node.
Remove and Sort Usings
This command removes and sort using statements for all classes given a project. It is useful, for example, in removing or organizing the using statements generated by a wizard. This command can be executed from a solution node or a single project node.
Extract Constant
This command creates a constant definition statement for a selected text. Extracting a constant effectively names a literal value, which can improve readability. This command can be executed from the code editor by right-clicking selected text.
Clear Recent File List
This command clears the Visual Studio recent file list. The Clear Recent File List command brings up a Clear File dialog which allows any or all recent files to be selected.
Clear Recent Project List
This command clears the Visual Studio recent project list. The Clear Recent Project List command brings up a Clear File dialog which allows any or all recent projects to be selected.
Transform Templates
This command executes a custom tool with associated text templates items. It can be executed from a DSL project node or a DSL folder node.
Close All
This command closes all documents. It can be executed from a document tab.

 

下载:
http://code.msdn.microsoft.com/PowerCommands

Posted by ccBoy | 1 Comments
Filed under:
More Posts Next page »
 
Page view tracker