Please read instructions at the end of the article on how to run the application after downloading the source.
Introduction
ASP.NET Atlas is a rich set of client side and server side libraries to develop AJAX-style applications using ASP.NET. This tutorial (and probably more in this series) attempts to provide a general view of the features available in Atlas. Since, Atlas is a very vast library, this very first tutorial concentrates on two most important features of Atlas:
- Ability to call server side web services from client side scripts
- Ease of developing cross-browser compatible JavaScript code
Background
MFC Scribble application was one of the first applications that I used to learn MFC. Therefore, I decided to base this tutorial on Scribble. Scribble application allows users to draw freehand sketches using the mouse. I first saw a similar application on the web, utilizing AJAX technologies, at the JavaScript Draw website. The JavaScript draw website works only on Mozilla Firefox. This article describes how to build a cross-browser version of the application. We will build on the application in each article in this series to demonstrates more features of Atlas.
Installing Atlas
At the time of writing of this article, December CTP of Atlas can be downloaded by clicking this link. If this link does not work, you can always go to the Atlas website to get the correct link. The Atlas library is available as a Visual Studio 2005 template (VSI). The download site has instructions on how to install the template.
Creating an Atlas Project
Once the Atlas template is installed, you can create a blank Atlas project by clicking selecting the menu option: File -> New -> Web Site. This brings up the New Web Site dialog box as shown:
Under location, you can either select File System or HTTP. Selecting HTTP will allow you to create a web site on an IIS server and selecting File System will allow you to create a web site on your local file system (which you can debug and test using the ASP.NET development web server). You can select either option, but I have found the application to work better with Internet Explorer on IIS.
Atlas Blank Project
The newly created Atlas web site has the following directory structure:
- App_Data
This is an empty directory where you can place data files. - Bin
This is the directory where DLL file for the assembly Microsoft.Web.Atlas
is placed. This contains the server portion of the Atlas library. - ScriptLibrary
A directory where you can place any JavaScript files for the application.
- Atlas
Atlas client Scripts are placed here in two different subdirectories.
- Debug
The debug version of Atlas client side JavaScript files are placed in this directory. - Release
The release version of Atlas client side JavaScript files are placed in this directory. The scripts in this directory are more compactly written and have some debug code removed.
Atlas Client Scripts
The December release of Atlas has the following client scripts:
- Atlas.js
This is the core Atlas script file consisting of basic utility functions and client side controls and components. - AtlasCompat.js
This file contains the Atlas compatibility layer for supporting Mozilla Firefox and Apple-iMac-Safari web browsers. This script ensures that Atlas code is cross browser compatible. - AtlasCompat2.js
Additional functions to ensure compatibility for the Safari web browser, are included in this file. - AtlasRuntime.js
This is a scaled down version of the core Atlas script file. This script file does not have the client side components and controls. This script file can be used when the aforementioned components or controls are not being used in a web page. - AtlasUIDragDrop.js
This file contains utility functions to provide drag drop functionality in a web page. - AtlasUIGlitz.js
This file contains utility functions to provide animation and other special effects in a web page. - AtlasUIMap.js
This is the script file for Atlas mapping framework that uses Virtual Earth.
Other Files
Atlas adds the following files to the root directory of the web site.
- Default.aspx and Default.aspx.cs
This is a web page containing Atlas Script Manager control that is responsible for rendering script blocks referring to the Atlas client side scripts. An client script of type test/xml-script block is also added to the page. This script block is used to write scripts using declarative XML syntax. - eula.rtf
- readme.txt
- Web.Config
The web.config is essential for running Atlas applications. It contains some configuration settings specific to Atlas and also adds the Atlas HTTP modules and HTTP handlers.
The Scribble Application
The Scribble application allows users to draw freehand sketches by clicking on the left mouse button and moving mouse around. A sketch stroke ends when the user releases the mouse button or moves outside the drawing area. There are ways to draw using JavaScript by utilizing VML, but we are not going to use VML in this sample.
The default web page in Scribble will have an image (a regular HTML image - the IMG
tag). The user mouse events over the image are captured using JavaScript event handlers. The JavaScript functions send the series of points in a sketch stroke to a web service. The web service updates and image object saved in a session variable by drawing lines through all the points sent by the client. Finally, the client requests an updated image from the server. The image source is an HTTP handler which streams the image stored in the session variable to the client. Here are the main components of the application.
- Default.aspx
The page with the dynamic image and Atlas Script Manager control. - ScribbleImage.ashx
This is an HTTP handler which streams the image object stored in the session variable. - ScribbleService.asmx
This is the web service to which all the sketching requests are sent. The webservice modifies the image. - Scribble.js
The JavaScript code for the application resides in this file to make a clear separation between design and the code. - Global.asax
The Session_Start
and the Session_End
events are handled in Global.asax. The Session_Start
creates the session variable and Session_End
disposes the image stored in the session variable.
Global.asax
We begin our coding process from Global.asax.
- In the Website menu, click on Add New Item or press Ctrl + Shift + A.
- In the Add New Item dialog box, select Global Application Class and click ok. You will see the Global.asax file created.
- We start by importing the
System.Drawing
namespace. Insert the following line of code just after the first line:
<%@ Import Namespace="System.Drawing" %>
- Add the following code to
Session_Start
function:
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;
}
The code creates a simple bitmap 200 pixels by 200 pixels white, paints the entire background white and assigns it to the session variable named Image
. - The
Session_End
function should dispose the image stored in the session variable.
Bitmap bmp = (Bitmap)Session["Image"];
bmp.Dispose();
- From the Website menu, select Add Reference.
- Select
System.Drawing
in the Add Reference dialog box and click OK. - Finally, in the Build menu click Build Web Site or press Ctrl + Shift + B to make sure that there are no build errors.
ScribbleImage.ashx
This web handler is supposed to stream the image stored in the session variable back to the client.
- In the WebSite menu, click on Add New Item or press Ctrl + Shift + A.
- In the Add New Item dialog box, select Generic Handler, set the name of the handler to ScribbleImage.ashx and click OK.
- For a web handler to use session variables, it needs to implement the interface
IRequiresSessionState
. This is only marker interface and has no methods to override. Edit the class declaration to look like the following:
public class ScribbleImage : IHttpHandler,
System.Web.SessionState.IRequiresSessionState
- Next, we add code to the
ProcessRequest
method.
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());
}
}
}
}
- The first line sets
ContentType
header in the response to image/png. This make sure that the browser identifies the response to be a png image instead of HTML. - The next four lines indicate to the browser that the response should not be cached. All these four lines are necessary to make sure that the code is cross browser compatible. We will optimize the code in the later versions of the tutorial.
- Finally, the bitmap from the session variable is saved to a memory stream and the contents of the memory stream are written to response. The
BinaryWrite
function is used, as the image is binary data.
ScribbleService.asmx
We have means of initializing the session image and to stream the image contents as response. Now, we need some method to add content to the image itself. We expect the clients to call on ScribbleService.asmx web service to add lines to the image.
- In the WebSite menu, click on Add New Item or press Ctrl + Shift + A.
- In the Add New Item dialog box, select Web Service, specify the name to be ScribbleService.asmx and click OK. Make sure that you uncheck Place Code in a Separate File.
- Import the
namespace System.Drawing
by adding the following line to the series of namespace imports:
using System.Drawing;
- Next, we need to define a simple class for a point. We cannot use the
System.Drawing.Point
class as it is not XML serializable. In a later tutorial, we will see how we can use System.Drawing.Point
instead of the custom class. Add the following code just before the ScribbleService
class declaration:
public class Point
{
public int X;
public int Y;
};
- Finally, we need to add a method to draw the sketch given a set of points. We add a web method
Draw
to our web service.
[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;
}
}
}
}
}
- The attribute
WebMethod(EnableSession = true)
ensures that the session variables are accessible from the web service. - The image is locked to make sure that concurrent accesses are safe.
- The drawing itself is pretty simple as it just joins the points supplied in the
points
array.
Scribble.js
We have the server side image handler and the server side web service to update the image. Now, we need the client side script in scribble application that will send the points from mouse events to the server web service.
- Highlight the ScriptLibrary folder in the solution explorer.
- In the WebSite menu, click on Add New Item or press Ctrl + Shift + A.
- In the Add New Item dialog box, select JScript File, sepcify the name to be Scribble.js and click OK. This will place Scribble.js in the ScriptLibrary folder.
- Next, we need to declare some global variables. In this first version of scribble, we will use global variables but in the coming versions, we start using JavaScript objects.
var image;
var originalSrc;
var iter = 0;
var points = null;
The comments above the variable declaration describe the purpose behind each variable. The iter
variable is used to modify the source of the image after a draw request is sent to the server. In case of Internet Explorer, setting image.src = image.src
does refresh the image but the same code does not work for Firefox. To work around this problem, we maintain the variables iter
which is incremented every time we send a Draw
request to the Webservice
. We add the iteration number to the originalSrc
variable so that the browser thinks that it needs to request the server to get fresh data as opposed to using the cached image. - We define the function
startStroke
which starts a stroke in response to the mousedown
event.
function startStroke()
{
points = new Array();
window.event.returnValue = false;
}
When a new stroke starts, we create a fresh set of points as indicated by the first line. The second line cancels the default behavior of the event. This is necessary as the default behavior of the mousedown
event for an image is to start a drag operation which prevents any further events from being fired. - When a stroke ends in response to the
mouseup
event or mouseout
event, we need to make the actual call to the webservice
. This is done in the endStroke
function.
function endStroke()
{
if (!points || points.length < 2)
return true;
ScribbleService.Draw(points, onWebMethodComplete,
onWebMethodTimeout, onWebMethodError);
points = null;
window.event.returnValue = false;
}
The only interesting line in the function is ScribbleService.Draw(points, onWebMethodComplete, onWebMethodTimeout, onWebMethodError);
, which invokes the web service method Draw
in ScribbleService.asmx asynchronously. The function is automatically made available to us by the Atlas framework. onWebMethodError
is a function which gets invoked by Atlas framework when an error occurs in the web service method and onWebMethodTimeout
gets invoked when the web method call exceeds a configurable timeout defined in the Atlas framework. In this version, we just show the user a message box with the error text.
function onWebMethodError(fault)
{
alert("Error occured:\n" + fault.get_message());
}
function onWebMethodTimeout()
{
alert("Timeout occured");
}
onWebMethodComplete
function is called when the web method call is successful. The image needs to be reloaded when this happens.
function onWebMethodComplete(result, response, context)
{
var shimImage = new Image(200, 200);
shimImage.src = originalSrc + "?" + iter++;
shimImage.onload = function()
{
image.src = shimImage.src;
}
}
We create an Image
object shimImage
and set its source to the original source of the image we are drawing on. When the image object loads, we set the source of the actual HTML image element on the page to the source of the temporary image object. This is done to avoid flicker when replacing the image. - We need to fill the
points
array during the mousemove
event. This is done in the addPoints
function.
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;
}
}
- A new point object is constructed with the
offsetX
and offsetY
properties of the event
object and then it is appended to the points
array. The offsetX
and offsetY
properties give the relative mouse position with respect to the HTML element causing the event. - If the length of array has reached
3
, we automatically request the server to do a draw operation and reset the points
array. This is done so that the user can see the drawing before he releases the mouse button.
- Finally, we need to hook the events, this is done in the
pageLoad
function.
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);
}
- The
pageLoad
function is a special function which gets invoked when the Atlas framework has finished loading. We use this instead of the regular window or body load events so that we can be sure that Atlas has finished loading. - The actual image element which gets sketched is placed, inside a
div
tag with an id of drawingSurface
. The size of the element is same as the size of the image so we can safely attach to the events to the drawingSurface
div
.
Default.aspx
The individual components of the application need to be assembled in the Default.aspx page. Here is the code of this page.
<%@ 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>
The most important aspect of this page is the atlas:ScriptManager
server control. The ScriptManager
server control is responsible for generating client side script blocks for Atlas and for any web service proxy scripts. Let's examine the usage of the ScriptManager
control in the Default.aspx page.
- The
EnableScriptComponents
property is set to false
. This generates a client side script block referring to AtlasRuntime.js instead of Atlas.js. We prefer the lightweight version of Atlas Framework in this version of scribble as we are not using any Atlas components or controls. - We add a service reference to the ScribbleService.asmx web service. This will generate a URL reference to client side script for the web service proxy.
- We add the Scribble.js as another script reference.
This brings all the pieces together and now you can compile and run the project. I encourage you to see the actual client HTML generated by the Atlas Script Manager.
The Atlas Magic
Here is what Atlas framework did for us:
- It allowed us to write the web application without us making special efforts to make it cross browser. The web service invocation and the client side event handling automatically works on both Internet Explorer and Firefox. The Atlas framework adds the required JavaScript prototypes to Firefox objects to make them look like Internet Explorer objects. The Internet Explorer specific functions like
attachEvent
and event.offsetX
and event.offsetY
are made available to Firefox. You can look at AtlasCompat.js file to see how this is done. - It automatically created a JavaScript proxy for the Scribble web service methods. The JavaScript proxy script file for a ScribbleService.asmx file has the URL ScribbleService.asmx/js. This is generated by the Atlas HTTP module added in the web.config.
Where are We
We have seen how to call web services and how to write cross browser applications with ease using Atlas. In an upcoming tutorial, we see more of Atlas client side controls and declarative programming (depending on user feedback!). If you liked the tutorial, please feel free to write a comment. If you did not like it, please write a comment on how to improve it.
Downloading and Running the Source
As Atlas is not yet redistributable, I have not included the Atlas files in the source download. Here are the steps you need to get the downloaded source to work.
- You need to download Atlas from Atlas Web Site.
- After downloading the Atlas blank project template, create a new Web Site by pointing to New in the File menu and selecting Web Site
- Extract the source zip file to the directory of newly created projects overwriting any existing files.
- In the Website menu, select Add Existing Item, add ScribbleService.asmx and ScribbleImage.ashx from the root directory of the website, and Scribble.js from the ScriptLibrary folder.
- Build and run the web site.