这篇文章简要的介绍了通过使用Atlas UpdatePanel控件进行局部页面渲染的概念。如果你理解了什么是Atlas,你将从这篇文章中受益匪浅。

这篇文章包含了一个很简单的代码示例,教您如何使用UpdatePanel控件来局部渲染页面。使用UpdatePanel的主要优点有:
1、减少了数据页面更新导致的页面刷新次数
2、减少了页面更新传送的数据量(因为在数据回送postback的时候,并不是整个页面都需要完全重新渲染)
3、通过加快UI响应速度提高了用户使用体验。

下面这个例子展示了如何为应用程序增加UpdatePanel控件。
http://atlas.asp.net/docs/Walkthroughs/Samples/datepicker.aspx

从这个示例中我们可以看到,点击页面中心的大的日历整个页面都会刷新,而点击左上方的小日历选择某个日子,页面并不会刷新,而且反应非常迅速。这就是因为这个日历被UpdatePanel包裹了,所以具有了局部页面无刷新渲染的特性。

因此,为一个ASP.NET控件增加AJAX的无刷新效果非常的简单,只要把其放在UpdatePanel内就可以了。

示例源代码如下:

<%@ page language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
protected void ChosenDate_TextChanged(object sender, EventArgs e)
{
DateTime dt = new DateTime();
DateTime.TryParse(ChosenDate.Text, out dt);

CalendarPicker.SelectedDate = dt;
CalendarPicker.VisibleDate = dt;
}

protected void Close_Click(object sender, EventArgs e)
{
SetDateSelectionAndVisible();
}

protected void ShowDatePickerPopOut_Click(object sender, ImageClickEventArgs e)
{
DatePickerPopOut.Visible = !DatePickerPopOut.Visible;

// corrdinates are relative to the control, so we need to get the control's
// position too
int h = (int)ShowDatePickerPopOut.Height.Value;
SetDatePickerPopOutStyle(e.X + h, e.Y);
}

protected void CalendarPicker_SelectionChanged(object sender, EventArgs e)
{
SetDateSelectionAndVisible();
}

private void SetDateSelectionAndVisible()
{
ChosenDate.Text = CalendarPicker.SelectedDate.ToShortDateString();
DatePickerPopOut.Visible = false;
}

private void SetDatePickerPopOutStyle(int x, int y)
{
DatePickerPopOut.CssClass = "PopUpShow";
DatePickerPopOut.Attributes.Add("style", "top:" + x + "px;left:" + y + "px;");
}
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Untitled Page</title>
<style type="text/css">

.PopUpShow
{
background-color:lightblue;
position:absolute;
visibility:show;
width:150px;
height:150px;
z-index:1;
border: solid 2px black;
}

</style>
</head>
<body>
<form id="form1" runat="server">
<atlas:scriptmanager id="ScriptManager" runat="server"
enablepartialrendering="true" />
<h4>With an UpdatePanel</h4>
<!-- This section of the page is wrapped by an UpdatePanel. -->
<atlas:updatepanel id="up" runat="server" mode="Conditional"
rendermode="Inline">
<triggers>
<atlas:controleventtrigger controlid="CalendarPicker"
eventname="SelectionChanged" />
</triggers>
<contenttemplate>
<asp:textbox runat="server" id="ChosenDate"
ontextchanged="ChosenDate_TextChanged" autopostback="true" />
</contenttemplate>
</atlas:updatepanel>
<span style="position: relative;">
<asp:imagebutton runat="server" id="ShowDatePickerPopOut"
onclick="ShowDatePickerPopOut_Click" imageurl="~/images/calendar.gif"
height="20px" width="20px" />
<!-- This section is also wrapped by an UpdatePanel. -->
<atlas:updatepanel runat="server" id="up1" mode="Conditional"
rendermode="inline">
<triggers>
<atlas:controleventtrigger controlid="ShowDatePickerPopOut"
eventname="Click" />
</triggers>
<contenttemplate>
<asp:panel runat="server" id="DatePickerPopOut" visible="false">
<asp:calendar id="CalendarPicker" runat="server" backcolor="White"
bordercolor="White" borderwidth="1px" font-names="Verdana"
font-size="6pt" forecolor="Black" height="139px"
nextprevformat="FullMonth" width="100px"
onselectionchanged="CalendarPicker_SelectionChanged">
<selecteddaystyle backcolor="#333399" forecolor="White" />
<todaydaystyle backcolor="#CCCCCC" />
<othermonthdaystyle forecolor="#999999" />
<nextprevstyle font-bold="True" font-size="6pt"
forecolor="#333333" verticalalign="Bottom" />
<dayheaderstyle font-bold="True" font-size="6pt" />
<titlestyle backcolor="White" bordercolor="Black" borderwidth="4px"
font-bold="True" font-size="8pt" forecolor="#333399" />
</asp:calendar>
<br />
<asp:linkbutton id="CloseDatePickerPopOut" runat="server"
font-names="Tahoma" font-size="small" onclick="Close_Click"
tooltip="Close Pop out">
Close
</asp:linkbutton>
</asp:panel>
</contenttemplate>
</atlas:updatepanel>
</span>
<hr />
<br />
<!-- The remainder of the page does not have an UpdatePanel, so it cannot
participate in the benefits of partial page rendering on postbacks. -->
<div style="text-align: center;">
<h4>Without an UpdatePanel</h4>
<asp:calendar id="Calendar1" runat="server"></asp:calendar>
</div>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet.
</p>
<p>
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie
consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et
accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit
augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt
ut laoreet dolore magna aliquam erat volutpat.
</p>
<p>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et
ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet.
</p>
<p>
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie
consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et
accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit
augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt
ut laoreet dolore magna aliquam erat volutpat.
</p>
</form>
</body>
</html>


最近研究了一下微软的Atlas(MS的AJAX解决方案),打算翻译一些资料,即学技术又练英语 [smile] 。今天先翻译了一篇“Atlas概述”,如果有不对的地方欢迎指正。后续打算翻译几篇实例讲解的文章。

标题:ASP.NET “Atlas” 概述
翻译:吴飞(Felix)
原文:http://atlas.asp.net/docs/Overview/Overview.aspx

介绍

很多网页开发人员一直以来都在努力提高HTML的UI使用体验,包括JavaScript的复杂性以及跨浏览器的兼容性。在很多情况下,开发人员不得不为了兼容不同浏览器通过创建基于服务器端的动态网页而牺牲丰富的用户体验。

在过去的几年里,浏览器的很多新特性促使网页开发人员重新探索是否有可能创建基于客户端应用程序。目前,所有主流浏览器都支持“可编程文档对象模型”(DOM),最新的几个版本中还增加了服务器端和客户端直接通信的能力,而不用再使用“回送”(Postback)。因此,新一代的网页程序应运而生,比如Microsoft Virtual Earth和Microsoft Windows Live,这些网站都提供了像拖拽(drag-and-drop)和实时处理数据的UI特性。

然而,开发这样的网页程序并不简单。你不得不了解浏览器的DOM并用JavaScript编写代码,而且也因不同的浏览器而不同。此外,JavaScript并没有面向对象(object-oriented)和类型安全(type-safe)等.NET所具有的特性。简而言之,创建面向客户端的网页程序需要精通一门新的编程语言和一个新的开发平台。

在这样的情况下,ASP.NET ‘Atlas’横空出世。Atlas是一种整合了客户端脚本库和ASP.NET 2.0服务器端开发平台的新的ASP.NET网页开发技术。这种网页由Atlas提供客户端支持,由ASP.NET提供服务器端支持。由于Atlas是ASP.NET的扩展,因此他完全整合了服务器端的服务。使用了Atlas,你就可以只把网页中的重要部分提供给客户端,而不用处理整个页面,并且可以在后台与服务器进行通讯。由此可见,你可以创建响应快、表现力丰富的UI和服务器通讯。

Atlas与AJAX

在网页开发业界,众所周知AJAX(Asynchronous JavaScript and XML)技术能够创建客户端服务器通讯和丰富的UI。AJAX是一种创建响应快、互动的网页的手段。Atlas可以让你创建AJAX样式的应用程序,而且超越了AJAX,能够为创建应用程序提供一个完整的开发平台。Atlas创建的应用程序可以针对应用需求而平衡客户端和服务器端的开发,并且为客户端和服务器端提供相同级别的编程能力。

Atlas提供了什么?
Atlas的主要目标就是整合客户端脚本和服务器端ASP.NET以提供一个完整的开发平台。

客户端特性
为了客户端开发,Atlas降低了基于JavaScript开发的复杂性,并提供了以下特性:
1、一致的、面向对象的JavaScript API组。可以使用.NET中很熟悉的OOP特性编码客户端程序。
2、浏览器自适用。可以不用再为不同的浏览器编写不同的代码。
3、客户端脚本API和组件支持丰富的UI特性,比如拖拽(drag-and-drop)。几乎使用很少的代码就可以为你的HTML控件加入这些特性。
4、类似ASP.NET服务器控件的客户端声明模型。

服务器端特性
Atlas也整合了客户端脚本与ASP.NET服务端,因此可以控制应用程序任务,无论在哪里都是有意义的。ASP.NET为Atlas应用程序提供以下特性:
1、Web Service可以与Atlas应用程序整合
2、ASP.NET网页服务器控件会自动加入Atlas应用程序所需要的客户端脚本,因此不用再为这些控件编写JavaScript代码。
3、为客户端开发整合了Visual Studio 开发工具,提供了debug、statement completion和其他高效开发的特性。

东软的C#编程规范,与大家分享。
点击下载

感觉挺有趣的,就转过来给大家看了。

紧耦合
  从前,在南方一块奇异的土地上,有个工人名叫彼得,他非常勤奋,对他的老板总是百依百顺。但是他的老板是个吝啬的人,从不信任别人,坚决要求随时知道彼得的工作进度,以防止他偷懒。但是彼得又不想让老板呆在他的办公室里站在背后盯着他,于是就对老板做出承诺:无论何时,只要我的工作取得了一点进展我都会及时让你知道。彼得通过周期性地使用“带类型的引用”(原文为:“typed reference” 也就是delegate??)“回调”他的老板来实现他的承诺,如下:

class Worker {
public void Advise(Boss boss) { _boss = boss; }
public void DoWork() {
Console.WriteLine(“工作: 工作开始”);
if( _boss != null ) _boss.WorkStarted();
Console.WriteLine(“工作: 工作进行中”);
if( _boss != null ) _boss.WorkProgressing();
Console.WriteLine("“工作: 工作完成”");
if( _boss != null ) {
int grade = _boss.WorkCompleted();
Console.WriteLine(“工人的工作得分=” + grade);
}
}
private Boss _boss;
}
class Boss {
public void WorkStarted() { /* 老板不关心。 */ }
public void WorkProgressing() { /*老板不关心。 */ }
public int WorkCompleted() {
Console.WriteLine(“时间差不多!”);
return 2; /* 总分为10 */
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.Advise(boss);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成”);
Console.ReadLine();
}
}

接口
  现在,彼得成了一个特殊的人,他不但能容忍吝啬的老板,而且和他周围的宇宙也有了密切的联系,以至于他认为宇宙对他的工作进度也感兴趣。不幸的是,他必须也给宇宙添加一个特殊的回调函数Advise来实现同时向他老板和宇宙报告工作进度。彼得想要把潜在的通知的列表和这些通知的实现方法分离开来,于是他决定把方法分离为一个接口:

interface IWorkerEvents {
void WorkStarted();
void WorkProgressing();
int WorkCompleted();
}
class Worker {
public void Advise(IWorkerEvents events) { _events = events; }
public void DoWork() {
Console.WriteLine(“工作: 工作开始”);
if( _events != null ) _events.WorkStarted();
Console.WriteLine(“工作: 工作进行中”);
if(_events != null ) _events.WorkProgressing();
Console.WriteLine("“工作: 工作完成”");
if(_events != null ) {
int grade = _events.WorkCompleted();
Console.WriteLine(“工人的工作得分=” + grade);
}
}
private IWorkerEvents _events;
}
class Boss : IWorkerEvents {
public void WorkStarted() { /* 老板不关心。 */ }
public void WorkProgressing() { /* 老板不关心。 */ }
public int WorkCompleted() {
Console.WriteLine(“时间差不多!”);
return 3; /* 总分为10 */
}
}

委托
  不幸的是,每当彼得忙于通过接口的实现和老板交流时,就没有机会及时通知宇宙了。至少他应该忽略身在远方的老板的引用,好让其他实现了IWorkerEvents的对象得到他的工作报告。(”At least he'd abstracted the reference of his boss far away from him so that others who implemented the IWorkerEvents interface could be notified of his work progress” 原话如此,不理解到底是什么意思 )
  他的老板还是抱怨得很厉害。“彼得!”他老板吼道,“你为什么在工作一开始和工作进行中都来烦我?!我不关心这些事件。你不但强迫我实现了这些方法,而且还在浪费我宝贵的工作时间来处理你的事件,特别是当我外出的时候更是如此!你能不能不再来烦我?”
  于是,彼得意识到接口虽然在很多情况都很有用,但是当用作事件时,“粒度”不够好。他希望能够仅在别人想要时才通知他们,于是他决定把接口的方法分离为单独的委托,每个委托都像一个小的接口方法:

delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();
class Worker {
public void DoWork() {
Console.WriteLine(“工作: 工作开始”);
if( started != null ) started();
Console.WriteLine(“工作: 工作进行中”);
if( progressing != null ) progressing();
Console.WriteLine("“工作: 工作完成”");
if( completed != null ) {
int grade = completed();
Console.WriteLine(“工人的工作得分=” + grade);
}
}
public WorkStarted started;
public WorkProgressing progressing;
public WorkCompleted completed;
}
class Boss {
public int WorkCompleted() {
Console.WriteLine("Betterhttp://www.cnblogs.com/Images/dot.gif");
return 4; /* 总分为10 */
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成”);
Console.ReadLine();
}
}

静态监听者
  这样,彼得不会再拿他老板不想要的事件来烦他老板了,但是他还没有把宇宙放到他的监听者列表中。因为宇宙是个包涵一切的实体,看来不适合使用实例方法的委托(想像一下,实例化一个“宇宙”要花费多少资源…..),于是彼得就需要能够对静态委托进行挂钩,委托对这一点支持得很好:

class Universe {
static void WorkerStartedWork() {
Console.WriteLine("Universe notices worker starting work");
}
static int WorkerCompletedWork() {
Console.WriteLine("Universe pleased with worker's work");
return 7;
}
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.started = new WorkStarted(Universe.WorkerStartedWork);
peter.completed = new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成”);
Console.ReadLine();
}
}

事件
  不幸的是,宇宙太忙了,也不习惯时刻关注它里面的个体,它可以用自己的委托替换了彼得老板的委托。这是把彼得的Worker类的的委托字段做成public的一个无意识的副作用。同样,如果彼得的老板不耐烦了,也可以决定自己来激发彼得的委托(真是一个粗鲁的老板):
// Peter's boss taking matters into his own hands
if( peter.completed != null ) peter.completed();
  彼得不想让这些事发生,他意识到需要给每个委托提供“注册”和“反注册”功能,这样监听者就可以自
己添加和移除委托,但同时又不能清空整个列表也不能随意激发彼得的事件了。彼得并没有来自己实现这些功能,相反,他使用了event关键字让C#编译器为他构建这些方法:

class Worker {
//http://www.cnblogs.com/Images/dot.gif
public event WorkStarted started;
public event WorkProgressing progressing;
public event WorkCompleted completed;
}

  彼得知道event关键字在委托的外边包装了一个property,仅让C#客户通过+= 和 -=操作符来添加和移除,强迫他的老板和宇宙正确地使用事件。

static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed += new WorkCompleted(boss.WorkCompleted);
peter.started += new WorkStarted(Universe.WorkerStartedWork);
peter.completed += new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine(“Main: 工人工作完成”);
Console.ReadLine();
}

“收获”所有结果
  到这时,彼得终于可以送一口气了,他成功地满足了所有监听者的需求,同时避免了与特定实现的紧耦合。但是他注意到他的老板和宇宙都为它的工作打了分,但是他仅仅接收了一个分数。面对多个监听者,他想要“收获”所有的结果,于是他深入到代理里面,轮询监听者列表,手工一个个调用:

public void DoWork() {
http://www.cnblogs.com/Images/dot.gif
Console.WriteLine("“工作: 工作完成”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
int grade = wc();
Console.WriteLine(“工人的工作得分=” + grade);
}
}
}

异步通知:激发 & 忘掉
  同时,他的老板和宇宙还要忙于处理其他事情,也就是说他们给彼得打分所花费的事件变得非常长:

class Boss {
public int WorkCompleted() {
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Betterhttp://www.cnblogs.com/Images/dot.gif"); return 6; /* 总分为10 */
}
}
class Universe {
static int WorkerCompletedWork() {
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Universe is pleased with worker's work");
return 7;
}
http://www.cnblogs.com/Images/dot.gif
}

  很不幸,彼得每次通知一个监听者后必须等待它给自己打分,现在这些通知花费了他太多的工作事件。于是他决定忘掉分数,仅仅异步激发事件:

public void DoWork() {
http://www.cnblogs.com/Images/dot.gif
Console.WriteLine("“工作: 工作完成”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() )
{
wc.BeginInvoke(null, null);
}
}
}

异步通知:轮询
  这使得彼得可以通知他的监听者,然后立即返回工作,让进程的线程池来调用这些代理。随着时间的过去,彼得发现他丢失了他工作的反馈,他知道听取别人的赞扬和努力工作一样重要,于是他异步激发事件,但是周期性地轮询,取得可用的分数。

public void DoWork() {
http://www.cnblogs.com/Images/dot.gif
Console.WriteLine("“工作: 工作完成”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
IAsyncResult res = wc.BeginInvoke(null, null);
while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);
int grade = wc.EndInvoke(res);
Console.WriteLine(“工人的工作得分=” + grade);
}
}
}

异步通知:委托
  不幸地,彼得有回到了一开始就想避免的情况中来,比如,老板站在背后盯着他工作。于是,他决定使用自己的委托作为他调用的异步委托完成的通知,让他自己立即回到工作,但是仍可以在别人给他的工作打分后得到通知:

 public void DoWork() {
http://www.cnblogs.com/Images/dot.gif
Console.WriteLine("“工作: 工作完成”");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);
}
}
}
private void WorkGraded(IAsyncResult res) {
WorkCompleted wc = (WorkCompleted)res.AsyncState;
int grade = wc.EndInvoke(res);
Console.WriteLine(“工人的工作得分=” + grade);
}

宇宙中的幸福
  彼得、他的老板和宇宙最终都满足了。彼得的老板和宇宙可以收到他们感兴趣的事件通知,减少了实现的负担和非必需的往返“差旅费”。彼得可以通知他们,而不管他们要花多长时间来从目的方法中返回,同时又可以异步地得到他的结果。彼得知道,这并不*十分*简单,因为当他异步激发事件时,方法要在另外一个线程中执行,彼得的目的方法完成的通知也是一样的道理。但是,迈克和彼得是好朋友,他很熟悉线程的事情,可以在这个领域提供指导。
  他们永远幸福地生活下去……

Yahoo! UI Library
http://developer.yahoo.net/yui/index.html
The Yahoo! User Interface Library is a set of utilities and controls, written in JavaScript, for building richly interactive web applications using techniques such as DOM scripting, HTML and AJAX. The UI Library Utilities facilitate the implementation of rich client-side features by enhancing and normalizing the developer's interface to important elements of the browser infrastructure (such as events, in-page HTTP requests and the DOM). The Yahoo UI Library Controls produce visual, interactive user interface elements on the page with just a few lines of code and an included CSS file. All the components in the Yahoo! User Interface Library have been released as open source under a BSD license and are free for all uses.

script.aculo.us
http://script.aculo.us/
The Web is changing. The 30-year-old terminal-like technology it was originally is gradually giving way to new ways of doing things. The power of AJAX allows for rich user interaction without the trouble that has bugged traditional web applications. Building upon the wonderful Prototype JavaScript library, script.aculo.us provides you with some great additional ingredients to mix in.

Prototype JavaScript Framework
http://prototype.conio.net/
Prototype is a JavaScript framework that aims to ease development of dynamic web applications. Featuring a unique, easy-to-use toolkit for class-driven development and the nicest Ajax library around, Prototype is quickly becoming the codebase of choice for web application developers everywhere.

http://openrico.org/
http://www.rubyonrails.com/
http://www.mochikit.com/

  在sql server 2000中,要实现显示某一页,就返回那一页数据的效果的方法实在不尽人意.网上很多通用的分页存储过程,但看着就头大.如果使用我前面提到的使用in,not in,top来进行返回特定页,特殊的限制又会比较多(比如ID要递增).现在Sql Server 2005中提供了一个函数ROW_NUMBER(),可以使自定义分页变得简单许多.
我们先来看看ROW_NUMBER()是干什么的.执行下面这段SQL语句:
SELECT [ReportID],[UserName], [ReportID],
[TimeStart], [TimeEnd],ROW_NUMBER() OVER (ORDER BY ReportID) AS RowNo
FROM [ExecutionLog]
执行结果如下图所示:
http://www.felixwoo.com/wp-content/uploads/attachments/200601/21_221728_.jpg
很简单,ROW_NUMBER() 就是生成一个顺序的行号,而他生成顺序的标准,就是后面紧跟的OVER(ORDER BY ReportID).现在,你看到了自定义分页的影子了吗?:)下面,我们看看怎么具体应用这个RowNo进行分页.
现在,假设我每一页的数据是10条,我们就可以使用如下所示的SQL语句返回指定页的数据:
@"
SELECT TOP 10 *
FROM
(
SELECT top 10 [InstanceName], [UserName], [ReportID],
[TimeStart], [TimeEnd],ROW_NUMBER() OVER (ORDER BY ReportID) AS RowNo
FROM [ExecutionLog]
) AS A
WHERE RowNo > " + pageIndex*10
pageIndex就是我们需要数据的页数.很简单,不是吗?并且,这种方式几乎没有什么限制,因为他相当于对于任何检索,都生成了一个新的排序列.我们就可以使用该列进行自定义分页.

Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads
http://msdn.microsoft.com/library/default.asp?url=/msdnmag/issues/03/02/multithreading/toc.asp

使用 Enterprise Library (2006年1月版) 只改设置文件切换数据库
http://blog.joycode.com/ghj/archive/2006/02/09/71245.aspx

(翻译)怎么在ASP.NET 2.0中使用Membership
http://lovecherry.cnblogs.com/archive/2005/12/05/291092.html

引言:
  微软的新的.NET平台为开发者带来了许多新的诸如GDI+、Globalization之类的编程机制,同时还发明了一门全新的类似Java的编程语言-C#。对于这些新知识,我们应尽快了解、掌握并试图运用到实践项目中去,而通过实例学习的方法无疑是一个非常有效的途径。本文就通过一个简单的实例,向大家展示了在Visual C#中如何运用GDI+和Unsafe代码类等技术以实现简单的数字图像处理。

  一.概述:
  本文的实例是一个数字图像处理的应用程序,它完成的功能包括对图像颜色的翻转、对图像进行灰度处理和对图像进行增亮处理。该程序对图像进行处理部分的代码包含在一个专门的Filters类里面,通过调用该类里的静态成员函数,我们就可以实现相应的图像处理功能了。为实现图像处理,我们要对图像进行逐个象素处理。我们知道图像是由一个个的象素点组成的,对一幅图像的每个象素进行了相应的处理,最后整个图像也就处理好了。在这个过程中,我们只需对每个象素点进行相应的处理,在处理过程中却不需要考虑周围象素点对其的影响,所以相对来说程序的实现就变得简单多了。
  由于GDI+中的BitmapData类不提供对图像内部数据的直接访问的方法,我们唯一的办法就是使用指针来获得图像的内部数据,这时我们就得运用unsafe这个关键字来指明函数中访问图像内部数据的代码块了。在程序中,我还运用了打开文件和保存文件等选项,以使我们的辛勤劳动不付之东流。
  二.程序的实现:
  1.打开Visual Studio.net,新建一个Visual C#的项目,在模板中选择"Windows 应用程序"即可,项目名称可自定(这里为ImageProcessor)。
  2.为使窗体能显示图像,我们需要重载窗体的OnPaint()事件函数,在该函数中我们将一个图像绘制在程序的主窗体上,为了使窗体能显示不同尺寸大小的图像,我们还将窗体的AutoScroll属性设置为true。这样,根据图像的尺寸,窗体两边就会出现相应的滚动条。该函数的实现如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(m_Bitmap, new Rectangle(this.AutoScrollPosition.X, this.AutoScrollPosition.Y,
(int)(m_Bitmap.Width), (int)(m_Bitmap.Height)));
}
3.给主窗体添加一个主菜单,该主菜单完成了一些基本的操作,包括"打开文件"、"保存文件"、"退出"、"翻转操作"、"灰度操作"、"增亮操作"等。前面三个操作完成图像文件的打开和保存以及程序的退出功能,相应的事件处理函数如下:
private void menuItemOpen_Click(object sender, System.EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Bitmap文件(*.bmp)|*.bmp|
Jpeg文件(*.jpg)|*.jpg|
所有合适文件(*.bmp/*.jpg)|*.bmp/*.jpg";
openFileDialog.FilterIndex = 2 ;
openFileDialog.RestoreDirectory = true ;
if(DialogResult.OK == openFileDialog.ShowDialog())
{
m_Bitmap = (Bitmap)Bitmap.FromFile(openFileDialog.FileName, false);
this.AutoScroll = true;
this.AutoScrollMinSize=new Size ((int)(m_Bitmap.Width),(int)
m_Bitmap.Height));
this.Invalidate();
}
}
  其中,m_Bitmap为主窗体类的一个数据成员,声明为private System.Drawing.Bitmap m_Bitmap;(注:因为程序中用到了相关的类,所以在程序文件的开始处应添加using System.Drawing.Imaging;)同时,在该类的构造函数中,我们必须先给它new一个Bitmap对象:m_Bitmap = new Bitmap(2,2);上述代码中的this.Invalidate();完成主窗体的重绘工作,它调用了主窗体的OnPaint()函数,结果就将打开的图像文件显示在主窗体上。
private void menuItemSave_Click(object sender, System.EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Bitmap文件(*.bmp)|*.bmp|
Jpeg文件(*.jpg)|*.jpg|
所有合适文件(*.bmp/*.jpg)|*.bmp/*.jpg";
saveFileDialog.FilterIndex = 1 ;
saveFileDialog.RestoreDirectory = true ;
if(DialogResult.OK == saveFileDialog.ShowDialog())
{
m_Bitmap.Save(saveFileDialog.FileName);
}
}
  其中m_Bitmap.Save(saveFileDialog.FileName);一句完成了图像文件的保存,正是运用了GDI+的强大功能,我们只需这么一条简单的语句就完成了以前很大工作量的任务,所以合理运用.NET中的新机制一定会大大简化我们的工作的。
private void menuItemExit_Click(object sender, System.EventArgs e)
{
this.Close();
}
  接下来,三个主要操作的事件处理函数如下:
private void menuItemInvert_Click(object sender, System.EventArgs e)
{
if(Filters.Invert(m_Bitmap))
this.Invalidate();
}
private void menuItemGray_Click(object sender, System.EventArgs e)
{
if(Filters.Gray(m_Bitmap))
this.Invalidate();
}
private void menuItemBright_Click(object sender, System.EventArgs e)
{
Parameter dlg = new Parameter();
dlg.nValue = 0;
if (DialogResult.OK == dlg.ShowDialog())
{
if(Filters.Brightness(m_Bitmap, dlg.nValue))
this.Invalidate();
}
}
  三个函数中分别调用了相应的图像处理函数Invert()、Gray()、Brightness()等三个函数。这三个函数Filters类中的三个类型为public的静态函数(含有static关键字),它们的返回值类型均是bool型的,根据返回值我们可以决定是否进行主窗体的重绘工作。
  Invert()、Gray()、Brightness()等三个函数均包含在Filters类里面,Invert()函数的算法如下:
public static bool Invert(Bitmap b)
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
int nWidth = b.Width * 3;
for(int y=0;y
  该函数以及后面的函数的参数都是Bitmap类型的,它们传值的对象就是程序中所打开的图像文件了。该函数中的BitmapData类型的bmData包含了图像文件的内部信息,bmData的Stride属性指明了一条线的宽度,而它的Scan0属性则是指向图像内部信息的指针。本函数完成的功能是图像颜色的翻转,实现的方法即用255减去图像中的每个象素点的值,并将所得值设置为原象素点处的值,对每个象素点进行如此的操作,只到整幅图像都处理完毕。函数中的unsafe代码块是整个函数的主体部分,首先我们取得图像内部数据的指针,然后设置好偏移量,同时设置nWidth为b.Width*3,因为每个象素点包含了三种颜色成分,对每个象素点进行处理时便要进行三次处理。接下来运用两个嵌套的for循环完成对每个象素点的处理,处理的核心便是一句:p[0] = (byte)(255-p[0]);。在unsafe代码块后,便可运用b.UnlockBits(bmData)进行图像资源的释放。函数执行成功,最后返回true值。注:由于是要编译不安全代码,所以得将项目属性页中的"允许不安全代码块"属性设置为true,图示如下:
http://www.yesky.com/image20010518/53286.jpg
该函数实现的程序效果如下:
http://www.yesky.com/image20010518/53287.jpg
http://www.yesky.com/image20010518/53288.jpg
 Gray()函数的算法如下:
public static bool Gray(Bitmap b)
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
byte red, green, blue;
for(int y=0;y
  本函数完成的功能是对图像进行灰度处理,我们的基本想法可是将每个象素点的三种颜色成分的值取平均值。然而由于人眼的敏感性,这样完全取平均值的做法的效果并不好,所以在程序中我取了三个效果最好的参数:.299,.587,.114。不过在这里要向读者指明的是,在GDI+中图像存储的格式是BGR而非RGB,即其顺序为:Blue、Green、Red。所以在for循环内部一定要设置好red、green、blue等变量的值,切不可颠倒。函数执行成功后,同样返回true值。
  该函数实现的程序效果如下:
http://www.yesky.com/image20010518/53289.jpg
http://www.yesky.com/image20010518/53293.jpg
 Brightness()函数的算法如下:
public static bool Brightness(Bitmap b, int nBrightness)
{
if (nBrightness 255)
return false;
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width,
b.Height), ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
int nVal = 0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
int nWidth = b.Width * 3;
for(int y=0;y 255) nVal = 255;
p[0] = (byte)nVal;
++p;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
  本函数完成的功能是对图像进行增亮处理,它比上面两个函数多了一个增亮参数-nBrightness,该参数由用户输入,范围为-255~255。在取得了增亮参数后,函数的unsafe代码部分对每个象素点的不同颜色成分进行逐个处理,即在原来值的基础上加上一个增亮参数以获得新的值。同时代码中还有一个防止成分值越界的操作,因为RGB成分值的范围为0~255,一旦超过了这个范围就要重新设置。函数最后执行成功后,同样得返回true值。
  该函数实现的程序效果如下:
http://www.yesky.com/image20010518/53295.jpg
三.小结:
  本文通过一个简单的实例向大家展现了用Visual C#以及GDI+完成数字图像处理的基本方法,通过实例,我们不难发现合理运用新技术不仅可以大大简化我们的编程工作,还可以提高编程的效率。不过我们在运用新技术的同时也得明白掌握基本的编程思想才是最主要的,不同的语言、不同的机制只是实现的具体方式不同而已,其内在的思想还是相通的。对于上面的例子,掌握了编写图像处理函数的算法,用其他的方式实现也应该是可行的。同时,在上面的基础上,读者不妨试着举一反三,编写出更多的图像处理的函数来,以充实并完善这个简单的实例。

  Ajax的风行说明用户对于丰富的Web体验的需求日益增长。然而,开发和调试Ajax风格的Web应用程序是一项非常艰难的工作。要编写一个丰富的Web UI,开发者需要详细地掌握DHTML和JavaScript,并且还要掌握各种浏览器之间在设计细节上的不同。然而没有哪些工具能够简化这些应用程序的设计和开发。最后,调试和测试这些应用程序会变得异常困难。
  微软致力于简化Ajax风格Web应用的开发,并提供丰富的、可交互的和个性化的用户体验。开发者可以对客户端脚本不甚了解;但他们可以无缝地将浏览器UI与他们编写的其他应用无缝地集成在一起,并且他们可以很容易地开发和调试这些应用程序。
  出于这一目的,微软启动了一个新的项目,研发代号为“Atlas”。同时,微软还启动了一个站点来持续更新其核心进展和示例,并围绕它建立一个活跃的社区。在9月份的PDC大会上,开发者可以看到Atlas的技术预览版。
  以下是Atlas所拥有的特性。

  Atlas客户端脚本框架
  Atlas客户端脚本框架是可扩展的、100%面向对象的JavaScript客户端脚本框架,允许开发者很容易地构建拥有丰富的UI功能并且可以连接Web Services的Ajax风格浏览器应用程序。使用Atlas,开发者可以使用DHTML、JavaScript和XMLHTTP来编写Web应用程序,而无须掌握这些技术的细节。
  Atlas客户端脚本框架可以在所有的现代浏览器上运行,不论使用的是什么Web服务器。它还完全不需要安装,只要在页面中引用正确的脚本文件即可。
  Atlas客户端脚本框架包含下列组件:
  l 一个可扩展的核心框架,其中为JavaScript添加了很多新特性,如生存期管理、继承、多播事件处理器和接口
  l 一个基础类库,提供了通用特性,如丰富的字符串操作功能、计时器和运行任务等
  l 一个UI框架,可以跨浏览器实现HTML的动态行为
  l 一个网络栈,用于简化对服务器的连接和对Web Services的访问
  l 一组具有丰富UI功能的控件,如自动完成文本框、弹出面板、动画控件和拖放
  l 一个浏览器兼容的层(Layer),用于在不同浏览器中定位不同的脚本行为

  Atlas的ASP.NET服务器控件
  对于ASP.NET应用程序,微软专门设计了一组Ajax风格的服务器控件,并且加强了现有的ASP.NET页面框架和控件,以便支持Atlas客户端脚本框架。
  ASP.NET 2.0中有一项称作异步客户端回调(Asynchronous Client Callbacks)的新特性,使得构建从服务器上更新内容时不会引发页面中断的ASP.NET页面变得很容易。异步客户端回调包装了XMLHTTP,能够在很多浏览器上工作。ASP.NET本身就包括了很多使用回调的控件,包括具有客户端分页和排序功能的GridView和DetalsView控件,以及TreeView控件的虚拟列表支持。
  Atlas客户端脚本框架将完全支持ASP.NET 2.0回调,但微软希望在今后进一步增强浏览器和服务器之间的集成性。例如,你可以将Atlas客户端控件的数据绑定并指定为服务器上的ASP.NET数据源控件,并且可以从客户端异步地控制Web页面的个性化特征。

  ASP.NET Web Services集成
  和任何客户端应用程序一样,一个Ajax风格的Web应用程序通常也需要访问Web服务器的一些功能。Atlas应用程序连接服务器的模型和其他平台类似,都是使用Web Services来实现。
  通过ASP.NET Web Services集成,Atlas应用程序将可以在任何支持XMLHTTP的浏览器上通过Atlas客户端用本框架来直接访问任何宿主了ASP.NET的asmx或Indigo服务。该框架将会自动处理、代理和脚本到对象、对象到脚本的序列化问题。通过使用Web Services集成,开发者可以使用单一的编程模型来编写Web Services,并且在任何应用程序中使用它们,不论是基于浏览器的站点上还是智能客户端应用程序中。

  Atlas的ASP.NET构建块
  在ASP.NET 2.0中,微软构建了一组丰富的构建块服务(Building Block Services),这使得构建强大、个性化的Web应用程序变得不可思议的简单。这些构建块极大地降低了在开发通用的Web应用程序过程中需要编写的代码数量,比如管理用户、通过角色验证用户和存储用户的个性化设置信息等。
  使用Atlas,我们可以在任何浏览器上的任何客户端应用程序中向访问Web Services那样访问这些功能。例如,如果你正在开发一个站点来显示用户的TO-DO项目,你可以使用ASP.NET的Profile服务来将他们存放在服务器上的用户自定义配置文件中。这样,即使用户从一台机器转移到另一台机器上,也同样可以访问这些项目。
  微软将提供的服务包括(全部是基于ASP.NET 2.0的):
  l Profile:在服务器上存放每个用户特有的数据
  l UI个性化:在服务器上存放个性化的UI设置信息
  l 验证:验证用户
  l 角色:基于用户的角色验证用户任务和提供不同的UI
  由于这些构建块是基于服务器的,开发者需要对他们应用和其他站点一样的安全模型。这些服务不需要客户端下载任何东西——只要在浏览器中引用脚本代理即可。
  所有的ASP.NET 2.0构建块服务都是可插拔的,这使用一种通用的提供者(Provider)模型可扩展模式在后台实现。微软提供的内建提供程序允许开发使用SQL Server数据库或Active Directory作为存储容器,开发者也可以很容易地插接自己的提供程序。例如,你可能希望使用集群而不是数据库服务器来存放用户的配置文件,这时,你只需将你的提供程序插接进来即可——这一切都由开发者来决定。

  客户端构建块服务
  除了DHTML、JScript和XMLHTTP,微软还提供了一组附加的服务来加强客户端的功能,并提供增强的体验。
  对于这样的服务,本地浏览器缓存就是一个很好的例子。当启用了本地浏览器缓存时,Web站点就可以将内容存储到缓存中,并在需要的时候很快地取出。但浏览器并未提供向缓存中存放数据的API,而且象Google Map或OWA这样的应用程序不得不通过很多工作产生一个唯一的URL,才能使浏览器缓存它。在Atlas中,微软提供了可编程的本地存储/缓存,因此应用程序可以很方便、有效并且安全地在本地缓存数据。
  同其它应用程序的集成是检验Web体验是否丰富的另一个新的标准。例如,当一个用户浏览一个拍卖网站并对一件商品出价时,他可能想随时知道这个拍卖什么时候结束,但他如何才能将这个事件添加到个人的日历程序中?Atlas带来了一系列客户端构建块服务,当用户选择“添加到日历”时,浏览器将调用接驳点来获取日历数据,并将其传递到本地的日历程序中。此时页面上无须下载或运行任何特殊的代码或执行任何初始化动作,因此,这比ActiveX要安全得多。

  下一步
  随着我们的技术进展,还会发生很多事情。我们现在已经开始研发、开发和调试工具的下一个版本了,因此,我们将很快为这些类型的丰富Web应用程序提供更加伟大的开发体验。
  您可能会问的一个问题是:Atlas如何在Avalon和智能客户端上使用?
  我们可以看到,Atlas是编写丰富的、可交互的和个性
化的Web浏览器应用程序的最好方式,而Avalon是微软的下一代表现层模型,可以在Windows平台上提供最丰富的用户体验。Avalon将使用最新的媒体集成功能和硬件加速设备,提供卓越的视觉体验。Avalon将带来超越浏览器的体验。
  当然,当你构建Avalon应用程序的时候,你依然可以重用ASP.NET和Atlas中的编程模型。例如,Avalon客户端上依然可以使用ASP.NET构建块服务和客户端构建块服务。这种模型可以使你平滑地过渡到下一代应用程序。

  众所周知,异步交互、JavaScript脚本和XML封装数据是AJAX的三大特征。其实,在实际应用中,不需要牢牢套死这三条大律,在我看来,AJAX - X,即去掉用XML封装数据,也不失为一种好的设计思路,如果应用恰当,更显轻盈步伐和巧妙思路。

  一般读取AJAX返回的XML结构的数据时使用XMLHttp的responseXML对象属性,同时,XMLHttp也提供了另外一个属性,即ResponseText,通过这个属性,XMLHttp可以接受来自服务器的文本结构的字符串信息。去掉XML的AJAX可以使用ResponseText这个对象属性,很灵活的操控返回数据的格式,可以自定义格式,比如我通常喜欢用c语言的那种文件流方式定义返回的字符串结构,有文件头和具体的文件信息实体,文件头分为状态信息以及文件字符长度,我摒弃了文件字符长度的定义,规定死接受的ResponseTex字符串中的第一位为状态码,比如设定常量值0表示一起正常,非0的数字表示不正常,甚至有错误等。如果有非0值,程序自动取第二位起到257位(长度为256)的字符串组成为状态信息,从258位开始到末尾的字符串就是服务器返回的正常结果信息。
substring(0,1)取状态码
substring(1,256)取服务器错误信息(错误信息不够256位用空格补齐,取到数据后进行Trim处理)
substring(256,末尾)取服务器返回的数据信息
三次substring即完成了一个简单但完整的交互工作。比起XML解析组件来说要快的多。

  用ResponseText比封装为XML处理数据快和简单是一个原因,另一个原因是可操控性更大更灵活,打开Google Suggest,在搜索框输入字符可以给你给出拼写提示,Suggest就是应用了AJAX技术,不过它在从服务器返回数据时并没有使用XML封装,也没有自定义ResponseText格式,而是直接将返回代码组织成js脚本,通过浏览器返回后直接执行,如eval(XMLHttp.ResponseText)这样的方式进行执行,http://www.google.com/complete/search?hl=en&js=true&qu=ajax 通过这个链接你可以看到Suggest利用AJAX得到的返回数据,此页面是在Google Suggest的搜索框中输入"AJAX"后得系统动态返回的数据。
sendRPCDone(frameElement, "ajax", new Array("ajax", "ajax amsterdam", "ajax fc", "ajax ontario", "ajax grips", "ajax football club", "ajax public library", "ajax football", "ajax soccer", "ajax pickering transit"), new Array("3,840,000 results", "502,000 results", "710,000 results", "275,000 results", "8,860 results", "573,000 results", "40,500 results", "454,000 results", "437,000 results", "10,700 results"), new Array("")); 浏览器段拿到这段代码后直接eval就可以了,至于sendRPCDone这个函数,那当然得实现定义后并装载到页面中啦。XMLHttp这个名字以XML开头,让很多人禁锢了思想和创意,完全抛弃X,你也可以做出纯AJAX的实例来。

  当然,对于大型系统来讲,为了保持数据接口的一致和整齐,还是用XML来传递更严谨更统一点,听说微软已经发起了重写XML Parse组件的号召,估计下一个版本的XMLHttp还是DOMParser还是MSXML2.DOMDocument都会大大提高效率,减少资源占用的。

一、AJAX最值得称赞的是异步交互,而不是无刷新
  很多人都看好AJAX无刷新的技术,以至于认同AJAX就是用来做无刷新的。这个认识是错误的,什么是无刷新?无刷新就是页面无需重载,那什么又是异步交互?异步交互就是一个简单的多线程,当你在一个blog里看文章时,同时也可以利用AJAX进行无刷新的回复提交,看起来虽然也是无刷新,但这里最重要的是异步,即你能一边看文章,一边又能向服务器提交你的回复信息,利用好这个异步,才能算是掌握了AJAX的精髓。很多场合,无刷新是呈现给用户的视觉体验,而异步交互却是默默无闻的工作在台后,这种情况导致大多数人的错误理解了AJAX的权重之分。
二、推荐在WEB上轻量级的应用AJAX

http://photos22.flickr.com/33335029_bb2b7d5a18_o.png
http://photos22.flickr.com/33335000_e87bb720cc_o.png
http://photos23.flickr.com/33335001_99b5084c07_o.png
  著名的图片存储网站Flickr利用AJAX可谓出神入化。我之所以这么说,是因为我认为Flickr深知AJAX的利与弊,并且牢牢抓住自己的网站的功能特点,并没有因AJAX而AJAX,而是架驱于技术至上,让AJAX融于网站之中,为网站提供了更好的功能服务。如Flickr中无论是在多图列表页面还是单图详细页面,修改图片的标题和描述都应用了AJAX技术,让用户无需跳转到单独的编辑页面中,编辑后单击保存,亦使用了异步交互的方式进行数据提交,这时,页面上显示一个Loading字符外,其他部分不受任何影响,可谓太贴心的服务。

http://photos23.flickr.com/33335047_3deafb867f_o.png
  再如基于Tag的专业Blog搜索服务商Technorati也使用了AJAX,在搜索某个Tag时,页面主导部分会即刻显示所有Technorati数据库中查询到的数据条目,在左边的侧边栏上会显示两个Loading图标,过一会儿,这两个Loading就会显示具体的内容了,显示的是此Tag相关的Flickr的图片和书签服务网站(Furl&del.icio.us)的链接,因为这两部分内容是取自其他网站,如果由服务器统一先取得数据在一同显示到页面时,会受到网速影响而变慢,通过AJAX的异步交互方式首先立即显示本地数据,然后由客户端去和Flickr、Furl、del.icio.us打交道分别取得它们的数据,即节约了流量带宽又不影响用户访问速度,可谓高明。

  通过以上两个国外成功应用AJAX的网站,我们发现他们都使用的是轻量级的AJAX,就是那种交互简单,数据较少的操作。这也符合AJAX的本意,虽然像www.backbase.combindows都在RIA上有惊人的表现能力,但是速度慢、搜索引擎支持不好、开发难度大等毛病还是无法让用户满意的,请记住:AJAX的最终目的是为了提高用户体
验,为了方便用户交互,而不是因技术而技术的。

三、AJAX的MVC架构设计
  很多人认为在成熟的框架中应用AJAX会破坏框架的完整性,比较常见的说法有三层架构的WEB应用中破坏MVC模式,其实不然。MVC的理论我就不多说了,经典的那三个层、五条线大家都很熟悉,在WEB应用中,因为浏览器/服务器固有的这种请求/响应的断开式网络通讯模式,决定了在Model层无法实现主动向View层发出数据更新事件,所以一般常见的成熟MVC框架中都将经典MVC理论稍作修改:由Model层处理完业务后通知Control层,然后由Control层承担向View发送数据更新的义务。但是AJAX天生具有监听功能,AJAX实现异步响应的那个OnReadyStateChange事件就具有在客户端程序中才会有的事件监听功能。现在想来,利用AJAX实现的MVC模型有如下图这样:
http://photos22.flickr.com/33351292_f29b63cf6b_o.png
理想化的设计如下所示:
三层对应的文件对象:view.jsp(视图)、action.do(控制器)、model.java(模型)
view.jsp是用户看到的界面,并通过内置的AJAX对象异步方式给action.do发送请求,AJAX.OnReadyStateChange开始监听
action.do接收到view.jsp发过来的请求(GET或者POST方式),通过Request判断后发送给相应的业务/数据模型model.java
model.java开始执行业务操作,执行完毕直接给view.jsp页面发送数据更新的通知,这个通知的消息有可能是XML封装的数据,也有可能是一段文本,甚至是一段HTML代码,当然,既然用MVC,不推荐有Model发送HTML,推荐还是用XML封装业务数据即可。
view.jsp页面中AJAX对象的OnReadyStateChange接收到了数据更新通知,根据实际情况用DOM进行页面呈现更新。
通过以上几步一气呵成,一个典型的基于MVC的三层交互就完成了。当然,熟悉WEB下的MVC框架的用户,如熟悉Struts的Java开发人员可能不习惯由Model层给View直接发送数据更新通知,那咱们也可以转变一下,Model层业务处理完毕将更新通知先发送给Control,由Control去通知View亦可。

京ICP备05053527号
经过27次查询历时0.450秒终于生成了此页面
Powered by WordPress & Designed by Felix © 2012