Atlas Keynote Walkthrough
I
mentioned a little earlier
that I’d post the Atlas sample code and overall talk flow
from the keynote demo I was part of earlier today at the
PDC. Here it is
below:
Step 1: The Web Service We Use
In the talk Anders used the new LINQ features in C# 3.0
to query for process information, then join it in memory
with data from a database, and then transform it into
XML. Don then
showed exposing it via Indigo as first a SOAP end-point,
then an RSS end-point, and then finally as a JSON
end-point that I could consume.
Unfortunately I don’t have a snap-shot of the above code
still with me (it is on another machine right now), so
can’t show what we really used during the keynote. Here is a simplified .asmx version, though, that shows
what the signature of the service looks like The sample
below is calling System.Diagnostics directly instead of
using the cool new LINQ features, but hopefully provides a
little insight into the service signature. Note that there is no Atlas specific decoration or
meta-data that needs to be applied to the web-service – it
is just a standard .asmx file (Atlas then installs an
HttpModule that can handle transforming the input/output
of the service to a JSON wire protocol as
appropriate):
LapService.asmx
<%@
WebService
Language="C#"
Class="LapService"
%>
using
System;
using
System.Collections;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.IO;
using
System.Web;
using
System.Web.Services;
using
System.Web.Services.Protocols;
public
class
LapService :
System.Web.Services.WebService
{
[WebMethod]
public
ProcessData[]
GetProcesses() {
return
this.GetAllProcesses().ToArray();
}
[WebMethod]
public
ProcessData[]
MatchProcesses(string
expression)
{
List<ProcessData> data =
this.GetAllProcesses();
if (String.IsNullOrEmpty(expression))
{
return
data.ToArray();
}
string[] words =
expression.Split(' ');
return
data.FindAll(new
Predicate<ProcessData>(delegate(ProcessData
process)
{
return
System.Text.RegularExpressions.Regex.IsMatch(process.Name, expression);
})).ToArray();
}
private
List<ProcessData> GetAllProcesses()
{
List<ProcessData> data = new
List<ProcessData>();
System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
foreach
(System.Diagnostics.Process
process in processes)
{
data.Add(new
ProcessData(process.ProcessName, process.WorkingSet64,
process.Threads.Count));
}
return data;
}
}
ProcessData.cs
using
System;
using
System.ComponentModel;
public
class
ProcessData
{
private
string _name;
private
long _workingSet;
private
int _threadCount;
public
string Name {
get {
return _name; }
set { _name =
value; }
}
public
long WorkingSet {
get {
return _workingSet; }
set { _workingSet =
value; }
}
public
long WorkingSetInMB {
get {
return _workingSet /
(1024 * 1000); }
private
set { }
}
public
int ThreadCount {
get {
return _threadCount;
}
set { _threadCount =
value; }
}
public ProcessData() {
}
public ProcessData(string
name, long workingSet,
int threadCount) {
_name =
name;
_workingSet = workingSet;
_threadCount = threadCount;
}
}
Step2 : Simple Invocation of the Web Service
The first step with Atlas we did on stage was to take a
simple html page with
no server-side code
and show binding to the remote LapService end-point with
it. The final code
for this segment looked like the page below.
Note how it is easy to add a JSON proxy to the page –
just add two script references to the page (one that
points to the AtlasCore.js library and one that points to
the .asmx with the /js flag specified). You can then just call methods on the remote service –
and setup a call-back event handler that will be invoked
when the response is returned. You can then work with the data using the same object
model (except in Jscript) that was used on the
server. All code
in the below sample runs on the client.
Default.aspx:
<!DOCTYPE
HTML
PUBLIC
"-//W3C//DTD HTML 4.0 Transitional//EN">
<html
xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en"
lang="en">
<head>
<title>Process Viewer</title>
<link
type="text/css"
rel="stylesheet"
href="simple.css"
/>
<script
src="ScriptLibrary/AtlasCore.js"
type="text/javascript"></script>
<script
src="LapService.asmx/js"
type="text/javascript"></script>
<script language="javascript" type="text/javascript">
function
Button1_onclick() {
var text1 =
document.getElementById("Text1");
LapService.MatchProcesses(text1.value,
onSearchComplete);
}
function
onSearchComplete(results) {
var searchResults =
document.getElementById("searchResults");
for (var
i=0; i<results.get_length(); i++) {
searchResults.innerHTML += results[i].Name +
"<br>";
}
}
</script>
</head>
<body>
<form
id="form1"
runat="server">
<div
id="logo">
Process
Explorer
</div>
<div
id="header">
Search:
<input
id="Text1"
type="text"
/>
<input
id="Button1"
type="button"
value="Search"
onclick="Button1_onclick();"
/>
</div>
<div
id="content">
<div
class="left">
<span
id="searchResults"></span>
</div>
</div>
</form>
</body>
</html>
Step 3: Using an Atlas ListView Control
[In the keynote demo we actually merged this step and the
next one together – but for this post I’ll actually show
what we originally planned (before we ran into a time
crunch and had to compress them together).]
In this step we’ll take our simple invocation further and
use one of the new Atlas controls called ListView to
customize the presentation of the data further (and avoid
having to hard-code it into javascript like the sample
above).
Atlas provides a Javascript client library that provides
a Listview control that I can declare and use with
javascript on the client. Alternatively, I can use the ASP.NET server control model
to nicely encapsulate this and handle automatically
sending out the appropriate client markup (which is what
I’ll do below).
Note that all Atlas-enabled ASP.NET Server Controls
support both a client-side and server-side object model –
so I can write code against them both from client
Javascript and my server code-behind file. In the below example I’m taking the same Results data
that we retrieved above and instead of doing a for-loop
over it I’m binding it on the client to the ListView (by
calling searchResults.control.set_data(results). Note also that all style design is done using standard
CSS.
Default.aspx:
<!DOCTYPE
HTML
PUBLIC
"-//W3C//DTD HTML 4.0 Transitional//EN">
<html
xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en"
lang="en">
<head>
<title>Process Viewer</title>
<link
type="text/css"
rel="stylesheet"
href="simple.css"
/>
<atlas:ScriptManager
runat="server"
/>
<script
src="LapService.asmx/js"
type="text/javascript"></script>
<script
language="javascript"
type="text/javascript">
function
Button1_onclick() {
var text1 =
document.getElementById("Text1");
LapService.MatchProcesses(text1.value,
onSearchComplete);
}
function
onSearchComplete(results) {
var searchResults =
document.getElementById("searchResults");
searchResults.control.set_data(results);
}
</script>
</head>
<body>
<form
id="form1"
runat="server">
<div
id="logo">
Process
Explorer
</div>
<div
id="header">
Search:
<input
id="Text1"
type="text"
/>
<input
id="Button1"
type="button"
value="Search"
onclick="Button1_onclick();"
/>
</div>
<div
id="content">
<div
class="left">
<atlas:ListView
id="searchResults"
runat="server"
ItemTemplateControlID="row"
CssClass="listView"
ItemCssClass="item"
AlternatingItemCssClass="alternatingItem">
<LayoutTemplate>
<ul
runat="server">
<li
id="row"
runat="server">
<atlas:Label
id="name"
runat="server">
<Bindings>
<atlas:Binding
DataPath="Name"
Property="text"
/>
</Bindings>
</atlas:Label>
<atlas:Label
runat="server"
CssClass="bar">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="style"
PropertyKey="width"
/>
</Bindings>
</atlas:Label>
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="text"
/>
</Bindings>
</atlas:Label>MB
</li>
</ul>
</LayoutTemplate>
</atlas:ListView>
</div>
</div>
</form>
</body>
</html>
Step 4: Implementing Drag/Drop
The next step is to implement a master/detail drill-down view, where we let visitors to the site drag/drop processes from our Listview into an ItemView control to drill into its details (entirely on the client without requiring any post-backs or page refreshes). Doing this is pretty easy with Atlas – drag/drop sourcing and targeting can be done declaratively or programmatically (note the <behaviors> tag to see how this is done below). Note that I also switching the CSS stylesheet to "color.css" (from simple.css") just to make it look a little better.
Default.aspx:
<!DOCTYPE
HTML
PUBLIC
"-//W3C//DTD HTML 4.0 Transitional//EN">
<html
xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en"
lang="en">
<head>
<title>Process Viewer</title>
<link
type="text/css"
rel="stylesheet"
href="color.css"
/>
<atlas:ScriptManager
runat="server"
/>
<script
src="LapService.asmx/js"
type="text/javascript"></script>
<script
language="javascript"
type="text/javascript">
function
Button1_onclick() {
var text1 =
document.getElementById("Text1");
LapService.MatchProcesses(text1.value,
onSearchComplete);
}
function
onSearchComplete(results) {
var searchResults =
document.getElementById("searchResults");
searchResults.control.set_data(results);
}
</script>
</head>
<body>
<form
id="form1"
runat="server">
<div
id="logo">
Process
Explorer
</div>
<div
id="header">
Search:
<input
id="Text1"
type="text"
/>
<input
id="Button1"
type="button"
value="Search"
onclick="Button1_onclick();"
/>
</div>
<div
id="content">
<!-- Listview -->
<div
class="left">
<atlas:ListView
id="searchResults"
runat="server"
ItemTemplateControlID="row"
CssClass="listView"
ItemCssClass="item"
AlternatingItemCssClass="alternatingItem">
<Behaviors>
<atlas:DragDropList
runat="server"
DataType="Process"
/>
</Behaviors>
<LayoutTemplate>
<ul
id="ul1"
runat="server">
<li
id="row"
runat="server">
<atlas:Label
id="name"
runat="server">
<Behaviors>
<atlas:DraggableListItem
runat="server"
Handle="name">
<Bindings>
<atlas:Binding
Property="data"
/>
</Bindings>
</atlas:DraggableListItem>
</Behaviors>
<Bindings>
<atlas:Binding
DataPath="Name"
Property="text"
/>
</Bindings>
</atlas:Label>
<atlas:Label
id="Label1"
runat="server"
CssClass="bar">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="style"
PropertyKey="width"
/>
</Bindings>
</atlas:Label>
<atlas:Label
id="Label2"
runat="server">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="text"
/>
</Bindings>
</atlas:Label>MB
</li>
</ul>
</LayoutTemplate>
</atlas:ListView>
</div>
<!-- Item Details -->
<div
class="right">
<atlas:ItemView
id="details"
runat="server">
<Behaviors>
<atlas:DataSourceDropTarget
runat="server"
Append="false"
AcceptedDataTypes="Process"
/>
</Behaviors>
<ItemTemplate>
<atlas:Label
runat="server"
CssClass="name">
<Bindings>
<atlas:Binding
DataPath="Name"
Property="text"
/>
</Bindings>
</atlas:Label>
<span
class="detailRow">
Working set:
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="text"
/>
</Bindings>
</atlas:Label>
</span>
<span
class="detailRow">
Thread count:
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataPath="ThreadCount"
Property="text"
/>
</Bindings>
</atlas:Label>
</span>
</ItemTemplate>
</atlas:ItemView>
</div>
</div>
</form>
</body>
</html>
Step 5: Implementing VirtualEarth
This step was completely gratuitous but fun. Since any “real”
Since there is no way to get real longitude or latitude
information from a running process, we just hard-coded in
the coordinates. But the name of the process was data-bound and live (and
if we did have longitude and latitude information we could
obviously have data-bound that too).
Here is what the code looked like with the virtual earth
inside the bottom of the ItemView (note that no other code
changes were required). I’m using an older push-pin image so the background image
is not transparent (it was for the demo) – but it should
give you an idea of what it looked like:
Default.aspx:
<!DOCTYPE
HTML
PUBLIC
"-//W3C//DTD HTML 4.0 Transitional//EN">
<html
xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en"
lang="en">
<head>
<title>Process Viewer</title>
<link
type="text/css"
rel="stylesheet"
href="color.css"
/>
<atlas:ScriptManager
runat="server"
/>
<script
src="LapService.asmx/js"
type="text/javascript"></script>
<script
language="javascript"
type="text/javascript">
function
Button1_onclick() {
var text1 =
document.getElementById("Text1");
LapService.MatchProcesses(text1.value,
onSearchComplete);
}
function
onSearchComplete(results) {
var searchResults =
document.getElementById("searchResults");
searchResults.control.set_data(results);
}
</script>
</head>
<body>
<form
id="form1"
runat="server">
<div
id="logo">
Process
Explorer
</div>
<div
id="header">
Search:
<input
id="Text1"
type="text"
/>
<input
id="Button1"
type="button"
value="Search"
onclick="Button1_onclick();"
/>
</div>
<div
id="content">
<!-- Listview -->
<div
class="left">
<atlas:ListView
id="searchResults"
runat="server"
ItemTemplateControlID="row"
CssClass="listView"
ItemCssClass="item"
AlternatingItemCssClass="alternatingItem">
<Behaviors>
<atlas:DragDropList
runat="server"
DataType="Process"
/>
</Behaviors>
<LayoutTemplate>
<ul
id="ul1"
runat="server">
<li
id="row"
runat="server">
<atlas:Label
id="name"
runat="server">
<Behaviors>
<atlas:DraggableListItem
runat="server"
Handle="name">
<Bindings>
<atlas:Binding
Property="data"
/>
</Bindings>
</atlas:DraggableListItem>
</Behaviors>
<Bindings>
<atlas:Binding
DataPath="Name"
Property="text"
/>
</Bindings>
</atlas:Label>
<atlas:Label
id="Label1"
runat="server"
CssClass="bar">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="style"
PropertyKey="width"
/>
</Bindings>
</atlas:Label>
<atlas:Label
id="Label2"
runat="server">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="text"
/>
</Bindings>
</atlas:Label>MB
</li>
</ul>
</LayoutTemplate>
</atlas:ListView>
</div>
</div>
<!-- Item Details -->
<div
class="right">
<atlas:ItemView
id="details"
runat="server">
<Behaviors>
<atlas:DataSourceDropTarget
runat="server"
Append="false"
AcceptedDataTypes="Process"
/>
</Behaviors>
<ItemTemplate>
<atlas:Label
runat="server"
CssClass="name">
<Bindings>
<atlas:Binding
DataPath="Name"
Property="text"
/>
</Bindings>
</atlas:Label>
<span
class="detailRow">
Working set:
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataPath="WorkingSetInMB"
Property="text"
/>
</Bindings>
</atlas:Label>
</span>
<span
class="detailRow">
Thread count:
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataPath="ThreadCount"
Property="text"
/>
</Bindings>
</atlas:Label>
</span>
<!-- Virtual Earth Control -->
<atlas:VirtualEarthMap
id="map"
runat="server"
ZoomLevel="17"
Latitude="34.042653"
Longitude="-118.269779"
PushpinImageUrl="~/TravelImages/pushpin.gif"
PushpinCssClass="pushpin"
PushpinImageWidth="12"
PushpinImageHeight="20"
PushpinActivation="Click"
PopupPositioningMode="TopLeft"
MapStyle="Hybrid">
<PopupTemplate>
<atlas:Label
runat="server">
<Bindings>
<atlas:Binding
DataContext="details"
DataPath="dataContext.Name"
Property="text"
/>
</Bindings>
</atlas:Label>
</PopupTemplate>
<Pushpins>
<atlas:Pushpin
Value="pp"
Latitude="34.042653"
Longitude="-118.269779"
/>
</Pushpins>
</atlas:VirtualEarthMap>
</ItemTemplate>
</atlas:ItemView>
</div>
</div>
</form>
</body>
</html>
Step 6: Showing it on a Mac
The final step we showed was switching to a Mac running
Safari and hitting the same page above using it. Safari browsers get the exact same user experience with
full Ajax support.
Hopefully this provides a rough sense of what the demo
was like.
- Scott