凌华 的个人资料张凌华照片日志列表更多 工具 帮助

日志


11月21日

我们的.Net培训进入第一个项目练手阶段了(实验)

一周时间完成如下课题:

 

一,简要:

用Asp.net2.0  开发一套有用户管理的建议博客系统 。

二,要求:

1,使用三层架构
2,充分融会“Asp.net开发即组件开发”这句话
3,使用Theme , Masterpage .
4,充分体会“一类一接口”这句话
5,文章发布模块的输入框最好使用FCKEdit等第三方输入容器 。
6,支持多用户的博客发布及管理

三,功能模块:

前台:
    用户注册,登录
    文章发布,修改和删除

后台:
    用户管理(增删改查)
    文章类别管理
    文章发布以及管理

四,可参考原型

http://spaces.live.com/

blog1  blog2

五,页面描述

前台:

    Default.aspx (首页)
    BlogList.aspx (列表页)
    BlogDetail.aspx (具体博客页面)

后台:

    自行设计页面

六,提示

如何来区分用户?
可以考虑二级目录或者Request参数来区分 。

七,周期
每日三小时 , 一周完成

八,指导讲师:林立峰 ,张凌华

 

实验场景 (场地简陋,不过我们也不计较这个了):

DSC00141  DSC00142

DSC00145  DSC00146

(四个角落缩影)

11月9日

版本控制之SVN (二)

SVN的第二节 , 常规操作及项目合作

Redirect This URL

版本控制SVN(三)

这部分主要讲解
1,SVN 和Apache 结合 , 通过Http协议发布 。远程访问
2,SVN 和Trac, Apache 结合,配置项目管理系统
3,SVN 的其它高级应用 。

视频文件有些大,不上传了。有兴趣的去这里看看:

SVN第三节

11月8日

版本控制之SVN (一)

此为ISTG .net 培训的一章内容

 

10月25日

已经开始讲课几天了

已经开始讲课几天了。Show一下环境 。

DSC00134 DSC00135

DSC00136 DSC00137

DSC00138

手机拍的,凑合着看了 。

DSC00139 DSC00140

我住处的楼梯和录像用的三脚架 。

DSC00129 DSC00130

林立峰抽空玩了一下魔兽世界 。 我好像对此毫无兴趣 。

语言的课程,林立峰讲 , 分析,设计以及某些工具我来讲 。这次第一个班,学员是10-11位(有一位还不定)  。 基本不赔本而已。

ISTG 培训已经开课了,Show一下

这次培训开课几天了,一直没有机会show,那么现在就稍微展示一下,手机拍摄,效果包涵 。


上课的时候

住处的楼梯和拍摄用的三脚架

林立峰抽空的时候Show了一下魔兽,用投影玩。

3月22日

Delphi 中的XML Data Binding . 用于SOAP的好方法

Delphi 6 含有许多更新更强的XML支持功能,增加了XML文件编程,XML数据绑定向导,XML映象和BizSnap(SOAP/XML Web服务)。我在上一篇文章论述了Delphi 6中的XML文件编程(XML Document Programming)。本文是三篇论述Delphi 6中XML功能系列文章的第二篇,论述Delphi 6中的XML数据绑定(XML Data Binding)。

XML文件编程
XMLDocument组件让我们能够遍历和编辑XML文件。但是在上一篇文章中我提到了,我们只能与无类型节点打交道,必须记住节点元素的名字。这意味着无法进行实时编译调试!幸亏的是,如果Delphi只能处理这样简单的问题就不成其为Delphi了。运用XML的内容相关结构可以做更高级的应用,这就是Delphi 6的XML数据绑定向导(XML Data Binding Wizard)。

XML数据绑定
在Delphi 6的模块仓库(Object Repository)中可以找到XML数据绑定向导(XML Data Binding Wizard)。程序员能够用它生成相应的接口和类来访问与修改XML文件数据,诸如ClientDataSetXML数据,ADO XML数据,其它XML文件数据(如我们在前文用到的Clinic.xml,本文继续使用这个简单的XML文件作示例)。

现在开始吧,启动Delphi 6,在主菜单上选择File | New - Other,然后在仓库中选择XML Data Binding,如图1所示。

向导有三个页面。第一页定义XML纲(Schema)或XML文件(本例用Clinic.xml),如图2所示。
在资源输入框内输入XML纲(Schema)或XML文件。“选项”(Options)对话框定义编码选项和数据类型映射关系(Data Type map)。以后我们还会谈到这些选项。

向导的第二页显示了树结构和节点数据类型(亦即向导生成了些什么样的代码)。图3可以看到我的XML文件结构。
可以看到XML文件里描述的嵌套节点(ClinicsType与ClinicType)和单节点(String)。这时可以打开选项(Options)对话框(图4),修改编码(比如修改前缀)和数据类型映射。

向导的第三页显示生成的相应接口和类。可以把这些结果保存到文件(例如生成Clinic.xdb)。
结果(存储为Clinic.xdb文件)显示如下。我们得到一个ClinicsType类型的Clinics元素,其中包括ClinicType类型的Clinic系列元素。

  <?xml version="1.0"?>
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xdb="http://www.borland.com/schemas/delphi/6.0/XMLDataBinding">
    <xs:element name="Clinics" type="ClinicsType"/>
    <xs:complexType name="ClinicsType">
      <xs:annotation>
        <xs:appinfo xdb:docElement="Clinics"/>
      </xs:annotation>
      <xs:sequence>
        <xs:element name="Clinic" type="ClinicType" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
    <xs:complexType name="ClinicType">
      <xs:sequence>
        <xs:element name="Title" type="xs:string"/>
        <xs:element name="Date" type="xs:string"/>
        <xs:element name="Topics" type="xs:string"/>
      </xs:sequence>
      <xs:attribute name="No" type="xs:string"/>
    </xs:complexType>
  </xs:schema>

文件同时定义ClinicType类型的Clinic元素包含一系列字符串元素(Title, Date和Topics)。

生成代码
向导生成的代码可以直接在应用中使用。不幸的是,Delphi 6有时会产生“非法操作”的出错信息。重新执行一遍,它又能工作了。
以下是生成的代码(Clinic.pas):

  {****************************************************}
  {                                                    }
  {              Delphi XML Data Binding               }
  {                                                    }
  {         Generated on: 2001/11/07 00:37:00          }
  {       Generated from: D:D6ClinicsrcClinic.xml   }
  {   Settings stored in: D:D6ClinicsrcClinic.xdb   }
  {                                                    }
  {****************************************************}
  unit Clinic;
  interface
  uses xmldom, XMLDoc, XMLIntf;

  type

  { Forward Decls }

    IXMLClinicsType = interface;
    IXMLClinicType = interface;

  { IXMLClinicsType }

  IXMLClinicsType = interface(IXMLNodeCollection)
    ['{06723E03-662D-11D5-81CE-00104BF89DAD}']
    { Property Accessors }
    function Get_Clinic(Index: Integer): IXMLClinicType;
    { Methods & Properties }
    function Add: IXMLClinicType;
    function Insert(const Index: Integer): IXMLClinicType;
    property Clinic[Index: Integer]: IXMLClinicType
      read Get_Clinic; default;
  end;

  { IXMLClinicType }

  IXMLClinicType = interface(IXMLNode)
    ['{06723E04-662D-11D5-81CE-00104BF89DAD}']
    { Property Accessors }
    function Get_No: WideString;
    function Get_Title: WideString;
    function Get_Date: WideString;
    function Get_Topics: WideString;
    procedure Set_No(Value: WideString);
    procedure Set_Title(Value: WideString);
    procedure Set_Date(Value: WideString);
    procedure Set_Topics(Value: WideString);
    { Methods & Properties }
    property No: WideString read Get_No write Set_No;
    property Title: WideString read Get_Title write Set_Title;
    property Date: WideString read Get_Date write Set_Date;
    property Topics: WideString read Get_Topics write Set_Topics;
  end;

  { Forward Decls }

    TXMLClinicsType = class;
    TXMLClinicType = class;

  { TXMLClinicsType }

  TXMLClinicsType = class(TXMLNodeCollection, IXMLClinicsType)
  protected
    { IXMLClinicsType }
    function Get_Clinic(Index: Integer): IXMLClinicType;
    function Add: IXMLClinicType;
    function Insert(const Index: Integer): IXMLClinicType;
  public
    procedure AfterConstruction; override;
  end;

  { TXMLClinicType }

  TXMLClinicType = class(TXMLNode, IXMLClinicType)
  protected
    { IXMLClinicType }
    function Get_No: WideString;
    function Get_Title: WideString;
    function Get_Date: WideString;
    function Get_Topics: WideString;
    procedure Set_No(Value: WideString);
    procedure Set_Title(Value: WideString);
    procedure Set_Date(Value: WideString);
    procedure Set_Topics(Value: WideString);
  end;

  { Global Functions }

  function GetClinics(Doc: IXMLDocument): IXMLClinicsType;
  function LoadClinics(const FileName: WideString): IXMLClinicsType;
  function NewClinics: IXMLClinicsType;

这里有二个接口类型:IXMLClinicsType和IXMLClinicType;用二个类(TXMLClinicsType和TXMLClinicType)来执行这二个接口。另外还有三个全局函数:GetClinics (获得根元素),LoadClinics (从外部XML文件加载)和NewClinics (在内存生成新文件)。

用法
使用生成的Clinic.pas单元是很容易的。跟前一篇文章的做法一样,使用XMLDocument组件(在Inernet标签内)。不过我们不再使用无类型节点了,我们可以调用GetClinics函数获得IXMLClinicsType类型。以下是具体操作过程:

在Delphi 6建立一个新的应用(project)
在XML数据绑定向导指引下建立Clinic.pas文件(经过命名存盘 - 译者)
在主窗体上加入一个XMLDocument组件,其FileName属性为Clinic.xml
在主窗体的OnCreate事件中加入以下代码:
  procedure TForm1.FormCreate(Sender: TObject);
  var
    Clinics: IXMLClinicsType;
  begin
    Clinics := GetClinics(XMLDocument1);
  end;

把Clinics变量放到主窗体中是很有用的,这样就可以在主窗体运行期间使用Clinics接口。使用IXMLClinicsType变量类型要比以前使用普通XMLDocument组件方便多了。现在可以通过Get_Clinic方法来获得各个Clinic元素,还可以在特定位置插入新的Clinic元素。用Clinics.Clinic可以获得节点元素,用Getter和Setter方法可以得到或设置元素值。现在可以直接访问No, Title, Date, Topics等属性了:
  procedure TForm1.ButtonGetClick(Sender: TObject);
  var
    Clinic: IXMLClinicType;
  begin
    Clinic := Clinics.Clinic[0];
    EditNo.Text := Clinic.No;
    EditTitle.Text := Clinic.Title;
    EditDate.Text := Clinic.Date;
    EditTopics.Text := Clinic.Topics
  end;

可以在Clinic.pas中看到,Getter和Setter是方法而不是属性(实际上,我始终认为使用属性更清楚些)。但是Delphi 6让你看到的却是属性描述而不是方法本身(Delphi 6的另一个受欢迎的优点)。将上面这段代码与前一篇文章使用的方法相比较,就能感到操作方便多了。

下面的例子在XML树的末尾加入一个节点:
  procedure TForm1.ButtonAddClick(Sender: TObject);
  begin
    with Clinics.Add do
    begin
      No := '2001-2-8;  // 8th Clinic of the 2nd series of 2001
      Title := 'Special Kylix 2 Clinic';
      Date := '2001/12/21';
      Topics := 'Kylix 2 New Features'
    end
  end;

如果没有把XMLDocument组件的AutoSave设置为真,可以用以下方法保存更动结果:
  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    XMLDocument1.SaveToFile;
  end;

这就是XML数据绑定向导,一个非常方便的方法。它能做得越来越好。

下一篇文章:
我们已经看到了XML数据绑定的优点。不过好象还有点不“满足”,比方要遍历各个节点,存取节点值(不单单是字符串类型),虽然可用选项决定,但还是用Delphi 6的XML映象更好,它的功能更强。我们将在下一篇文章论述。

Delphi 调用 .net 开发的WebService 强类型处理

最近要写一个产品 。用到Delphi来做客户端,调用C#写的WebService   。 这里把自己的一些心得写一下:
调用方法是看了一篇老外的文章。经过我的实验,证实老外文章中方法是正确的,可恶的是很多小地方没有提及,花了我不少时间,这里我用红色字写出来,希望大家不要再走我的弯路。
  delphi调用返回数据集web services的方法:
  delphi通过httpRIO的控件,来接收soap。然后把soap写成xml文件,通过外部工具xml mapper生成一个xtr翻译文件。这个xtr就做为XMLTransformProvider的transformFile(翻译文件)。以后只要httpRIO传来的soap传给xmlTransformProvider后,xmTransformProvider就可以通过翻译文件(xtr)得到数据,提供给clientDataSet。
        是不是觉得很麻烦,dotnet里面dataset可以直接接住数据。不过我个人认为delphi还残留一点优势,毕竟win32程序从开始淘汰到正式淘汰还有漫长的岁月。就算longhorn系统出现,也仍有虚拟机来跑win32程序。那么使用delphi7结合web services开发,一方面可以暂时省去学习其他开发工具的时间,另一方面使用web services也可以为以后开发重用。如果项目时间紧,开发成员对vs.net开发环境不熟悉,倒是一种过渡性的开发方式。

下面文章摘于http://community.borland.com/article/0,1410,28631,00.html

Use ADO.NET datasets in Delphi
Ratings: be the first!  Rate It

Abstract: Learn how to use ADO.NET datasets in Delphi, using XML mapper to transform XML across the platforms. Demonstrated using a .NET web service and Delphi client. 

Use ADO.NET Datasets in Delphi


By Deepak Shenoy

Introduction

If you've experimented with Web Services, you might have hit some Microsoft .NET based Web Services which return data all right, but it's in the default XML format from ADO.NET. So you end up with some XML but you have no clue what to do with it! This article explains how you can take this XML, make sense out of it and even display it in a DB Grid.

Scope

I'm not going to explain much about .NET, or the ADO.NET XML format. What I'll talk about is the most probable case you'll encounter .NET datasets: as XML returned from a .NET based Web Service. If you're going to use .NET datasets in some other way, you might want to read this article to get an idea of how to make your Delphi application aware of them.

Hitting the .NET service

Let's start with a simple .NET service, as given in http://services.pagedownweb.com/ZipCodes.asmx. I've used the Web Service Importer in File | New | Other | Web Services and generated the Pascal files. Here's the declaration that looks odd:


  rtnZipDSResult = class(TRemotable)
  private
    Fs_schema: String;
  published
    property s_schema: String read Fs_schema write Fs_schema;
  end;

  ZipCodesSoap = interface(IInvokable)
  ['{FEF279A0-29EE-CF0B-FBB2-7DD79A5502CE}']
    ...
	function  rtnZipDS(const City_IN: String; const State_IN: String): rtnZipDSResult; stdcall;
	...
  end;
The rtnZipDS function returns a .NET dataset, as XML. Here, the s_schema of the rtnZipDSResult class is simply the dataset as XML in ADO.NET's default format. This means that a .NET client could easily get this XML and show it on a grid or a form - but can we do this with Delphi? Let's see.

Using the .NET service in Delphi

I've created a sample form , which looks like this:

Now we've setup the HTTPRio, and the code behind the Get Zip Codes button is:


procedure TForm1.Button1Click(Sender: TObject);
begin
  (HTTPRIO1 as ZipCodesSoap).rtnZipDS(edtCity.Text, edtState.Text);
end;
I've also added an event handler on the HTTPRio1.OnAfterExecute like so:

procedure TForm1.HTTPRIO1AfterExecute(const MethodName: String;
  SOAPResponse: TStream);
begin
  SOAPResponse.Position := 0;
  Memo1.Lines.LoadFromStream(SOAPResponse);

  SOAPResponse.Position := 0;
end;

This is only to display the returned content on to a Memo so we can figure out what to do with it. Here's how the form looks now:

Interpreting the .NET XML

We have to figure out how to get Delphi to USE this data. We would like to have a Client Data Set read the XML so we can display it all in a Grid. For that we'll have to use XDR transforms. No, that's not very complicated, and here's how we'll do it.

1. First we're going to save the XML returned into an XML file. I've saved it as "data.xml".
2. Run XML mapper from the Tools menu, and open this XML file. Here's a mega screen shot:

{用C#开发web services的时候,如果你这样写  oleDbDataAdapter1.Fill(ds,'tablename');}那么你是看不到上面橙色筐中的字段的。千万不要表明数据集中表名。你这样写就可以了,oleDbDataAdapter1.Fill(ds);就能显示字段了!

3. The ZIPDATA(*) means there's multiple rows of "ZIPDATA" available. Columns availabl are Zip, City, State, County and AreaCode. Let's double-click each one of these to add them to the transformation and then click the DataPacket from XML in the Create Menu. Here's what it all looks like:

4. Save the Transformation using File | Save | Transformation, as "Ziptrans.xtr". Don't try to test the transformation yet. (There's a bug in Delphi Source code that doesn't like SOAP namespaces in certain elements so it doesn't show up any data).

5. We'll now FIX this bug. The XTR file is an XML file which you can open in any text editor. Open it, and change the first line from:


<SelectEach dest="DATAPACKETROWDATAROW" from="soap:Envelopesoap:Body....">

   [Change To]

<SelectEach dest="DATAPACKETROWDATAROW" from="Envelopesoap:Body....">
The reason for this is that Delphi's XML Transform provider does not like the "soap:" in the first element of the "from" attribute. That might get fixed in some update pack, so this point might not apply

{from后面除了soap:,只要是单词后面有冒号的,该单词和冒号都要去掉,delphi才能显示数据}

6. We're nearly there. Drop a TClientDataset, a TXMLTransformProvider and a TDatasource on the form. Here's what the form looks like now:

Link the Grid, the Datasource and the ClientDataset, and set the ClientDataset's ProviderName to point to the XML Transform Provider. 7. Set the TransformRead.TransformationFile of the XMLTransformProvider to Ziptrans.XTR.

8. Now we need to set the data of the XML Transform Provider at run time. Here's some additional code in the HTTPRio's OnAfterExecute:


procedure TForm1.HTTPRIO1AfterExecute(const MethodName: String;
  SOAPResponse: TStream);
var
  XMLDoc: IXMLDocument;
begin
  SOAPResponse.Position := 0;
  Memo1.Lines.LoadFromStream(SOAPResponse);

  ClientDataset1.Active := FALSE;
  SOAPResponse.Position := 0;
  XMLDoc := NewXMLDocument;
  XMLDoc.Encoding := SUTF8; //应该是'SUTF8'并需要引用XMLIntf,XMLDoc两个单元
  SOAPResponse.Position := 0;
  XMLDoc.LoadFromStream(SOAPResponse);

  XMLTransformProvider1.TransformRead.SourceXmlDocument := XMLDoc.GetDOMDocument;
  ClientDataset1.Active := TRUE;
end;

You'll notice that we've created an XML Document , loaded it from the Received SOAP stream, and applied the transform to it. The client dataset gets data from the provider and displays the data :

That's it!

Amazing.

Thank you.

What's next?

This transformation is very specific to this particular service and XML schema. SO if you know what XML is going to be returned (the format) then you can use XML mapper to generate a transformation for it.

I haven't been able to write a "general" transform that can be applied to ANY .NET returned XML, but if anyone does I'd love to hear about it.

Also, why have I used the HTTPRio's OnAfterExecute, rather than manipulating the the s_schema parameter? There's another bug in Delphi that doesn't like parameters returned as XML. More revealed in this thread.

You can download all the code for this project at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=17807 or at http://www.agnisoft.com/soap/dotnetds.zip.

Deepak Shenoy(shenoy@agnisoft.com) is a Director at Agni Software, a software company in India that offers consultancy and offshore development solutions. It might be a while before his hair gets pointy so he's allowed to understand some technology.

2月6日

ASP.NET 缓存功能的不足

缓存功能有其自身的不足。例如,显示的内容可能不是最新、最准确的,为此,必须设置合适的缓存策略。又如,缓存增加了系统的复杂性并使其难于测试和调试,因此建议在没有缓存的情况下开发和测试应用程序,然后在性能优化阶段启用缓存选项。
Cache 即高速缓存 ,我想很多人对他的第一印象一定像我一样,感觉他一定能提高系统得性能和运行速度。的确。Net推出cache的初衷确实是这样的。那么cache是如何提高系统性能与运行速度呢?是不是在任何情况下用cache都能提高性能?是不是cache用的越多就越好呢?我在近期开发的项目中有所体会,写下来当作总结也希望能跟大家一起探讨探讨,有错误的地方希望大家批评指正。
1.       Cache 是如何工作的。
l         Cache 是分配在服务器上的一个公共的内存片。
所谓公共指的cache只要一创建是任何一个客户端浏览器都可以通过后台代码访问到它,它面向的是所有用户,相对而言session也是服务器上的一段内存,但他面向的是单个用户。它是服务器的一段内存块,也就是说每个cache一经创建就占用了服务器资源的。所以从这点来说我们就可以说:并不是cache越多越好。
l         cache 是有时间限制的,超过了服务器设定的过期时间,它就会被服务器回收。
l         c.cache 可以存放任何对象
2.       Cache 如何创建以及如何销毁。
l         创建cache
在。Net环境下通过Cache.Insert(string key,object o)方法创建。其中key 代表cache的ID,o代表存到cache里的对象。
l         销毁cache.
通过方法Cache.Remove(string key)其中key 代表cache的 ID.
l         调用cache.
Cache支持装箱/拆箱操作。如你可以把一个DataSet对象ds通过Cache.Insert(“dsCache”,ds)的方式存到Cache中,可以通过拆箱操作 DataSet ds = (DataSet)Cache[“dsCache”]来访问它。
 
3.       什么时候用cache.
Cache 一般用于数据较固定,用的较频繁的地方。例如可以把进销存系统中可以把产品信息存入cache,在用户调用产品信息时通过调用cache即可,这样从很大程度上减少了用户与数据库的交互,提高了系统的性能。反之,cache不适合用在数据变动快,使用范围很窄的地方。例如把一个具体采购单存入 cache中。
 
4.       cache 调用注意事项。
Cache是有时间限制的。超过了服务器设置的过期时间,就会被服务器回收。当cache被回收后对应的内存块就会被清空,再次通过cache[“cachekey”]访问对象时返回的就是null值。所以以下这种调用就会出现异常
DataSet ds = (DataSet)Cache[“cacheds”];
DataRow dr = ds.Table[0].Row[0];  //出错,ds为null值,不存在表0。
正确的写法应该是:
DataSet ds
If(Cache[“cacheds”] != null)
{
ds = (DataSet)Cache[“cacheds”];
}
Else
{
ds= GetDsFromDataBase();
}
 
DataRow dr = ds.Table[0].Row[0];

探索ASP.NET下的缓存机制

当Web站点的用户访问数量与日俱增时,有限服务器资源和网络带宽会不断的被消耗以致不能及时响应客户端的每一个请求,Web应用程序的性能也将因此而受到极大的影响。服务器的硬件升级以及应用程序的代码优化都不失为提高Web站点性能和加快Web程序响应速度的良方,但是如果我们仔细分析一下那些加重服务器以及网络负荷的看似繁杂的客户端请求,就会发现其中有很多请求是相似甚至完全相同的。然而在ASP.NET下,由于每个ASPx文件都被映射到由其自身以及codebehind的代码文件编译而成的dll文件以动态处理客户端请求,所以即使完全相同的请求也会导致服务器端应用程序的反复执行而最终生成了完全相同的响应。
  我们不妨采用“缓兵之计”来应对这些不计其数而又大同小异的用户请求,就是说将由这些请求而产生的响应(包括HTML文本以及生成响应时服务器端的数据对象)在服务器端或客户端缓存起来,对于同样的请求无需再作处理便可直接从缓存中得到响应。
二、ASP.NET下的缓存机制
  ASP.NET下的缓存机制主要分为两种:输出缓存(缓存ASPx页面和ascx用户控件)和对服务器端数据对象的缓存。这两种机制都是在首次请求对象时将对象存储在内存(或临时目录)中以避免重新创建满足先前请求的信息。当创建这种信息需要消耗大量服务器端资源时,缓存无疑能够极大的减轻服务器端负荷并提高Web应用程序的性能。
1.输出缓存
  输出缓存用来缓存HTML格式的服务器端响应,包括动态页面及用户控件。这种缓存机制实质上是对HTML协议中提供的缓存机制的一种封装和改进。当我们提到缓存时,通常会想到使用Internet Explorer浏览器浏览网页后保存在Temporary Internet Files文件夹下以供脱机浏览或后续浏览的缓存文件,实际上在HTML协议中,缓存可以发生在具备HTML 1.1功能的任何设备上,包括Web服务器以及代理服务器。在HTTP协议的消息头(HEADER)中定义了该消息的缓存策略,包括是否缓存、缓存到期时间、可见性策略(cachability)等。在ASP.NET中使用输出缓存可以采用高级别声明API或低级别编程API,后者可以更加灵活的设定缓存策略。

  前文中已经提到ASP.NET可以根据不同的客户端请求动态生成页面,这就要求我们所使用的输出缓存不能够只是简单的为每一个ASPx页面缓存一个静态页面,而是要为同一页面缓存多个不同的输出,或者说是多个版本。这样就可以根据不同用户请求中的细微差异来选择不同版本的页面作为响应。
  ASP.NET提供三种为输出缓存提供不同版本的方法:根据参数缓存页的版本、根据HTTP标头缓存页的版本以及根据自定义字符串缓存页的版本。
  根据参数缓存页的版本是指根据用户请求中的查询字符串和表单变量的不同取值为输出缓存不同的版本;根据HTTP标头缓存页的版本是指服务器端根据用户请求的HTTP标头中的某个或某些字段(如Accept-Charset、Accept-Encoding、Accept-Language等)的不同取值为输出缓存不同的版本。这两种方法可以解决一般情况下的输出缓存的多版本问题。前者实际上反映了ASP.NET页面根据用户的不同输入进行相应的操作并给出输出,而后者则更多地反映了客户端平台(操作系统、浏览器等)的具体情况。
  如果我们制定不同版本的标准更加复杂,比如说需要根据用户请求中的cookie值或者根据服务器端某一设定的值(如数据库中为程序设置的参数)来设置不同的版本,那么上面提到的两种方法就无能为力了。这时我们需要根据自定义字符串来缓存页的版本。在MSDN中,这种最具备灵活性的缓存页的不同版本的方法只是被简单的举例说明为可以根据浏览器的不同版本来生成不同版本的缓存页。实际上这种方法完全可以实现 前两种方法所能够实现的效果并且可以实现更加细致的缓存版本控制。
  让我们先来看看HTTP协议中是如何定义响应标头的Vary字段的:
  The Vary response-header field is used by a server to signal that the response entity was selected from the available representations of the response using server-driven negotiation. Field- names listed in Vary headers are those of request-headers. The Vary field value indicates either that the given set of header fields encompass the dimensions over which the representation might vary, or that the dimensions of variance are unspecified ("*") and thus may vary over any ASPect of future requests.
                   Vary  = "Vary" ":" ( "*" | 1#field-name )
  An HTTP/1.1 server must include an appropriate Vary header field with any cachable response that is subject to server-driven negotiation. Doing so allows a cache to properly interpret future requests on that resource and informs the user agent about the presence of negotiation on that resource. A server should include an appropriate Vary header field with a non-cachable response that is subject to server-driven negotiation, since this might provide the user agent with useful information about the dimensions over which the response might vary.
  ( 译文:服务器使用响应的标头字段“Vary”来表明响应实体是服务器端根据具体的请求而从不同版本的有效响应缓存中选择出的最合适的响应。 “Vary”标头中列出的字段名称取自请求标头集合。“Vary”字段的值或者是指出确定响应不同版本所依据的请求标头字段集合,或者是指出此依据并不确定(”*”)而是可以根据新的请求的任何一方面来确定。
               Vary  = "Vary" ":" ( "*" | 1#field-name )
  一个使用HTTP1.1协议的服务器必须为每一个由服务器端控制其版本的可以被缓存的响应包含一个合适的”Vary”标头字段。这样做使得缓存能够恰当的解释对此资源新提交的请求并通知用户代理对此资源进行缓存的版本控制策略。服务器应该为每一个由服务器端根据具体的请求而选择的不能被缓存的响应包含一个合适的”Vary”标头字段,因为这将为用户代理提供响应可能变化的有用信息。)
  由此可见,Vary字段可以包含请求标头中的多个字段,而服务器则根据这些字段的不同取值来生成不同版本的响应,这样看来上文中提到的根据HTTP标头(在HTTP协议中这些标头被称为“选择”请求标头(”selecting” request-headers))缓存页的多个版本正是对Vary字段的使用,或者说ASP.NET对HTTP协议中的这种机制作了很好的封装。上文中对Vary字段的定义中多次提到了”server-driven negotiation”,这是指服务器端根据具体的请求来选择最合适的响应的过程。选择的标准是被缓存的响应所具有的由Vary字段指定的所有标头的值必须和新到请求的对应标头值完全相同。下面这段文字很好的描述了这个选择过程:
  When the cache receives a subsequent request whose Request-URI specifies one or more cache entries including a Vary header, the cache must not use such a cache entry to construct a response to the new request unless all of the headers named in the cached Vary header are present in the new request, and all of the stored selecting request-headers from the previous request match the corresponding headers in the new request.
  (译文:当缓存接收到的后续请求的URI(统一资源标识,通常是URL)指定了一个或多个包含一个”Vary”标头的缓存项,只有当”Vary”标头的取值指出的所有标头都在新的请求中出现,并且所有已存储的作为选择依据的前一个请求的标头都和新请求的对应标头完全相同,缓存才能使用这样的缓存项来构建一个对新请求的响应。)
  这也是HTTP协议中对根据HTTP标头缓存页的不同版本的直接解释。我们之所以能够使用第三种方法,则是因为协议中还存在着如下定义:
A Vary field value of "*" signals that unspecified parameters, possibly other than the contents of request-header fields (e.g., the network address of the client), play a role in the selection of the response representation. Subsequent requests on that resource can only be properly interpreted by the origin server, and thus a cache must forward a (possibly conditional) request even when it has a fresh response cached for the resource.
  (译文:”Vary”标头的值如果为”*”则表示某些不属于请求标头字段内容(例如,客户端的网络地址)的未指定的参数在响应的不同版本的选择中起到重要作用。对该资源的后续请求只能够被原始服务器恰当地解释,因此即便是对该资源的响应有最新的缓存,新的请求也必须被缓存转发给服务器。)
  这说明当Vary字段的值是”*”时,服务器端不仅可以使用请求标头的字段,还可以根据其他的信息来选择不同的响应,或者说缓存不同版本的响应输出。
  下面我们举一个具体的例子来说明在ASP.NET中是如何使用自定义字符串来缓存页的不同版本的。假设在我们的Web应用程序中有一个登录页面Login.ASPx,这个页面的基本功能是让用户输入其用户帐号及密码,然后将信息提交给服务器并与数据库中所存用户信息进行验证,若通过验证则进入下一个页面,否则提示用户错误信息;除此以外该页面还检查数据库中的参数信息以决定是否在登录页面加载的同时显示帮助窗口。
  从上述分析中我们不难看出Login.ASPx页面在任何用户第一次进入该页面时可以根据是否现实帮助窗口而划分成两个典型的版本。而这一标准却需要我们从服务器端的数据库中取出,另外我们还要根据用户请求中的表单变量集合是否为空来判断用户是否第一次进入该页面(原因是用户第一次进入页面时发出的请求中尚未包含用户信息,表单变量集合必为空)。如果我们还需要因为某种原因(如数据库的更新及服务器的配置改变)而允许在Web应用程序中手动的使所有缓存失效,还应该设置一个Application变量OutputCacheVersion来标识当前所有缓存的版本。所有这些对缓存的版本控制代码都被放置在在应用程序的 global.asax 文件的代码声明块中,通过重写GetVaryByCustomString()方法以根据不同的条件为响应的Vary字段赋予不同的自定义值来实现。
  下面我们先给出Login.ASPx.cs文件中为输出缓存设置缓存策略的代码:
  Response.Cache.SetExpires(DateTime.MaxValue);
  Response.Cache.SetCacheability(HttpCacheability.Public);
  Response.Cache.SetValidUntilExpires(true);
  Response.Cache.VaryByParams["*"]=true;
  Response.Cache.SetVaryByCustom("login");
  前三行代码设置缓存策略,包括到期时间、可见性策略以及是否应忽略由使缓存无效的客户端发送的 HTTP Cache-Control 标头。第四行代码采用了ASP.NET提供的第一种版本控制方法,指定缓存设备根据用户请求中的查询字符串和表单变量的取值来缓存不同版本的输出。这也告诉我们可以结合使用多种版本控制方法。最后一行代码是我们要重点说明的,由于所有采用自定义字符串来缓存页的多个版本的页面都是通过重写global.asax文件中的GetVaryByCustomString()方法来实现的,所以为了区分不同的版本策略,或者说区分不同的页面,必须为  GetVaryByCustomString()方法提供一个参数custom,这个参数在上述代码段中的取值为”login”。
  接着我们要给出global.asax文件中的GetVaryByCustomString()方法:
 public override string GetVaryByCustomString(HttpContext context, string arg)
 {
  if (arg == "login")
  {
   if (context.Request.Form.Count==0)
   {
    DBHexie database=new DBHexie();
    return Application["OutputCacheVersion"].ToString()
+database.GetSystemParameterValue(237);
   }
   else
    return Guid.NewGuid().ToString();
  }
 }
  在这段代码中,arg变量就是标识当前版本策略的参数,可以根据不同的arg取值制定更多的版本策略。context变量是当前HTTP传输中的上下文信息,对于我们来说主要用到的就是其中的请求信息,即context.Request。方法的返回值实际上被服务器端用来设定输出响应的Vary字段。首先我们判断客户端请求中的表单变量集合是否为空。如果该集合为空,则表明用户进入登录页面,此时的页面版本受到数据库中[系统参数]数据表中第237行的参数信息(由database.GetSystemParameterValue(237)方法返回该参数值,用以决定是否显示帮助窗口)以及整个程序的输出缓存版本的影响(由Application["OutputCacheVersion"]标识)。当程序中需要令所有输出缓存失效时,为Application [“OutputCacheVersion”]赋新值。如果用户请求中的表单变量集合不为空,则说明此次提交的请求是由用户输入账号和密码后要求服务器端验证引起的。在上述代码中我们简单的为由这种请求引发的响应设置一个新的版本(Guid.NewGuid().ToString()),因为我们要保证服务器端对每一个这种请求都进行实际的验证而从不由缓存中取已生成的响应以保证整个Web应用程序的用户授权正确。
  在ASP.NET下我们还可以仅缓存页面的一部分,这是通过缓存用户自定义控件来实现的。这种片断缓存和页面缓存有很多相似之处,为输出缓存提供了更多的灵活性也使得其实用性更强。
2.对应用程序数据的缓存
  这种缓存与上面提到的输出缓存有很大的不同。输出缓存可以存储于从客户端浏览器到服务器整个HTTP流上任何具备HTTP 1.1功能的设备上,而应用程序的数据则只能被缓存在服务器端的内存中。与输出缓存相比,对应用程序数据的缓存需要我们做更多的缓存控制,同时也为我们提供了更多的灵活性。
  在ASP.NET中通过Cache类来实现对应用程序数据的缓存,其使用方法与HttpSessionState、HttpApplicationState和StateBag(Control.ViewState)类似,下面我们举一个具体的例子来说明Cache类的使用。
  在很多情况下我们需要把数据库中的数据以HTML表格的形式显示出来,在ASP.NET下的一般做法是设定DataGrid的DataSource,然后调用DataBind()方法进行数据绑定。如果我们要改变表格呈现数据的方式,或者对表格数据源加以处理,往往需要反复从数据库中取数据,当数据量很大时,这么做会消耗大量服务器资源并且极大的影响应用程序的性能。
  在我们的例子中,使用用户自定义控件DataGridPro封装DataGrid对象,并采用一种不同于DataGrid的分页机制来缓解大量数据在HTML网页上的呈现问题。当用户浏览的表格包含大量数据时,服务器并不是一次显示所有数据给用户,因为这么做会占用极多的网络带宽和CPU时间,而是先显示一部分数据给用户,并把所有数据缓存在服务器端的内存中。当用户拉动表格滚动条请求浏览更多数据时,服务器端将直接从缓存中取得数据并显示更多的内容给用户。在这里我们巧妙地对用户使用了“缓兵之计”,既加快了程序的响应速度,又减小了服务器及网络的负荷,极大的提高了Web应用程序的性能。
  下面我们给出DataGridPro控件的部分代码以具体说明如何实现对表格数据的缓存。在第一次为表格绑定数据时,将表格数据源在缓存中的键值strCacheItemKey保存在ViewState的CacheItemKey_DataSource项中,这么做的原因是Cache的生存期及作用域都是在整个应用程序的范围内,我们必须在表格数据源和某一具体页面之间建立联系。ViewState[“CacheItemKey_DataSource”]正是建立这一联系的桥梁。代码如下:
if (ViewState["CacheItemKey_DataSource"]!=null)
  ViewState.Remove("CacheItemKey_DataSource");
 //生成缓存索引(键)
 string strCacheItemKey=Guid.NewGuid().ToString();
 ViewState["CacheItemKey_DataSource"]=strCacheItemKey;
  然后我们通过如下语句来缓存表格数据源:
   Cache.Insert(strCacheItemKey,dataSource);
  在这里我们使用了插入缓存项的一个最简单的重载版本,实际应用中我们应该根据具体情况为缓存项设定各种缓存策略(包括依赖项、过期时间和优先级策略以及从缓存中移除对象时将调用的委托)。可以看到strCacheItemKey被用来作为dataSource在缓存中的键值。
  当用户下拉滚动条请求显示更多内容时,DataGridPro从缓存中取得数据源再次绑定并重新设置DataGrid显示的记录数。代码如下:
  dataSource=Cache[(string)ViewState["CacheItemKey_DataSource"]];
   dataGrid.DataSource=dataSource;
   dataGrid.DataBind();
  注意在上面的第一行代码中我们有意忽略了dataSource的数据类型,实际上具体实现时可以根据表格数据源的不同类型来声明不同的dataSource。
三、使用缓存的其他技巧
  虽然本文题为“.NET下的缓兵之计”,而.NET推荐的做法是将尽可能多的工作在服务器端完成以减轻客户端负载并保证客户端的平台无关性及安全性,但是在开发Web应用程序的过程中我们仍然会在很多情况下不可避免的撰写自己的客户端脚本。这些脚本是应该写在ASPx文件中,还是在ASPx.cs文件中通过Page类的客户端脚本注册方法输出?前者更加方便,而后者更加灵活。但是更好的办法是将这些脚本写在单独的.js文件中,这样.js文件就能够被缓存在客户端,使用同一个.js文件的其他ASPx页面可以直接从本地的缓存中调用这个客户端脚本文件以提高性能。使用DHTML Behaviors也能够达到同样的效果,.htc文件将被缓存在客户端以供多次调用。
  虽然Windows操作系统本身的文件管理是不区分大小写的,但是Internet Explorer对缓存文件的管理区分大小写。这是因为如果服务器是一个UNIX系统,它将认为拼写相同大小写不同的两个请求是完全不同的而分别给予响应。所以在我们的Web应用程序中应该保证对超链接的大小写一致以充分利用缓存。
四、结束语
  缓急之道,看似相悖,实则相济。缓存页面及应用程序数据虽然会在缓存的生成及控制上为服务器增加一定的开销,但却能够从总体上提高Web应用程序的性能,加快服务器的响应速度,不失为一条缓兵良方。
1月29日

CuteEditor使用手记

1、拷贝文件

(1)将CuteEditor、Bin文件夹下的:

CuteEditor.dll

CuteEditor.lic(解密文件)

CuteEditor.ImageEditor.dll (5.0增加的EditorImage功能)

NetSpell.SpellChecker.dll(拼写检查功能)

拷贝到项目的Bin目录下。

注:(“.dic”为扩展名的文件是词典保存为纯文本文件的格式。将bin文件夹里的都拷到项目的bin目录下也可以)

(2)将CuteSoft_Client文件夹及文件拷贝到项目的相应目录。

注:FilesPath用来设置所对就的目录,如:

FilesPath="~/admin/CuteSoft_Client/CuteEditor/"

(3)把example.css文件拷贝到相应目录,并设置EditorWysiwygModeCss属性。如:EditorWysiwygModeCss="/admin/CuteSoft_Client/CuteEditor/themes/example.css

综合设置如下:

<CE:Editor ID="Editor1" runat="server" FilesPath="~/admin/CuteSoft_Client/CuteEditor/" EditorWysiwygModeCss="/admin/CuteSoft_Client/CuteEditor/themes/example.css">        </CE:Editor>

2、修改Web.config文件

  <appSettings>

     <add key="DictionaryFolder" value="bin" />

  </appSettings>

  <system.web>//注本节代码在.net2.0下是否需要设置,本人未验证。

     <browserCaps>

       tagwriter=System.Web.UI.HtmlTextWriter

     </browserCaps>

  </system.web>

3、引用:

·<%@ Register Assembly="CuteEditor" Namespace="CuteEditor" TagPrefix="CE" %>

·<CE:Editor ID="ce1" runat="server" FilesPath="~/admin/CuteSoft_Client/CuteEditor/" EditorWysiwygModeCss="~/Admin/CuteSoft_Client/CuteEditor/Themes/example.css" ThemeType="Office2003_BlueTheme" >

</CE:Editor>

注:

可修改CuteSoft_Client\CuteEditor\Configuration\AutoConfigure文件夹下的文件,改便CuteEditor工具栏按钮的显示或排列。

可修改文件CuteSoft_Client\CuteEditor\Configuration\Shared\Common.config来添加字体。
12月11日

一个简单的javascript体会

小知识:
 
在玩Ajax的时候免不了要和javascript打交道 。那么有一个事件属性,需要非常明白 。那就是 event.clientX , event.clientY具体代表什么含义 。这里自己简单解释一下他们以及类似的几个属性的含义和区别 。希望能让概念更加明晰 。
 
event.x:设置或者是得到鼠标相对于目标事件的父元素的外边界在x坐标上的位置。
event.clientX:相对于客户区域的x坐标位置,不包括滚动条,就是正文区域。
event.offsetx:设置或者是得到鼠标相对于目标事件的父元素的内边界在x坐标上的位置。
event.screenX:相对于用户屏幕。
 
这里我写了一段测试代码 , 你运行一下既可明了几个概念的差别
 

<table border=1 cellpadding=15 cellspacing=15 style="position:relative;left:100;top:100">
<tr><td>
<div onclick="show()" style="background:silver;cursor:hand">
Click here to show.
</div>
</td></tr>
</table>
<script>
function show(){
alert
("window.event.x:"+window.event.x+"\nwindow.event.y:"+window.event.y+"\nevent.clientX:"+event.clientX+"\nevent.clientY:"+event.clie
ntY+"\nevent.offsetX:"+event.offsetX+"\nevent.offsetY:"+event.offsetY+"\nwindow.event.screenX:"+window.event.screenX+"\nwindow.even
t.screenY:"+window.event.screenY);
}
</script>
11月21日

Asp.net Ajax 客户端和服务器端 复杂通讯(序列化及反序列化)示例 - 2

范例2:使用Web Services将对象序列化成XML并使用客户端XSLTView空间输出信息

  使用了与上例相同的Employee和Company两个类,在这里就不重复了,先来看一下Web Service方法GetXmlSerializedCompany的代码:
GetXmlSerializedCompany方法代码
 1 [WebService(Namespace = "http://tempuri.org/")]
 2 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 3 public class ComplexTypeWS  : System.Web.Services.WebService {
 4 
 5     [WebMethod]
 6     [WebOperation(false, ResponseFormatMode.Xml)]
 7     public Company GetXmlSerializedCompany(Company company)
 8     {
 9         return company;
10     }
11 }

  这个方法简单地令人惊讶,只是直接将参数返回。其精妙之处就是使用了Microsoft.Web.Services.WebOperationAttribute进行标记,表明了该方法将以XML形式输出。

  接下来是HTML,与上例非常的相似,就不多作解释了。代码如下:
HTML代码
 1 <atlas:ScriptManager ID="ScriptManager1" runat="server" />
 2     
 3 <form id="form1" runat="server">
 4     
 5     <div>Employees:</div>
 6     <div id="employees"></div>
 7     <hr />
 8     <div>Add Employee:</div>
 9     <div>Name: <input type="text" id="empName" /></div>
10     <div>Age: <input type="text" id="empAge" /></div>
11     <input type="button" value="Add employee" onclick="addEmployee()" /><br />
12     <hr />
13     <div>Company Name:<input type="text" id="companyName" /></div>
14     <input type="button" value="Serialize!" onclick="serialize()" /><br />
15     <hr />
16     <div id="xmlDisplay"></div>
17 
18 </form>

  然后准备一下Atlas Xml Script,声明一个XmlDataSource,用来获得XSLT文件。再添加一个XSLTView,将其transform属性与XmlDataSource的document属性绑定起来。代码如下:
Atlas Xml Script代码
 1 <script type="text/xml-script">
 2     <page>
 3         <components>
 4             <xmlDataSource id="xsltSource" autoLoad="true" serviceURL="Company.xsl" />
 5             <xsltView id="xmlDisplay">
 6                 <bindings>
 7                     <binding property="transform" dataContext="xsltSource" dataPath="document" />
 8                 </bindings>
 9             </xsltView>
10         </components>
11     </page>
12 </script>

  顺便给出Company.xsl文件代码:
Company.xsl
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 3     <xsl:template match="/Company">
 4         <div>
 5             Company:
 6             <xsl:value-of select="Name" />
 7         </div>
 8         <xsl:for-each select="Employees/Employee">
 9             <div>
10                 <xsl:value-of select="Name" />
11                 <xsl:text></xsl:text>
12                 <xsl:value-of select="Age" />
13                 <xsl:text> years old.</xsl:text>
14             </div>
15         </xsl:for-each>
16     </xsl:template>
17 </xsl:stylesheet>

  然后是Javascript代码,大部分与上例相同,只作了少量注释:
Javascript代码
 1 <script language="javascript">
 2     var empArray = new Array();
 3         
 4     function addEmployee()
 5     {
 6         var emp = new Object();
 7         emp.Name = $('empName').value;
 8         emp.Age = parseInt($("empAge").value, 10);
 9         
10         empArray.push(emp);
11         updateSource();
12     }
13         
14     function updateSource()
15     {
16         var html = "";
17         
18         for (var i = 0; i < empArray.length; i++)
19         {
20             var emp = empArray[i];
21             html += ((i + 1+ "" + emp.Name + "" + emp.Age + " years old.<br />")
22         }
23         
24         $("employees").innerHTML = html;
25     }
26     
27     function serialize()
28     {
29         // 构造一个Company对象作为参数,
30         // 结构和服务器端对象相同。
31         var company = new Object();
32         company.Name = $("companyName").value;
33         company.Employees = empArray;
34         
35         var params = { "company" : company }
36         var method = new Sys.Net.ServiceMethod("ComplexTypeWS.asmx""GetXmlSerializedCompany"null);
37         
38         method.invoke(params, onMethodComplete);
39     }
40         
41     function onMethodComplete(resultXml, response, userContext)
42     {
43         // 这时第一个参数是一个Xml,
44         // 用它来设置XSLTView的document属性。
45         $("xmlDisplay").control.set_document(resultXml);
46         
47         empArray.length = 0;
48         updateSource();
49     }
50 </script>

  代码就是这些,接下来看一下使用。首先依旧是添加数个Employee:


  填写Company Name并点击“Serialize!”按钮,可以看到下方的XSLT输出:


  使用Fiddler查看的Request Body和Response Body的信息:


  正如我们所期望的那样,Response Body里的信息是Company对象被Xml序列化之后的结果,然后使用XSLT转换后即得到了我们需要的信息!



  通过了上面两个例子,我们可以看出Atlas对于Web Services的支持是非常灵活的。具体的使用方式让开发人员有很大的发挥空间,开发人员完全可以选择合适的方式把Atlas灵活运用在自己的项目中。

  当然,Atlas对于Web Services的支持还远不止这些,在以后的文章里我会继续从实现角度对Atlas的Web Services进行分析,并提供更多的范例给大家参考。

Asp.net Ajax 客户端和服务器端 复杂通讯(序列化及反序列化)示例 - 1

范例1:在Web Services方法中使用复杂的数据类型。

  首先,我们定义两个表示数据的类,Employee和Company。代码如下:
Employee与Company代码
 1 [Serializable]
 2 public class Employee : IComparable<Employee>
 3 {
 4     public string Name;
 5     
 6     public int Age;
 7 
 8     #region IComparable<Employee> Members
 9 
10     public int CompareTo(Employee other)
11     {
12         return this.Name.CompareTo(other.Name);
13     }
14 
15     #endregion
16 }
17 
18 [Serializable]
19 public class Company
20 {
21     public string Name;
22 
23     public Employee[] Employees;
24 }

  接着我们定义一个Web Services方法Sort,该方法的作用是拿到公司姓名和一个Employee数组作为参数,将Employee按照姓名排序之后,再组成一个Company对象输出。代码如下:
Sort方法 
 1 [WebService(Namespace = "http://tempuri.org/")]
 2 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 3 public class ComplexTypeWS  : System.Web.Services.WebService {
 4 
 5     [WebMethod]
 6     public Company Sort(string companyName, Employee[] employees)
 7     {
 8         Array.Sort<Employee>(employees);
 9         
10         Company company = new Company();
11         company.Name = companyName;
12         company.Employees = employees;
13         return company;
14     }
15 }

  然后就是HTML了。在页面最上方(id为employees的div)会显示内存中目前所有的Employee,之后是向内存中添加Employee的输入框,接着是填写公司名的文本框和排序按钮,最后则是经过了Web Services排序后的结果显示区域(id为sortedDisplay的div):
HTML代码
 1 <body style="font-family:Verdana; font-size: 14px;">
 2     <form id="form1" runat="server">
 3         <atlas:ScriptManager runat="server" ID="ScriptManager1" />
 4         
 5         <div>Employees:</div>
 6         <div id="employees"></div>
 7         <hr />
 8         <div>Add Employee:</div>
 9         <div>Name: <input type="text" id="empName" /></div>
10         <div>Age: <input type="text" id="empAge" /></div>
11         <input type="button" value="Add employee" onclick="addEmployee()" /><br />
12         <hr />
13         <div>Company Name:<input type="text" id="companyName" /></div>
14         <input type="button" value="Sort!" onclick="sort()" /><br />
15         <hr />
16         <div id="sortedDisplay"></div>        
17     </form>
18 </body>

  最后我们来看Javascript代码:
Javascript代码
 1 <script language="javascript">
 2     // 内存中的Employee数组
 3     var empArray = new Array();
 4     
 5     // 添加一个Employee
 6     function addEmployee()
 7     {
 8         // 建立一个对象表示Employee
 9         var emp = new Object();
10         emp.Name = $('empName').value;
11         emp.Age = parseInt($("empAge").value, 10);
12         
13         // 加入数组
14         empArray.push(emp);
15 
16         // 更新最上方的显示
17         updateSource();
18     }
19     
20     // 将内存中的empArray数组显示在id为employee的div中
21     function updateSource()
22     {
23         var html = "";
24         
25         for (var i = 0; i < empArray.length; i++)
26         {
27             var emp = empArray[i];
28             html += ((i + 1+ "" + emp.Name + "" + emp.Age + " years old.<br />")
29         }
30         
31         $("employees").innerHTML = html;
32     }
33     
34     // 访问Web Service进行排序
35     function sort()
36     {
37         // 构造参数
38         var params = { "companyName" : $("companyName").value, "employees" : empArray };
39         // 构造Web Service方法访问对象
40         var method = new Sys.Net.ServiceMethod("ComplexTypeWS.asmx""Sort"null);
41         
42         // 调用Web Service方法
43         method.invoke(params, onMethodComplete);
44     }
45     
46     // 回调函数
47     function onMethodComplete(company, response, userContext)
48     {
49         // 在id为sortedDisplay的div中显示所有的Employee,
50         // 可以发现company对象和服务器端对象的结构相同
51         var html = "Company Name: " + company.Name;
52         for (var i = 0; i < company.Employees.length; i++)
53         {
54             var emp = company.Employees[i];
55             html += ("<br />" + (i + 1+ "" + emp.Name + "" + emp.Age + " years old.")
56         }
57         
58         $("sortedDisplay").innerHTML = html;
59         
60         // 清空内存中的Employee
61         empArray.length = 0;
62         // 更新最上方的显示
63         updateSource();
64     }
65 </script>

  所有的代码都在这里,我们来看一下使用。首先打开页面,输入数个Employee,如图:


  然后点击填写好Company Name并点击Sort按钮,则可以看出按照姓名排序后的结果:


  我们使用Fiddler查看一下数据传输,可以看到Request Body和Response Body里的JSON代码:


  可以看出,Atlas使用了JSON方式传递数据非常的直观,对于复杂的类型支持也非常好。在客户端得到的对象,其结构和服务器端相同,这对于开发人员带来了不小的便利。