Atlas 教程: 创建一个 AJAX 涂鸦应用程序






4.86/5 (53投票s)
本教程介绍如何创建流行的 MFC 示例的 AJAX 版本。本教程使用了 ASP.NET Atlas 框架。
下载源代码后,请阅读文章末尾的说明,了解如何运行该应用程序。
引言
ASP.NET Atlas 是一套丰富的客户端和服务器端库,用于使用 ASP.NET 开发 AJAX 风格的应用程序。本教程(以及本系列中可能更多的教程)旨在全面介绍 Atlas 中可用的功能。由于 Atlas 是一个非常庞大的库,因此本教程将重点介绍 Atlas 的两个最重要功能:
- 从客户端脚本调用服务器端 Web 服务的能力
- 轻松开发跨浏览器兼容的 JavaScript 代码
背景
MFC Scribble 应用程序是我用来学习 MFC 的第一个应用程序之一。因此,我决定以此为基础来编写本教程。Scribble 应用程序允许用户使用鼠标绘制自由手绘草图。我首先在 JavaScript Draw 网站上看到了一个利用 AJAX 技术实现的类似应用程序。JavaScript Draw 网站仅在 Mozilla Firefox 上运行。本文介绍如何构建该应用程序的跨浏览器版本。我们将在本系列文章的每一篇中继续完善该应用程序,以演示更多 Atlas 的功能。
安装 Atlas
撰写本文时,可以通过点击此 链接 下载 Atlas 的十二月 CTP。如果此链接不起作用,您可以随时访问 Atlas 网站以获取正确的链接。Atlas 库可作为 Visual Studio 2005 模板 (VSI) 提供。下载网站上有关于如何安装模板的说明。
创建 Atlas 项目
安装 Atlas 模板后,可以通过选择菜单选项:文件 -> 新建 -> 网站 来创建一个空白 Atlas 项目。这将弹出“新建网站”对话框,如下图所示:
在“位置”下,您可以选择“文件系统”或“HTTP”。选择 HTTP 将允许您在 IIS 服务器上创建网站,选择文件系统将允许您在本地文件系统上创建网站(您可以使用 ASP.NET 开发 Web 服务器对其进行调试和测试)。您可以选择任一选项,但我发现该应用程序在 IIS 上的 Internet Explorer 中运行效果更好。
Atlas 空白项目
新创建的 Atlas 网站具有以下目录结构:
- App_Data
这是一个空目录,您可以在其中放置数据文件。 - Bin
这是放置程序集Microsoft.Web.Atlas
的 DLL 文件的目录。它包含 Atlas 库的服务器部分。 - ScriptLibrary
您可以在此目录中放置应用程序的任何 JavaScript 文件。- Atlas
Atlas 客户端脚本在此处的两个不同子目录中。- Debug
Atlas 客户端 JavaScript 文件的调试版本放置在此目录中。 - Release
Atlas 客户端 JavaScript 文件的发布版本放置在此目录中。此目录中的脚本写得更紧凑,并移除了一些调试代码。
- Debug
- Atlas
Atlas 客户端脚本
Atlas 的十二月版本包含以下客户端脚本:
- Atlas.js
这是核心 Atlas 脚本文件,包含基本实用函数以及客户端控件和组件。 - AtlasCompat.js
此文件包含 Atlas 兼容层,用于支持 Mozilla Firefox 和 Apple-iMac-Safari Web 浏览器。此脚本可确保 Atlas 代码跨浏览器兼容。 - AtlasCompat2.js
此文件包含额外的函数,以确保与 Safari Web 浏览器兼容。 - AtlasRuntime.js
这是核心 Atlas 脚本文件的精简版本。此脚本文件不包含客户端组件和控件。在不使用上述组件或控件的网页中,可以使用此脚本文件。 - AtlasUIDragDrop.js
此文件包含实用函数,可在网页中提供拖放功能。 - AtlasUIGlitz.js
此文件包含实用函数,可在网页中提供动画和其他特殊效果。 - AtlasUIMap.js
这是 Atlas 地图框架的脚本文件,该框架使用 Virtual Earth。
其他文件
Atlas 将以下文件添加到网站的根目录。
- Default.aspx 和 Default.aspx.cs
这是一个包含 Atlas Script Manager 控件的网页,该控件负责呈现引用 Atlas 客户端脚本的脚本块。页面中还添加了一个类型为 test/xml-script 的客户端脚本块。此脚本块用于使用声明式 XML 语法编写脚本。 - eula.rtf
- readme.txt
- Web.Config
web.config 对于运行 Atlas 应用程序至关重要。它包含一些特定于 Atlas 的配置设置,并添加了 Atlas HTTP 模块和 HTTP 处理程序。
Scribble 应用程序
Scribble 应用程序允许用户通过单击鼠标左键并四处移动鼠标来绘制自由手绘草图。当用户释放鼠标按钮或移出绘图区域时,草图笔触结束。可以通过利用 VML 的 JavaScript 来绘制,但在此示例中我们不使用 VML。
Scribble 中的默认网页将包含一个图像(普通的 HTML 图像 - IMG
标签)。通过 JavaScript 事件处理程序捕获用户在图像上的鼠标事件。JavaScript 函数将草图笔触的点序列发送到 Web 服务。Web 服务通过绘制客户端发送的所有点之间的线条来更新会话变量中保存的图像对象。最后,客户端请求服务器更新的图像。图像源是一个 HTTP 处理程序,该处理程序将存储在会话变量中的图像流式传输到客户端。以下是应用程序的主要组件:
- Default.aspx
包含动态图像和 Atlas Script Manager 控件的页面。 - ScribbleImage.ashx
这是一个 HTTP 处理程序,用于流式传输存储在会话变量中的图像对象。 - ScribbleService.asmx
这是将所有绘图请求发送到的 Web 服务。该 Web 服务会修改图像。 - Scribble.js
应用程序的 JavaScript 代码在此文件中,以实现设计与代码之间的清晰分离。 - Global.asax
在 Global.asax 中处理Session_Start
和Session_End
事件。Session_Start
创建会话变量,Session_End
释放存储在会话变量中的图像。
Global.asax
我们将从 Global.asax 开始编码过程。
- 在“网站”菜单中,单击“添加新项”或按 Ctrl + Shift + A。
- 在“添加新项”对话框中,选择“全局应用程序类”并单击“确定”。您将看到创建的 Global.asax 文件。
- 我们首先导入
System.Drawing
命名空间。在第一行之后插入以下代码行:<%@ Import Namespace="System.Drawing" %>
- 将以下代码添加到
Session_Start
函数:void Session_Start(object sender, EventArgs e) { Bitmap bmp = new Bitmap(200, 200); using (Graphics g = Graphics.FromImage(bmp)) { g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, bmp.Width, bmp.Height)); g.Flush(); } Session["Image"] = bmp; }
该代码创建一个简单的 200 像素 x 200 像素的白色位图,将整个背景涂成白色,并将其分配给名为Image
的会话变量。 Session_End
函数应释放存储在会话变量中的图像。Bitmap bmp = (Bitmap)Session["Image"]; bmp.Dispose();
- 在“网站”菜单中,选择“添加引用”。
- 在“添加引用”对话框中,选择
System.Drawing
并单击“确定”。 - 最后,在“生成”菜单中单击“生成网站”或按 Ctrl + Shift + B,以确保没有生成错误。
ScribbleImage.ashx
此 Web 处理程序用于将存储在会话变量中的图像流式传输回客户端。
- 在“网站”菜单中,单击“添加新项”或按 Ctrl + Shift + A。
- 在“添加新项”对话框中,选择“通用处理程序”,将处理程序的名称设置为 ScribbleImage.ashx 并单击“确定”。
- 要使 Web 处理程序能够使用会话变量,它需要实现
IRequiresSessionState
接口。这只是一个标记接口,没有需要重写的方法。将类声明编辑为如下所示:public class ScribbleImage : IHttpHandler, System.Web.SessionState.IRequiresSessionState
- 接下来,我们将代码添加到
ProcessRequest
方法。public void ProcessRequest (HttpContext context) { context.Response.ContentType = "image/png"; context.Response.Cache.SetNoStore(); context.Response.Cache.SetCacheability(HttpCacheability.NoCache); context.Response.Cache.SetExpires(DateTime.Now); context.Response.Cache.SetValidUntilExpires(false); System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)context.Session["Image"]; lock(bmp) { using (MemoryStream ms = new MemoryStream()) { bmp.Save(ms, ImageFormat.Png); ms.Flush(); context.Response.BinaryWrite(ms.GetBuffer()); } } } }
- 第一行将响应中的
ContentType
标头设置为 image/png 。这确保浏览器将响应识别为 PNG 图像而不是 HTML。 - 接下来的四行指示浏览器不缓存响应。这四行代码对于确保代码跨浏览器兼容性是必需的。我们将在本教程的后续版本中优化代码。
- 最后,将会话变量中的位图保存到内存流,并将内存流的内容写入响应。使用
BinaryWrite
函数,因为图像是二进制数据。
- 第一行将响应中的
ScribbleService.asmx
我们有初始化会话图像并以响应形式流式传输图像内容的方法。现在,我们需要一些方法来向图像本身添加内容。我们期望客户端调用 ScribbleService.asmx Web 服务来向图像添加线条。
- 在“网站”菜单中,单击“添加新项”或按 Ctrl + Shift + A。
- 在“添加新项”对话框中,选择“Web 服务”,将名称指定为 ScribbleService.asmx 并单击“确定”。确保取消选中“将代码放在单独的文件中”。
- 通过将以下行添加到命名空间导入系列中,导入
namespace System.Drawing
:using System.Drawing;
- 接下来,我们需要定义一个简单的点类。我们不能使用
System.Drawing.Point
类,因为它不是 XML 可序列化的。在后续教程中,我们将看到如何使用System.Drawing.Point
而不是自定义类。在ScribbleService
类声明之前添加以下代码:public class Point { public int X; public int Y; };
- 最后,我们需要添加一个方法来绘制给定点集的草图。我们将一个 Web 方法
Draw
添加到我们的 Web 服务中。[WebMethod(EnableSession = true)] public void Draw(Point[] points) { Image scribbleImage = (Image)Session["Image"]; lock(scribbleImage) { using (Graphics g = Graphics.FromImage(scribbleImage)) using(Pen p = new Pen(Color.Black, 2)) { if (points.Length > 1) { int startX = points[0].X; int startY = points[0].Y; for (long i = 1; i < points.Length; i++) { g.DrawLine(p, startX, startY, points[i].X, points[i].Y); startX = points[i].X; startY = points[i].Y; } } } } }
WebMethod(EnableSession = true)
属性可确保可以从 Web 服务访问会话变量。- 图像被锁定以确保并发访问是安全的。
- 绘图本身非常简单,因为它只是连接
points
数组中提供的点。
Scribble.js
我们有了服务器端图像处理程序和用于更新图像的服务器端 Web 服务。现在,我们需要 Scribble 应用程序中的客户端脚本,该脚本会将鼠标事件中的点发送到服务器 Web 服务。
- 在解决方案资源管理器中突出显示 ScriptLibrary 文件夹。
- 在“网站”菜单中,单击“添加新项”或按 Ctrl + Shift + A。
- 在“添加新项”对话框中,选择“JScript 文件”,将名称指定为 Scribble.js 并单击“确定”。这将把 Scribble.js 放在 ScriptLibrary 文件夹中。
- 接下来,我们需要声明一些全局变量。在此 Scribble 的第一个版本中,我们将使用全局变量,但在后续版本中,我们将开始使用 JavaScript 对象。
//The HTML image element that is to be drawn var image; //The source of the image var originalSrc; //The number of iteration var iter = 0; //The array of points var points = null;
变量声明上方的注释描述了每个变量的用途。iter
变量用于在将绘图请求发送到服务器后修改图像的源。对于 Internet Explorer,设置image.src = image.src
可以刷新图像,但相同的代码在 Firefox 中无效。为了解决这个问题,我们维护iter
变量,每次向Webservice
发送Draw
请求时都会递增该变量。我们将迭代号添加到originalSrc
变量,以便浏览器认为它需要请求服务器以获取新数据,而不是使用缓存的图像。 - 我们定义了
startStroke
函数,该函数响应mousedown
事件来开始笔触。function startStroke() { points = new Array(); window.event.returnValue = false; }
当新的笔触开始时,我们创建一个新的点集,如第一行所示。第二行会取消事件的默认行为。这是必需的,因为图像的mousedown
事件的默认行为是启动拖动操作,这将阻止任何进一步的事件被触发。 - 当笔触响应
mouseup
事件或mouseout
事件结束时,我们需要实际调用webservice
。这在endStroke
函数中完成。function endStroke() { if (!points || points.length < 2) return true; //Send the points to the webservice ScribbleService.Draw(points, onWebMethodComplete, onWebMethodTimeout, onWebMethodError); points = null; window.event.returnValue = false; }
函数中唯一有趣的一行是ScribbleService.Draw(points, onWebMethodComplete, onWebMethodTimeout, onWebMethodError);
,它异步调用 ScribbleService.asmx 中的 Web 服务方法Draw
。Atlas 框架会自动为我们提供该函数。 onWebMethodError
是 Atlas 框架在 Web 服务方法中发生错误时调用的函数,而onWebMethodTimeout
在 Web 方法调用超过 Atlas 框架中定义的配置超时时调用。在此版本中,我们只向用户显示一个带有错误文本的消息框。function onWebMethodError(fault) { alert("Error occured:\n" + fault.get_message()); } function onWebMethodTimeout() { alert("Timeout occured"); }
- 当 Web 方法调用成功时,会调用
onWebMethodComplete
函数。此时需要重新加载图像。function onWebMethodComplete(result, response, context) { //We need to refresh the image var shimImage = new Image(200, 200); shimImage.src = originalSrc + "?" + iter++; shimImage.onload = function() { image.src = shimImage.src; } }
我们创建一个Image
对象shimImage
,并将其源设置为我们正在绘制的图像的原始源。当图像对象加载时,我们将页面上的实际 HTML 图像元素的源设置为临时图像对象的源。这是为了在替换图像时避免闪烁。 - 我们需要在
mousemove
事件期间填充points
数组。这在addPoints
函数中完成。function addPoints() { if (points) { var point = { X : window.event.offsetX, Y : window.event.offsetY}; points.push(point); if (points.length == 3) { endStroke(); points = new Array(); points.push(point); } window.event.returnValue = false; } }
- 使用
event
对象的offsetX
和offsetY
属性构造一个新的点对象,然后将其添加到points
数组中。offsetX
和offsetY
属性给出相对于导致事件的 HTML 元素的位置的鼠标位置。 - 如果数组长度达到
3
,我们将自动请求服务器执行绘图操作并重置points
数组。这样做是为了让用户在释放鼠标按钮之前看到绘图。
- 使用
- 最后,我们需要挂钩事件,这在
pageLoad
函数中完成。function pageLoad() { var surface = document.getElementById("drawingSurface"); image = surface.getElementsByTagName("IMG")[0]; originalSrc = image.src; surface.attachEvent("onmousedown", startStroke); surface.attachEvent("onmouseup", endStroke); surface.attachEvent("onmouseout", endStroke); surface.attachEvent("onmousemove", addPoints); }
pageLoad
函数是一个特殊函数,在 Atlas 框架加载完成后调用。我们使用它而不是常规的 window 或 body 加载事件,以便我们可以确信 Atlas 已加载完毕。- 实际进行素描的图像元素位于具有 ID
drawingSurface
的div
标签内。该元素的大小与图像的大小相同,因此我们可以安全地将事件附加到drawingSurface
div
。
Default.aspx
应用程序的各个组件需要在 Default.aspx 页面中进行组装。以下是该页面的代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Atlas Scribble Sample</title>
</head>
<body>
<form id="form1" runat="server">
<Atlas:ScriptManager ID="AtlasScriptManager" runat="server"
EnableScriptComponents="False" >
<Services>
<Atlas:ServiceReference Path="ScribbleService.asmx" />
</Services>
<Scripts>
<Atlas:ScriptReference Path="ScriptLibrary/Scribble.js" />
</Scripts>
</Atlas:ScriptManager>
<div id="drawingSurface"
style="border:solid 1px black;height:200px;width:200px">
<img alt="Scribble" src="ScribbleImage.ashx"
style="height:200px;width:200px" galleryimg="false" />
</div>
</form>
</body>
</html>
此页面最重要的方面是 atlas:ScriptManager
服务器控件。ScriptManager
服务器控件负责生成 Atlas 的客户端脚本块以及任何 Web 服务代理脚本。让我们检查 ScriptManager
控件在 Default.aspx 页面中的用法。
EnableScriptComponents
属性设置为false
。这会生成一个引用 AtlasRuntime.js 而不是 Atlas.js 的客户端脚本块。在此版本的 scribble 中,我们更喜欢使用 Atlas Framework 的轻量级版本,因为我们没有使用任何 Atlas 组件或控件。- 我们添加了对 ScribbleService.asmx Web 服务的服务引用。这将生成 Web 服务代理的客户端脚本的 URL 引用。
- 我们将 Scribble.js 添加为另一个脚本引用。
这会将所有部分整合在一起,现在您可以编译并运行项目了。我鼓励您查看 Atlas Script Manager 生成的实际客户端 HTML。
Atlas 的魔力
Atlas 框架为我们做了以下工作:
- 它允许我们编写 Web 应用程序,而无需我们付出额外的努力来使其跨浏览器兼容。Web 服务调用和客户端事件处理在 Internet Explorer 和 Firefox 上都能正常工作。Atlas 框架向 Firefox 对象添加了必需的 JavaScript 属性,使其看起来像 Internet Explorer 对象。Internet Explorer 特有的函数,如
attachEvent
和event.offsetX
、event.offsetY
,都可以在 Firefox 中使用。您可以查看 AtlasCompat.js 文件,了解这是如何实现的。 - 它自动为 Scribble Web 服务方法创建了一个 JavaScript 代理。ScribbleService.asmx 文件的 JavaScript 代理脚本文件的 URL 是 ScribbleService.asmx/js 。这是由 web.config 中添加的 Atlas HTTP 模块生成的。
我们现在在哪里
我们已经看到了如何使用 Atlas 轻松调用 Web 服务以及如何编写跨浏览器应用程序。在接下来的教程中,我们将学习更多 Atlas 客户端控件和声明式编程(取决于用户反馈!)。如果您喜欢本教程,请随时发表评论。如果您不喜欢,请发表评论,说明如何改进。
下载和运行源代码
由于 Atlas 尚不可分发,因此我未在源代码下载中包含 Atlas 文件。以下是使下载的源代码能够正常工作的步骤:
- 您需要从 Atlas 网站 下载 Atlas。
- 下载 Atlas 空白项目模板后,通过在文件菜单中指向“新建”并选择“网站”来创建一个新网站。
- 将源代码 zip 文件解压缩到新创建的项目目录中,覆盖任何现有文件。
- 在“网站”菜单中,选择“添加现有项”,从网站的根目录添加 ScribbleService.asmx 和 ScribbleImage.ashx ,并从 ScriptLibrary 文件夹添加 Scribble.js 。
- 生成并运行网站。