You are on page 1of 157

Jit N’ Run

The Best of
Simple Talk - ASP.NET
Vol.1
ISBN: 978-1-906434-03-8
Shelving: Development/Computer Science
www.simpletalkpublishing.com
In association with
The Best of Simple Talk
i

Jlt N' Run: velume 1
1he Best ef $lmple-1alk - A$P.N£1














by ban Wahlln, bamen Armstrene, Galdar Maedanurev, Jenas
$tawskl, Chrls Ullman, banlel Penred, Jehn Papa, Jesse Llberty,
Alex Rerevltz and Jehn Bewer
lirst published 2008 bv Simple-1alk Publishing
ii


(opvright Dan \ahlin. Damon Armstrong. Gaidar Magdanuro·. Jonas Stawski. (hris Ullman. Daniel Penrod. John Papa. Jesse
Libertv. Alex loro·itz and John Bower 2008

ISBN 9¯8-1-906434-03-8

1he right oí Dan \ahlin. Damon Armstrong. Gaidar Magdanuro·. Jonas Stawski. (hris Ullman. Daniel Penrod. John Papa.
Jesse Libertv. Alex loro·itz and John Bower to be identiíied as the author oí this work has been asserted bv him in accordance
with the (opvright. Designs and Patents Act 1988

All rights reser·ed. No part oí this publication mav be reproduced. stored or introduced into a retrie·al svstem. or transmitted. in anv íorm. or bv
anv means electronic. mechanical. photocopving. recording or otherwise, without the prior written consent oí the publisher. Anv person who does
anv unauthorised act in relation to this publication mav be liable to criminal prosecution and ci·il claims íor damages.

1his book is sold subject to the condition that it shall not. bv wav oí trade or otherwise. be lent. re-sold. hired out. or otherwise circulated without
the publisher`s prior consent in anv íorm other than which it is published and without a similar condition including this condition being imposed
on the subsequent publisher.


1vpeset bv Andrew (larke
iii

CUN1£N1$
(ontents .............................................................................................................................................3
About the Authors............................................................................................................................8
acknowledgements......................................................................................................................... 10
Introduction.................................................................................................................................... 10
ASP.NL1 Master Pages 1ips and 1ricks.................................................................................... 11
Using the Master1vpe Directi·e 11
(reating Master Page Base (lasses 12
landling Nested Master Page Design Issues 13
Sharing Master Pages across IIS Applications 14
(onclusion 1¯
\eb Parts in ASP.NL1 2.0........................................................................................................... 18
A quick reíresher on pro·iders 19
1he \eb Parts pro·ider deíinition: PersonalisationPro·ider 20
Storing multiple pages to a single data store 21
(reating the MultiPageSqlPersonalizationPro·ider class 21
(oníiguring vour new pro·ider in the web.coníig 24
(oníiguring the \eb Parts manager to use a speciíic pro·ider coníiguration 25
Using the demo application 25
(onclusion............................................................................................................................ 26
Implementing \aiting Pages in ASP.NL1 ................................................................................ 2¯
Architecture oí the waiting page solution ....................................................................... 2¯
Solutions................................................................................................................................ 2¯
1he simplest solution 2¯
\aiting íor more than one process 30
Returning custom data objects írom the asvnchronous processes 35
Adding Ajax íeatures 39
(onclusion............................................................................................................................ 40
1oken Replacement in ASP.NL1................................................................................................ 41
Basic token replacement concepts .................................................................................... 41
1oken ´ String Replacement in ASP.NL1....................................................................... 42
Inline Script 42
1he String.lormat Method 42
1he String.Replace and StringBuilder.Replace Methods 43
Acquiring All ASP.NL1 Page (ontent............................................................................. 44
\riting the Page Source to StringBuilder 45
Global Replacements 45

Page Speciíic Replacements 46
(hecking out the demo application .................................................................................. 46
(onclusion ............................................................................................................................ 46
Regular Lxpression Based 1oken Replacement in ASP.NL1................................................. 48
\hat are regular expressions·............................................................................................ 48
Solution o·er·iew ................................................................................................................ 49
Matches and captures .......................................................................................................... 49
Regular expression concepts used in the solution.......................................................... 51
Backslash `, modiíier 51
Grouping constructs 51
(haracter sets 52
Ouantiíiers 52
Breaking down the token íunction regular expression pattern..................................... 53
Adding regular expressions to the 1okenReplacementPage class ................................ 55
Svstem.1ext.RegularLxpressions namespace 56
Static Regex ·ariable 56
RunRegularLxpressionReplacements method 5¯
(reateParamList method 58
Updating the Render method 58
(hecking out the Demo Application................................................................................ 59
(onclusion ............................................................................................................................ 59
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 ......................................................... 60
\hv use URL rewriting·..................................................................................................... 60
Usabilitv 60
Maintainabilitv 60
Nati·e URL mapping in ASP.NL1 2.0............................................................................. 60
1he URL rewriting module................................................................................................ 61
landling the coníiguration section................................................................................... 63
Using parameters írom rewritten URL............................................................................. 64
Rewriting URLs.................................................................................................................... 65
Registering RewriteModule in web.coníig........................................................................ 68
Using RewriteModule 68
IIS (oníiguration: using RewriteModule with extensions other than .aspx............... 69
(onclusions........................................................................................................................... ¯4
1ake Row-Le·el (ontrol oí \our GridView............................................................................. ¯5
Manipulate the GridView control in ASP.NL1 to displav vour data the right wav .. ¯5
1aking ad·antage oí the GridView e·ents and properties ¯5
1he RowDataBound is vour íriend. ¯6
·

(onclusion 82
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions...................................................... 83
Installing the ASP.NL1 AJAX extensions 83
Adding AJAX íunctionalitv into ASP.NL1 \eb lorms 84
Displaving progress 85
Using triggers 88
(onclusion 93
(alling (ross Domain \eb Ser·ices in AJAX.......................................................................... 94
1he XMLlttpRequest object............................................................................................ 94
1he currencv con·erter example....................................................................................... 94
Internet Lxplorer workaround .......................................................................................... 96
Application proxies.............................................................................................................. 9¯
llash - (rossDomain.xml 9¯
Ser·er-side proxv 9¯
cURL proxv 98
Dvnamic Script 1ag hack.................................................................................................... 99
luture directions................................................................................................................ 101
llashXMLlttpRequest 101
(ontextAgnosticXMLlttpRequest 101
JSONRequest 102
(onclusion.......................................................................................................................... 102
Using \ebSer·ices with ASP.NL1........................................................................................... 103
Getting started.................................................................................................................... 104
Setting up the en·ironment 104
Setting up web.coníig 104
Lxploring the \indowsLi·eSearch class ....................................................................... 105
Items to consider 105
1he Search Method 106
1he \eb (lient 10¯
1he ObjectDataSource 108
AJAX AJAX AJAX............................................................................................................ 109
1he AJAX(ontrol1oolKit and the Auto(omplete íunction 109
Virtual Larth API .............................................................................................................. 110
Summarv ............................................................................................................................. 110
Gathering RSS leeds using Visual Studio and RSS.NL1..................................................... 111
Application o·er·iew 111
Setting up the database 111
·i
Gathering the íeed list 113
Polling the íeeds íor posts 114
Inserting the posts 115
(reating an installer 115
Install the \indows Ser·ice 116
Debugging the Ser·ice 116
\rapping Up 11¯
Getting Started with XAML....................................................................................................... 118
(reating .NL1 3 Applications with XAML .................................................................. 118
Getting Started \ith XAML 118
(ore XAML Llements 118
1he PelotonShack Project 118
(reating the \eb Application 119
(reating the Detail View 124
Creating the Reflection 125
Let there be Sil·erlight ................................................................................................................ 12¯
li-Oh Sil·erlight ................................................................................................................ 12¯
Sil·erlight \ears Ahead...................................................................................................... 12¯
leading towards the Sil·erlight ....................................................................................... 128
\our íirst Sil·erlight project lello \orld!,................................................................... 128
Sil·erlight-Speed Loop................................................................................................................ 133
(reating a ligh-speed Loop............................................................................................ 133
Adding objects at run-time............................................................................................... 133
1. Getting Started 134
2. (reating an Image object at run-time 134
3. (reating a Storvboard at run-time 135
4. (reating \our ligh-Speed Loop 13¯
Lxtending vour application: lrames per second without Ja·aScript,....................... 138
Summarv.............................................................................................................................. 141
Sil·erlight Skinnable User Interíaces ........................................................................................ 143
1. Getting Started 143
2. (reating a button in Lxpression Blend 145
3. (reating and Reading (ookies using ASP 149
4. (reating and populating lorm elements using ASP 150
5. (reating our User (ontrol button object at run-time 150
6. (hanging parameters íor our UI object 151
¯. Passing data írom the Document to Sil·erlight 153
Summarv.............................................................................................................................. 155
·ii

Index.............................................................................................................................................. 156

·iii
ABUU1 1R£ AU1RUR$
John Bower
John Bower spent six vears as Lead \eb Designer at lreshLgg beíore getting an MA in 3D
(omputer Animation at the National (entre oí (omputer Animation. UK. le is now
running DeepSpaceObjects consultancv. specialising in llash De·elopment and has
contributed to a number oí celebritv and business websites.
Daniel Penrod
Daniel works as an Application De·elopment Specialist íor Goodvear 1ire & Rubber
(ompanv. le primarilv uses: Ja·a. (4.NL1 and occasionallv Rubv depending on which tvpe
oí ser·er he is messing with. le enjovs spending time with his íamilv e·en ií the majoritv oí
that time is spent sleeping.
Damon Armstrong
Damon Armstrong is a senior architect with (ogent (ompanv in Dallas. 1exas. and author oí
Pro ASP.NL1 2.0 \ebsite Programming. le specializes in Microsoít technologies with a
íocus on web application design using ASP.NL1. \hen not staving up all night coding. he
can be íound plaving disc golí. soítball. working on something íor (arrollton \oung Liíe. or
reco·ering írom staving up all night coding.
Jonas Stawski
Jonas Stawksi works as a consultant íor ASPSOl1. Inc www.aspsoít.com,. Jonas is a
Microsoít Most Valuable Proíessional MVP, in Visual (4 and Microsoít (ertiíied
Application De·eloper M(AD, with o·er 5 vears oí experience building and architecting
.NL1 applications and o·er ¯ vears oí proíessional experience using Microsoít de·elopment
technologies such as ASP. Visual Basic. and SOL Ser·er. Jonas has worked in se·eral markets
such as Insurance. Real Lstate. Leasing. Medical. Restaurant Management. among others. \ou
can ·isit his blog at www.jstawski.com.
Gaidar Magdanurov
Gaidar. an MVP íor Visual Basic. li·es in Moscow. lis primarv job is scientiíic research in
INLOS RAS. Gaidar also works as a soítware de·eloper´consultant and is editor-in-chieí oí
the VBStreets web site. dedicated to Visual Basic and Microsoít .NL1 technologies. An acti·e
member oí the Russian GotDotNet communitv. Gaidar also enjovs speaking at user-group
meetings. In his spare time. Gaidar likes plaving the guitar. going to the theater. walking in the
park and ·isiting íriends.
Dan Wahlin
Dan \ahlin Microsoít Most Valuable Proíessional íor ASP.NL1 and XML \eb Ser·ices, is
a .NL1 de·elopment instructor at Interíace 1echnical 1raining http:´´www.interíace-
1raining.com,. Dan íounded the XML íor ASP.NL1 De·elopers \eb site www.XMLíor-
ASP.NL1,. which íocuses on using XML. ADO.NL1 and \eb Ser·ices in Microsoít`s .NL1
platíorm. le`s also on the INL1A Speaker's Bureau and speaks at se·eral coníerences. Dan
has co-authored´authored se·eral diííerent books on .NL1 including ASP.NL1 2.0 MVP
lacks and XML íor ASP.NL1 De·elopers Sams,. \hen he`s not writing code. articles or
books. Dan enjovs writing and recording music and plaving golí and basketball with his wiíe
and kids. Dan blogs at http:´´weblogs.asp.net´dwahlin and http:´´blogs.interíacett.com´dan-
wahlins-blog.
John Papa
John Papa. Senior .NL1 (onsultant at ASPSOFT. is a Microsoft MVP [C#]. M(SD.NL1. and
an author oí se·eral data access technologv books. John has o·er 10 vears' experience in
de·eloping Microsoít technologies as an architect. consultant. and a trainer. le is the author
oí the Data Points column in MSDN Magazine and can oíten be íound speaking at user
ix

groups. MSDN \eb (asts. and industrv coníerences such as VSLi·e and De·(onnections.
John is a baseball íanatic who spends most oí his summer nights rooting íor the \ankees
with his íamilv and his íaithíul dog. Kadi. \ou can contact John at johnpapa.net.
Jesse Liberty
Jesse Libertv. Microsoít .NL1 MVP. is the author oí O'Reillv Media's Programming
ASP.NL1. Programming (4. the íorthcoming Learning ASP.NL1´AJAX and o·er a dozen
other books on programming. le is president oí Libertv Associates. Inc where he pro·ides
contract programming. and consulting
Alex Horovitz
Alex loro·itz spent time at both NeX1 (omputer and Apple in the 1990s. (urrentlv he
pro·ides soítware engineering leadership and programming to clients seeking to de·elop
enterprise applications le·eraging the Model-View-(ontroller design pattern and re-usable
lrameworks.
Chris Ullman,
(hris Ullman is a íreelance web de·eloper and technical author who has spent manv vears
stewing in ASP´ASP.NL1. like a teabag leít too long in the pot. (oming írom a (omputer
Science background. he gra·itated towards MS technologies during the summer oí ASP
199¯,. Since then he has written on o·er 25 books. most notablv as lead author íor \rox's
best-selling Beginning ASP´ASP.NL1 1.x´2 series. Aíter lea·ing \rox as a íull time
emplovee in August 2001. he branched out into VB.NL1´(4 programming and ASP.NL1
de·elopment and started his own business (UASP (onsulting Ltd. in April 2003. le
maintains a ·arietv oí sites írom http:´´www.cuasp.co.co.uk. his "work" site. to
http:´´www.atomicwise.com. a selection oí his writings on music and art.
x
ACKNUWL£bG£M£N1$
Grateíul thanks are here gi·en to all those who helped in the production oí this Lbook.
including (laire Brooking. Anna Larjomaa. Simon Galbraith. Laila Lotíi and 1onv Da·is at
Red-Gate. \e`d also like to thank the authors. who all submitted cheeríullv to the editing
process that had their contributions altered to coníorm with Simple-1alk`s 'house stvle'.
lN1RUbUC1lUN
1his Lbook represents some oí the best oí Simple-1alk`s .ASP.NL1. Articles. 1hese articles
are written to appeal to a wide range oí readership. Simple-1alk has readers who work
proíessionallv as De·elopers. Database Administrators. testers. 1echnical authors. Architects.
I1 managers. 1he articles are designed to iníorm without patronising or talking down` to the
less technical oí the readers. Simple-1alk alwavs likes to take a slightlv diííerent approach to
Internet-based I1 publishing and we are not aíraid on introducing humor. or in going out oí
our wav to explain highlv esoteric subjects.
Simple-1alk is an online I1 magazine that is designed íor those who are proíessionallv
in·ol·ed in soítware de·elopment and who work with Microsoít 1echnologies. the most
important to us being SOL Ser·er and .NL1. Our readers are those who ha·e bought. or
shown an interest in. Red-Gate tools. and the magazine is used. not onlv to pro·ide general
technical content. but also to keep our readers abreast oí anv important changes or
de·elopments at Red-Gate. \e are independent oí Microsoít. but we are. oí course. Red-
Gate`s house` magazine. so are unapologetic íor occasionallv íeaturing Red-Gate products
amongst Microsoít`s and others. where it is rele·ant.




ASP.NL1 Master Pages 1ips and 1ricks 11


A$P.N£1 MA$1£R PAG£$ 1lP$ ANb 1RlCK$
14 June 2007
by Dan Wahlin
\ith the release oí ASP.NL1 2.0. de·elopers were gi·en a simple and eííecti·e wav to applv a
consistent lavout across multiple pages in a website. Bv creating a íile with a .master extension
that deíined a website's o·erall lavout template and reíerencing it with the Page directi·e's
MasterPageFile attribute. website de·elopment and maintenance took a step íorward in
the direction oí greater producti·itv.
Master pages ha·e been around íor o·er one and a halí vears. so I won't co·er the
íundamentals oí creating and using them. as manv tutorials and books ha·e alreadv been
written about the topic. Instead. I'll íocus on a íew tips and tricks that can be applied when
using master pages. 1o start. let's examine how the MasterType directi·e can be used to
reíerence master page controls in a stronglv-tvped manner írom a content page.
Uslne the Master1ype blrectlve
Pages that reíerence controls in a master page. such as a Label in the header. or Menu on the
leít or right oí a website template. tvpicallv do so bv using the Page class's Master propertv
along with the FindControl() method. as shown in ligure 1.
Label lbl = this.Master.Page.FindControl("lblHeader") as Label;
if (lbl != null)
{
lbl.Text = "Welcome from the content page!";
}
Iigure J: controls in a master page can be accessed bv using the Master propertv combined
with the FindControl() method.
\hile this approach certainlv works. anv misspellings in the quotes will not be caught bv the
compiler. resulting in a runtime error or a null object reíerence being returned. lortunatelv. a
stronglv-tvped solution is a·ailable that doesn't in·ol·e casting the Master propertv to the
base class oí the master page in order to access its members keep in mind that anv ser·er
controls deíined in the master page won't be accessible e·en aíter a cast is períormed.
because thev're marked as protected bv deíault,.
In cases where a control deíined in a master page needs to be exposed to one or more
content pages in a stronglv-tvped manner. a public propertv with a get block can be added
into the master page class as shown in ligure 2. 1he get block returns a Label control
instance named lblHeader.
public Label HeaderLabel
{
get { return lblHeader; }
}
Iigure 2: exposing a Label control in a master page through a public propertv.
A content page can reíerence members deíined in the custom master page class bv adding the
MasterType directi·e immediatelv under the Page directi·e:
12 bv Dan \ahlin
<%@ MasterType VirtualPath="~/Templates/WebsiteMasterPage.master" %>
1his causes the ASP.NL1 compiler to use the custom master page class íor the tvpe oí the
Page class's Master propertv as opposed to the deíault MasterPage class located in the
Svstem.\eb.UI namespace. As a result. the public propertv deíined in the master page can be
directlv accessed írom the content page in a stronglv-tvped manner. as shown in ligure 3.
protected void Page_Load(object sender, EventArgs e)
{
this.Master.HeaderLabel.Text = "Label updated using MasterType " +
"directive with VirtualPath attribute.";
}
Iigure 3: accessing a master page's public propertv írom a content page bv using the
MasterType directi·e.
Using the MasterType propertv not onlv results in less code being written across multiple
content pages. but also leads to better períormance and eliminates the need to pass quoted
·alues to FindControl().
Creatlne Master Paee Base Classes
De·elopers who need to dvnamicallv change master pages on the ílv during the Page's
PreInit e·ent will quicklv disco·er that using the MasterType. along with the
VirtualPath attribute. will not work. 1his is because the VirtualPath ·alue is 'hard
coded' into the content page. lowe·er. another solution exists that can be used in situations
where multiple master pages are in plav.
In cases where the same public propertv must be deíined in multiple master pages such as a
Label control in a website header,. a base master page class can be created that deri·es írom
MasterPage. as shown in ligure 4. 1his class can be added in the App_Code íolder.
public abstract class BaseMasterPage : MasterPage
{
public abstract Label HeaderLabel
{
get;
}
}
Iigure 4: creating a custom master page íile class with a single public abstract propertv.
Bv deíining the BaseMasterPage class as abstract. it can't be created directlv and can onlv
ser·e as the base íor another class. Bv deíining the HeaderLabel propertv as abstract. master
pages that deri·e írom BaseMasterPage must pro·ide an implementation íor the propertv.
ligure 5 shows an example oí deri·ing a master page class írom BaseMasterPage and
implementing the abstract HeaderLabel propertv.
public partial class Templates_InheritedMasterPage : BaseMasterPage
{
public override Label HeaderLabel
{
get { return lblHeader; }
}


protected void Page_Load(object sender, EventArgs e)
{
ASP.NL1 Master Pages 1ips and 1ricks 13

//Provide default text in case content page doesn't set any
if (String.IsNullOrEmpty(lblHeader.Text))
{
this.lblHeader.Text = DateTime.Now.ToLongDateString();
}
}
}
Iigure 5: deri·ing írom BaseMasterPage and implementing an abstract propertv.
Pages that need to access the HeaderLabel propertv. but don't want to reíerence a speciíic
master page using the MasterType's VirtualPath attribute. can use the TypeName
attribute instead. as shown next:
<%@ MasterType TypeName="BaseMasterPage" %>
1he compiler will applv the class deíined bv the TypeName attribute to the Page class's
Master propertv allowing stronglv-tvped access to the HeaderLabel propertv írom a
content page. as shown earlier in ligure 3. 1he downside oí this approach is that anv custom
controls deíined in a concrete master page class won't be accessible through Intellisense¹
and will ha·e to be accessed using FindControl(). lowe·er. anv master page that deri·es
írom BaseMasterPage will expose a HeaderLabel propertv. allowing master pages to be
dvnamicallv loaded in PreInit and used. 1his technique can oí course be used in more
ad·anced scenarios where multiple controls need to be exposed to content pages.
Randllne Nested Master Paee beslen lssues
Master pages can be nested inside oí other master pages in cases where an o·erall site's lavout
template needs to contain a child template see the sample code íor an example oí nesting
master pages,. \hile nesting master pages is useíul in some situations. it presents a problem
when trving to use the Visual Studio .NL1 2005 design suríace to drag and drop controls
onto a content page. 1his problem is resol·ed in the next release oí VS.NL1 currentlv called
Orcas,.
1here are a íew diííerent wavs to get around the nested master page design-time issue. One
potential solution is to temporarilv change the MasterPageFile attribute's ·alue to emptv
strings on the Page directi·e. Although vou won't be able to see how the lavout template
deíined in the master page looks when combined with the content page. vou'll be able to drag
and drop controls onto the content page while in design ·iew. lowe·er. vou'll ha·e to
remember to update the MasterPageFile attribute with the proper master page íile path
beíore mo·ing the page to test or production en·ironments.
Another solution is to le·erage a lesser known aspect oí the Page directi·e. (ustom
properties deíined in an ASP.NL1's code-behind class can be reíerenced in the Page directi·e
as attributes I íirst learned about this trick írom Microsoít's Scott Guthrie,. 1his íeature can
be used to pro·ide a run-time reíerence to a master page and get around the VS.NL1 nested
master page designer issue. as no master page is deíined until the page is actuallv run.
ligure 6 shows a base class named BasePage that deri·es írom Svstem.\eb.UI.Page and
deíines a RuntimeMasterPageFile propertv. BasePage o·errides the Page's PreInit
e·ent and dvnamicallv assigns the MasterPageFile propertv to the ·alue contained in the
RunTimeMasterPageFile propertv.
public class BasePage : System.Web.UI.Page {
private string _RuntimeMasterPageFile;
public string RuntimeMasterPageFile {
get {
14 bv Dan \ahlin
return _RuntimeMasterPageFile;
}
set {
_RuntimeMasterPageFile = value;
}
}
protected override void OnPreInit(EventArgs e) {
if (!String.IsNullOrEmpty(RuntimeMasterPageFile)) {
this.MasterPageFile = RuntimeMasterPageFile;
}
base.OnPreInit(e);
}
}
Iigure 6: creating a base class that deri·es írom Page and deíines a single propertv named
RuntimeMasterPageFile. 1his propertv is used to speciív the master page íile that should
be used at runtime.
A page that deri·es írom BasePage can then assign the MasterPageFile attribute oí the
Page directi·e to emptv strings to a·oid the designer issue mentioned earlier, but then deíine
the master page íile that should be used at runtime bv adding a RuntimeMasterPageFile
attribute as shown next:
<%@ Page AutoEventWireup="true"
CodeFile="WorkingWithNestedMasterAndBasePage.aspx.cs"
CodeFileBaseClass="BasePage"
Inherits="WorkingWithNestedMasterAndBasePage" Language="C#"
MasterPageFile=""
RuntimeMasterPageFile="~/Templates/NestedMasterPage.master"
Title="Nested Master Page Demo" %>
Deíining the RuntimeMasterPageFile attribute will cause the associated propertv in
BasePage to be assigned a ·alue which is then used during PreInit to assign a ·alue to the
MasterPageFile propertv. Although e·erv page that reíerences a nested master page has to
deri·e írom BasePage íor this trick to work. it's one potential solution to nested master
pages that pre·ents ha·ing to temporarilv remo·e the MasterPageFile attribute ·alue to
edit a content page in design ·iew.
$harlne Master Paees acress ll$ Appllcatlens
1he MasterPage class a·ailable in ASP.NL1 2.0 deri·es írom UserControl and just like
user controls. master pages can't be shared across IIS applications. 1here are a íew diííerent
solutions that ha·e been proposed. such as setting up ·irtual directories in each IIS
application that point to the same phvsical íolder. but there is a wav to share master pages
across applications with a little work on vour part without resorting to duplicating ·irtual
directories across multiple websites. Bv le·eraging the VS.NL1 2005 Publish \eb Site tool it's
possible to create an assemblv that contains all oí the master page l1ML code and (4 or
VB.NL1 code. gi·e the assemblv a strong name. and install it into the Global Assemblv
(ache GA(,.
I consider this trick more oí a hack. but it's something vou can trv out ií´when the situation
requires it. 1here are se·eral steps in·ol·ed. so a step-bv-step approach íollows. as well as
issues to watch out íor when períorming the steps. I originallv wrote about this some time
ago on mv blog at http:´´weblogs.asp.net´dwahlin.
1. 1. (reate an emptv \ebsite in VS.NL1 2005. Delete e·ervthing in it including
App_Data. Deíault.aspx. and web.coníig ií it exists,.
2. 2. Add a master page into the website. A simple master page íile is shown in ligure ¯.
ASP.NL1 Master Pages 1ips and 1ricks 15

<%@ Master Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Header
<br />
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
<br />
Footer
</div>
</form>
</body>
</html>
Iigure 7: a simple master page íile that deíines a ContentPlaceHolder control.
3. 3. Select Build | Publish \ebsite írom the VS.NL1 menu.
4. 4. On the screen that íollows. select a target location. and check all oí the checkboxes
shown in ligure 8. Note that the image shown in ligure 8 reíerences a strong name kev
íile named keyfile.snk that was created using the sn.exe command-line tool that
ships with .NL1. 1his is required in order to install assemblies into the GA(. 1he
íollowing svntax can be used to create the kev íile run it using the Visual Studio .NL1
2005 command prompt,: sn.exe -k keyfile.snk.

Iigure 8: using the Publish \eb Site tool to create an assemblv írom a master page.
5. 5. Aíter the publish operation completes. open the new website in VS.NL1 2005
named MasterDemo in the example abo·e,. \ou should see a new assemblv with a
strange name, in the Bin íolder. 1his assemblv is vour master page in compiled íorm.
16 bv Dan \ahlin
6. 6. Install the assemblv into the GA( using gacutil.exe or drag-and-drop it into
c:\Windows\Assembly using \indows Lxplorer. Once vou'·e done this. delete the
original assemblv as well as the newlv created XML íiles associated with it írom the
website.
¯. ¯. Add a web.coníig íile into the website and add the íollowing within the
<system.web> begin and end tags.
<compilation debug="true">
<assemblies>
<add assembly="App_Web_masterpagebase.master.cdcab7d2, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=cceb8435cfc68486" />
</assemblies>
</compilation>
\ou'll need to change the name oí the assemblv to the name that is generated íor vour
project the one vou added into the GA(, and change the PublicKeyToken to the one vou
see in the GA(. Note that the assemblv attribute ·alue shouldn't wrap at all. I didn't gi·e mv
base master page a ·ersion íor simplicitv. but vou can do so bv applving the |assemblv:
AssemblvVersion"1.0.0.0",| attribute to the master page code-behind class. Note that ií
vou're using the \eb Application Project íeature oí VS.NL1 2005 vou can gi·e vour master
page assemblv a íriendlier name. I'll lea·e that as an exercise íor the reader.
8. Add a master page into the website. but don't create a code-behind page íor itvou can. but
it's not needed in this case since the master page is onlv used to reíerence the one installed in
the GA(,.

9. Remo·e all code within the new master page and add the íollowing at the top. It should be
the onlv code in the page.
<%@ Master Language="C#" Inherits="ASP.masterpagebase_master" %>
Ií vou named the original master page the one created in step 2, diííerentlv. then vou'll need
to change the Inherits ·alue. Use the VS.NL1 object browser to see the name oí the class
within the .dll generated in step 4.
10. (reate a content page that reíerences MasterPage.master the one vou created in the
pre·ious step,. 1he deíault ContentPlaceHolderID is ContentPlaceHolder1. so use
that in the <asp:Content> tag unless vou ga·e the id a diííerent name in step 2.
Aíter completing these steps. anv IIS application can share the same master page used bv
other IIS applications bv placing the emptv MasterPage.master íile into the application
and updating the web.coníig íile to point to the master page assemblv in the GA(. 1he
downíall oí this approach is that vou ha·e to recompile the base master page and put it back
into the GA( each time vou need to make a change and the design time support is lacking.
1here mav be other issues as well that ha·en't been disco·ered vet. so períorm proper testing
beíore assuming this technique will work íor vour particular situation.
In working with more complex master pages vou mav see a 'could not íind string resource'
error come back when using this approach. Ií vou use Reílector
http:´´www.aisto.com´roeder´dotnet, to analvze the code generated when the master page
is compiled into the assemblv. vou'll likelv see a call to a method named
CreateResourceBasedLiteralControl() in the code rather than seeing the actual
l1ML írom the master page being embedded into the assemblv. Ií vou strip out some oí the
whitespace in the l1ML it should eliminate the call to
CreateResourceBasedLiteralControl() that the Publish \eb Site tool added and
compile correctlv. lor example. change the íollowing:
ASP.NL1 Master Pages 1ips and 1ricks 1¯

<td align="left" valign="top" height="45">
<www:HtmlOutput ID="egovHeader" runat="Server"
XmlSource="/XML/SiteLinks.xml"
XsltSource="/XSLT/Header.xslt"
LanguageCookieName="EgovCookie/Language" />
</td>
1o the íollowing all in one line,:
<td align="left" valign="top" height="45"><www:HtmlOutput ID="egovHeader"
runat="Server" XmlSource="/XML/SiteLinks.xml" XsltSource="/XSLT/Header.xslt"
LanguageCookieName="EgovCookie/Language" /></td>
Ií vou trv this technique and continue to get a string resource error. vou'll probablv ha·e to
plav around with vour l1ML in the base master page until no
CreateResourceBasedLiteralControl() calls are made in the generated code. As
mentioned beíore. I would ad·ise using Reílector to take a look.
Cencluslen
Master pages pro·ide a great wav to applv a consistent lavout to an ASP.NL1 2.0 website.
resulting in better producti·itv and reduced maintenance. In this article vou'·e seen ·arious
tips and tricks that can be used with master pages such as accessing master page controls in a
stronglv-tvped manner using the Master1vpe directi·e. creating base master page classes. and
working with nested master pages. \ou'·e also seen one potential technique íor sharing
master pages across IIS applications. Additional samples are a·ailable with this article's
downloadable code.

18 bv Damon Armstrong
W£B PAR1$ lN A$P.N£1 2.0
05 May 2006
by Damon Armstrong
Sharing Web Parts across multiple pages
Most \eb Parts implementations allow users to create a single portal page where thev can
personalize the look. íeel. íunctionalitv. and experience oí their "Mv lome" page in an
application. But what ií vou want to take \eb Parts a step íurther and allow users to deíine a
personalized interíace that applies to a series oí pages in an application· \ouldn't it be useíul
ií vour users could place helpíul tools and components on the peripherv oí vour application
and ha·e those a·ailable on each page thev ·isit instead oí just on their "Mv lome" page·
Absolutelv! 1he question is how do vou do it·
At íirst glance. it mav seem that Master Pages http:´´www.simple-talk.com´
dotnet´asp.net´beginning-asp.net-2.0´, are the solution to the issue. but vou will quicklv run
into a stumbling block: personalization settings íor \eb Parts are stored on a page-bv-page
basis. L·en ií vou deíine all oí the \eb Part logic in a single Master Page. the \eb Parts
personalization settings íor the pages that use the Master Page are stored separatelv írom one
another. pre·enting vou írom applving a single set oí \eb Parts to e·erv page on the site.
1his is illustrated in ligure 01.

ligure 01 - 1he standard personalization pro·ider stores settings on a page-bv-page basis
ligure 1 demonstrates that changes and updates made on PageA.aspx will not appear when
vou ·isit PageB.aspx because each page has its own personalization settings. Our objecti·e is
to get changes on PageA.aspx to appear on PageB.aspx. Page(.aspx. and on whate·er other
\eb Parts in ASP.NL1 2.0 19

pages we so desire. One wav to go about doing this is to set up a mechanism that copies
changes írom one page to another as thev occur - in other words. to svnchronize settings
between the pages. I am íar too lazv íor such an approach because it sounds like a lot oí hard
work and testing. lortunatelv. there is an easier wav. \e are going to create a personalization
provider that stores iníormation írom multiple pages into a single data store. as shown in
ligure 02.

ligure 02 - 1he MultiPageSqlPersonalizationProvider stores settings o·er
multiple pages
1hus. when vou make change to Page..a.p·. it will be reílected on Pageß.a.p· because both
pages store and retrie·e their \eb Parts data írom the same data store. 1o do this vou need to
know a bit about the internals oí the personalization mechanism that stores \eb Part
settings.
A qulck refresher en prevlders
Beíore we get too deep into the personalization pro·ider in ASP.NL1 2.0. it's worth
re·iewing some oí the íundamentals oí pro·iders in general. A pro·ider is an abstraction
mechanism that allows ASP.NL1 to request data without ha·ing to worrv about how or
where that data is stored. A pro·ider is made up oí two parts: a pro·ider deíinition and a
pro·ider implementation.
1he provider definition is normallv an abstract class though it can be an interíace, that
deíines a set oí methods and properties ASP.NL1 mav call to retrie·e data. 1he pro·ider
deíinition itselí does not. howe·er. contain anv data-retrie·al logic. It merelv deíines a
"contract" that stipulates that a call to one oí the pro·ider methods will return data.
20 bv Damon Armstrong
regardless oí the location írom which it is coming. lor anv gi·en pro·ider personalization.
membership. and so on, there is onlv one deíinition.
A provider implementation inherits írom the pro·ider deíinition and implements the data-
retrie·al logic íor a particular data store. As such. there can be anv number oí pro·ider
implementations íor a gi·en pro·ider deíinition. 1his allows vou to create pro·ider
implementations íor anv number oí data storage scenarios: SOL Ser·er. Oracle. MvSOL.
Access. loxPro. an XML lile. a (SV íile. or e·en an Lxcel íile it's possible. but terriblv slow
íor a web app. so please don't actuallv do it unless vou're just showing oíí,. \ou can then
coníigure ASP.NL1 to use a particular pro·ider implementation íor whiche·er data store vou
want ASP.NL1 to use to store vour data. ligure 03 depicts a pro·ider deíinition and its
·arious pro·ider implementations.

ligure 03 - Pro·ider Deíinition and Pro·ider Implementations
1he Web Parts prevlder deflnltlen: PersenallsatlenPrevlder
ASP.NL1 uses the PersonalizationProvider abstract class as the pro·ider deíinition
íor \eb Parts settings. 1able 1 contains a brieí listing oí the abstract methods the class
exposes to deíine how ASP.NL1 sa·es and retrie·es the appropriate settings íor the \eb
Parts on a particular page.
Method Returns Parameters Description
lindState Personaliz
ationState
Inío(olle
ction
PersonalizationScope scope.
PersonalizationStateOuerv
query.
int pageIndex.
int pageSize.
out int totalRecords
Returns a collection oí
PersonalizationStateInío objects
based on the querv and scope
parameters. 1his allows ASP.NL1
to retrie·e personalization settings
íor a gi·en page.
Get(ountOíSt
ate
int PersonalizationScope scope.
PersonalizationStateOuerv
query
Returns the number oí items in the
data store íor the gi·en querv and
scope.
LoadPersonaliz
ationBlobs
·oid \ebPartManager
webPartManager.
string path.
string userName.
reí bvte|| sharedDataBlob.
reí bvte|| userDataBlob
Loads raw data írom the data
source and con·erts it into a
PersonalizationStateInío object.
1his. in essence. con·erts the data
írom a data-source speciíic íormat
into a non-data-source speciíic
íormat.
ResetState int PersonalizationScope scope.
string|| paths.
string|| usernames
Resets all personalization data
based on the gi·en parameters
ResetUserState int string path.
Date1ime
userInacti·eSinceDate
Resets all \eb Part personalization
data based on the gi·en parameters
\eb Parts in ASP.NL1 2.0 21

Sa·ePersonaliz
ationBlob
·oid \ebPartManager
webPartManager.
string path.
string userName.
bvte|| dataBlob
Sa·es personalization data
1able 1 - Abstract Methods in the PersonalizationPro·ider Abstract (lass
Please note the bolded parameters in 1able 1 because thev will be oí importance later on in
the article.
1he Web Parts provider implementation: SqlPersonalizationProvider
Naturallv. a pro·ider deíinition bv itselí is not oí much use unless there is a pro·ider
implementation to accompanv it. lortunatelv. ASP.NL1 2.0 ships with a pro·ider íor SOL
Ser·er called the SqlPersonalizationProvider class. which resides in the
System.Web.UI.WebControls.WebParts namespace. 1his class contains a SOL Ser·er-
speciíic implementation oí the abstract methods írom the PersonalizationPro·ider class. As
vou can imagine. this means that the class contains a lot oí code to connect to a SOL Ser·er
database. to querv the database íor pertinent records. and to read the data returned bv SOL
Ser·er.
1ruth be told. we are not reallv all that concerned about the SqlPersonalization-
Provider class speciíicallv. It's just that the technique I am going to show vou íor storing
multiple pages to a single data stores requires that we begin with a íullv íunctional Person-
alizationPro·ider implementation. In this article I'll be using the PersonalizationPro·ider íor
SOL Ser·er. Ií vou happen to be using Oracle. MvSOL. or some other pro·ider. then vou can
still use this technique against that pro·ider implementation.
$terlne multlple paees te a slnele data stere
\e know that the deíault SqlPersonalizationProvider stores \eb Parts settings on a
page-bv-page basis. It thereíore stands to reason that each page needs a unique identiíier so
that the personalization pro·ider can store and retrie·e settings íor the page. And what is the
easiest wav to uniquelv identiív a page· Bv the page path! Ií vou look back at the bolded
parameters in 1able 1 vou will notice there are a number oí parameters named path. \hen
the pro·ider is sa·ing or retrie·ing iníormation íor PageA.aspx. it passes as the path
parameter ·alue a string ·alue containing "PageA.aspx" and ií PageA.aspx is in a directorv
called Pages. then it would pass "MvDirectorv´PageA.aspx" as the parameter ·alue,.
lortunatelv. this path ·alue is simplv a unique kev. so it does not ha·e to be a ·alid path in the
web application. 1hus. ií vou want to store settings íor use on multiple pages. make sure thev
all use the same unique kev.
1he easiest wav to do this is to inherit írom an existing pro·ider implementation and o·erride
all oí the methods that require a page path. Inside the o·erridden method. vou just call down
to the base-class method to do the actual work. but vou pass vour own shared kev into the
method instead oí the page path. 1hus. anv page that uses vour pro·ider will share \eb Parts
settings with anv other page that uses vour pro·ider. and vou don't ha·e to worrv about the
nastv implementation details oí the pro·ider.
Creatlne the MultlPaee$qlPersenallzatlenPrevlder class
(ode listing 1 pro·ides the complete deíinition íor the MultiPageSqlPerson-
alizationProivder class. \ou will see that it is a ·erv simple class because it simplv calls
down to the base class when it needs to do anv real work. Also take note oí anv bold areas
because thev show the important portions oí the code. and will be discussed in more detail
aíter the code listing:
22 bv Damon Armstrong
using System;
using System.Web.UI.WebControls.WebParts;

namespace Providers
{
public class MultiPageSqlPersonalizationProvider :
SqlPersonalizationProvider
{
private string _groupName = “PageGroup”;
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
}
}

public override PersonalizationStateInfoCollection FindState
(PersonalizationScope scope,PersonalizationStateQuery query,
int pageIndex, int pageSize, out int totalRecords)
{
if (query.PathToMatch != String.Empty)
{
query.PathToMatch = GroupName;
}
return base.FindState(scope, query, pageIndex, pageSize, out
totalRecords);
}

public override int GetCountOfState(PersonalizationScope scope,
PersonalizationStateQuery query)
{
if (query.PathToMatch != String.Empty)
{
query.PathToMatch = GroupName;
}
return base.GetCountOfState(scope, query);
}


protected override void LoadPersonalizationBlobs(WebPartManager
webPartManager,string path, string userName, ref byte[]
sharedDataBlob, ref byte[] userDataBlob)
{
base.LoadPersonalizationBlobs(webPartManager, GroupName, userName,
ref sharedDataBlob, ref userDataBlob);
}


protected override void ResetPersonalizationBlob(WebPartManager
webPartManager,string path, string userName)
{
base.ResetPersonalizationBlob(webPartManager, GroupName, userName);
}


public override int ResetState(PersonalizationScope scope,
string[] paths,string[] usernames)
{
return base.ResetState(scope, new string[] { GroupName }, usernames);
}

public override int ResetUserState(string path,
DateTime userInactiveSinceDate)
{
return base.ResetUserState(GroupName, userInactiveSinceDate);
}

protected override void SavePersonalizationBlob(WebPartManager
webPartManager,string path, string userName, byte[] dataBlob)
{
\eb Parts in ASP.NL1 2.0 23

base.SavePersonalizationBlob(webPartManager, GroupName, userName,
dataBlob);
}

public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection configSettings)
{
GroupName = configSettings[”groupName”];
if (string.IsNullOrEmpty(GroupName)) GroupName = “PageGroup”;
configSettings.Remove(”groupName”);
base.Initialize(name, configSettings);
}
} //class
} //namespace
Let's begin with the GroupName propertv that appears at the beginning oí the class.
Remember that we need a unique identiíier to use as the kev íor anv pages we want to share
\eb Parts settings. and GroupName stores the ·alue oí that kev. It is a simple string
propertv that uses the _groupName íield to store its ·alue. \ou will see in a moment how
we set the GroupName ·alue when initializing the pro·ider and when coníiguring the
pro·ider in web.coníig.
1hroughout the class vou will notice the GroupName propertv used in place oí the path
parameter. which eííecti·elv o·erwrites the path ·alue with our own unique kev. \ou will see
the GroupName propertv used in three diííerent wavs. 1he most common wav is as it
appears in the LoadPersonalizationBlobs. ResetPersonalizationBlob.
ResetUserState. and SavePersonalizationBlob methods - it simplv takes the
place oí the path propertv.
1he second wav appears in both the FindState and GetCountOfState methods. In
those methods vou will NO1 see a path parameter. but vou will see a parameter named querv.
which is a PersonalizationStateQuery object. One oí the properties oí this object
is the PathToMatch propertv. Ií the PathToMatch ·alue is not an emptv string. vou
want to o·erwrite the ·alue with the ·alue in GroupName.
And vou can see the third wav ií vou look at the ResetState method. where the
GroupName ·alue is passed into the constructor oí a new string arrav. 1his ensures that the
base method onlv operates on the single GroupName ·alue instead oí a series oí actual
paths.
At the ·erv end oí the class is the o·erridden Initialize method. aptlv named since
ASP.NL1 calls the method when initializing the pro·ider. 1he configSettings
parameter on the Initialize method is a NameValueCollection object containing
a series oí settings and their ·alues írom the web.coníig we'll get to the \eb.coníig
momentarilv,. 1o set the GroupName propertv ·alue. vou simplv assign the propertv the
·alue contained in configSettings["groupName"]. Ií the "groupName" attribute is
NO1 set in the web.coníig. then the call to configSettings["groupName"] returns
null. On the next line. ií the ·alue oí the GroupName is null or emptv. the method gi·es the
GroupName propertv a deíault ·alue oí "PageGroup" to ensure there is a unique kev íor the
sa·ing and retrie·al oí \eb Parts settings. It then remo·es the "groupName" attribute name
and ·alue írom the configSettings parameter. \hv. vou ask· \hen vou call the
Initialize method in the base class. it runs a svntax check on the setting names in the
coníigSettings NameValueCollection. Ií the "groupName" attribute name is in the
collection. the call to the Initialize method in the base class throws an exception
because the base class does not expect a "groupName" attribute setting írom the web.coníig.
Once vou remo·e it. howe·er. there is no problem. And vou ha·e a \eb Part
PersonalizationPro·ider that allows vou to share \eb Parts Settings between pages.
24 bv Damon Armstrong
Cenfleurlne yeur new prevlder ln the web.cenfle
Now that vou ha·e a new \eb Part pro·ider class. vou ha·e to setup vour application to use
it. 1his is accomplished in the ·coníiguration· -· ·svstem.web· -··webParts· -·
·personalization· -· ·pro·iders· section oí the web.coníig. 1he íollowing code listing
illustrates how to coníigure a MultiPageSqlPersonlizationProvider named
MyMultiPageProvider:
<configuration>
...
<system.web>
...
<webParts>
<personalization defaultProvider="AspNetSqlPersonalizationProvider">
<providers>
<add connectionStringName="LocalSqlServer" groupName="MyGroupKey"
name="MyMultiPageProvider"
type="Providers.MultiPageSqlPersonalizationProvider" />
</providers>
</personalization>
</webParts>
</system.web>
</configuration>
Let's íocus íirst on the ·add· element shown abo·e. 1here are íour attributes in the ·add·
element. each oí which is brieílv detailed in 1able 2.
Attribute Name Description
connectionStringName
Name oí the connection string in the ·connectionStrings· section that this
pro·ider should use to connect to a SOL Ser·er database.
groupName
Unique kev under which all \eb Parts settings will be stored. 1his ·alue is
placed in the GroupName propertv when the
MultiPageSqlPersonalizationPro·ider is initialized
name
Unique name that identiíies the pro·ider coníiguration settings. \ou use this
name when telling the \eb Part Manager which pro·ider coníiguration to
use. or when coníiguring a deíault pro·ider coníiguration in the \eb.coníig
tvpe 1vpe name oí the \eb Part Personalization Pro·ider implementation class
1able 2 - Add Llement Attributes
In the abo·e example. the pro·ider coníiguration is named MyMultiPageProvider. \ou will
see shortlv where to use this name to tell ASP.NL1 to use this pro·ider coníiguration. 1he
connectionStringName is set to use the "LocalSqlServer" connection string. which is
a built-in connection string in ASP.NL1 2.0 that points to the ASPNL1DB.mdf íile in the
APP_DA1A íolder oí vour web application. Ií the ASPNL1DB.mdí íile does not exist. the
SqlPersonlizationProvider automaticallv creates it and initializes anv tables it
needs remember. we are using the SqlPersonalizationProvider as a base class so
we inherit its abilitv to automaticallv create a database íor itselí,. 1he "groupName" attribute
is set to "MvGroupKev". so anv pages that use this pro·ider coníiguration will ha·e their path
iníormation o·erwritten with this ·alue. And lastlv. the tvpe is set to
"Pro·iders.MultiPageSqlPersonalizationPro·ider" because the MultiPageSql-
PersonalizationProvider class resides in the Pro·ider's namespace. Ií vou are
using an external assemblv. vou mav ha·e to qualiív the tvpe name with the assemblv e.g.
"|AssemblvName.| 1vpeName",.
Also notice the defaultProvider attribute in the ·personalization· element oí the
web.coníig. 1his tells ASP.NL1 which pro·ider coníiguration to use as a deíault ií a pro·ider
\eb Parts in ASP.NL1 2.0 25

coníiguration is not explicitlv speciíied in the \eb Part Manager. In the example abo·e. it is
set to AspNetSqlPersonalizationProvider. 1his is the deíault built-in pro·ider
coníiguration íor ASP.NL1 that uses the ASPNL1DB.mdí íile in the APP_DA1A directorv
oí vour web application to store \eb Parts settings. Ií vou onlv want to use the
MyMultiPageProvider bv deíault in vour application. then vou can set it as the deíault
pro·ider:
<configuration>
...
<system.web>
...
<webParts>
<personalization defaultProvider="MyMultiPageProvider">
<providers>
<add connectionStringName="LocalSqlServer" groupName="MyGroupKey"
name="MyMultiPageProvider"
type="Providers.MultiPageSqlPersonalizationProvider" />
</providers>
</personalization>
</webParts>
</system.web>
</configuration>
Ií vou speciív the MyMultiPageProvider as the deíault pro·ider coníiguration. then vou
do not ha·e to explicitlv speciív it as the pro·ider coníiguration in the \eb Part Manager. But
in case vou want to lea·e the deíault as AspNetSqlPersonalizationProvider. I'll
co·er how to setup the \eb Part Manager next.
Cenfleurlne the Web Parts manaeer te use a speclflc prevlder cenfleura-
tlen
L·erv page that contains \eb Parts must ha·e one and onlv one, WebPartManager control
to manage the storage. retrie·al. and interaction oí \eb Parts. \hen vou íirst place a
\ebPartManager on a page or a Master Page. the control deíinition looks similar to this:
<asp:WebPartManager runat="server" ID="wpManager" />
Since there the control is not being explicitlv told which pro·ider coníiguration to use. it uses
the deíault coníiguration speciíied in the web.coníig.
Ií vou want to use a speciíic pro·ider coníiguration. then vou ha·e to tell the control which
pro·ider coníiguration to use. \ou do this bv passing in the unique name oí the pro·ider
coníiguration to the control. as íollows:
<asp:WebPartManager runat="server" ID="wpManager">
<Personalization ProviderName="MyMultiPageProvider" />
</asp:WebPartManager>
Now that vou can coníigure the \ebPartsManager to use the MultiPageSql-
PersonalizationProvider. vou can embed vour \eb Parts logic into a Master Page and
easilv share \eb Parts o·er multiple pages.
Uslne the deme appllcatlen
Open up the demo application a·ailable using the code download link at the top oí the page,
in Visual Studio and run the application. \ou are presented with a login screen. Login using
one oí the user names and passwords listed on the page. or click on the Login link next to the
user name to automaticallv log in. Once vou log in. vou will see two options presented as
26 bv Damon Armstrong
links. 1he íirst link savs "Standard Pro·ider Pages" and demonstrates the "normal" \eb Parts
íunctionalitv. (lick on the link and vou will be taken into the Standard\eb íolder. 1his íolder
has three pages. named PageA.aspx. PageB.aspx. and Page(.aspx. lollow the directions on
the page and add a íew \eb Parts to the page. \hen vou are íinished. go to PageB.aspx.
Notice that vour \eb Parts disappear. Use the back button to return to the page with the two
links.
1he second link savs "Multi-Page Pro·ider Pages" and demonstrates the íunctionalitv oí the
MultiPageSqlPersonalizationPro·ider we ha·e been discussing. (lick on the link and vou will
be taken into the MultiPage\eb íolder. 1his íolder also has three page named PageA.aspx.
PageB.aspx. and Page(.aspx. lollow the directions on the page and add a íew \eb Parts to
the page. \hen vou are íinished. go to PageB.aspx. Notice that vour \eb Parts remain.
(hanges vou make írom anv oí these pages will appear on the other pages.
lere is a quick run down oí the important íiles in the application and what thev contain:
Iile Name Description
MultiPage\ebParts.sln Visual Studio 2005 Solution lile
\eb.coníig (oníiguration settings íor the \eb Parts Personalization
Pro·ider
Deíault.aspx Login screen
StandardPro·ider.master Master Page with \eb Parts controls and logic that uses the
deíault AspNetSqlPersonalizationPro·ider to store \eb
Parts settings
MultiPagePro·ider.master Master Page with \eb Parts controls and logic that uses the
MultiPageSqlPersonalizationPro·ider to store \eb Parts
Setting.
Standard\eb`PageA.aspx
Standard\eb`PageB.aspx
Standard\eb`Page(.aspx
Pages that use the StandardPro·ider.master and demonstrate
the deíault \eb Parts íunctionalitv
MultiPage\eb`PageA.aspx
MultiPage\eb`PageB.aspx
MultiPage\eb`Page(.aspx
Pages that use the MultiPagePro·ider.master and
demonstrate how to share \eb Parts settings between pages.
\ebParts` lolder containing extremelv simple \eb Parts and the \eb
Parts catalog íor use in the demo application.
1able 3 - Demo Application liles and Descriptions
Cencluslen
Now vou ha·e a simple. vet poweríul. solution íor SOL Ser·er that allows users to set up \eb
Parts that span multiple pages inside vour application. Also remember that the technique
works íor other PersonalizationPro·ider implementations as well. so ií vou e·er need to work
Oracle. MvSOL. or another database. vou can setup the same mechanism íairlv quicklv. Good
luck!

Implementing \aiting Pages in ASP.NL1 2¯

lMPL£M£N1lNG WAl1lNG PAG£$ lN A$P.N£1
16 July 2007
by Gaidar Magdanurov
One thing users do not like about \eb-applications is waiting íor some long-running process
to íinish without knowing ií it is reallv working or not. 1here are íew greater írustrations than
looking at the screen and think \hat`s going on·`. while the application períorms some
underco·er operation. 1hat is whv application should pro·ide user with íeedback. showing
that something is happening and. telling the user that he will ha·e to wait just a little bit
longer.
Ií the process is running svnchronouslv vou ha·e no options - the user will ha·e to wait
while page is reloading anvwav. But then an asvnchronous process comes to plav. vou mav like
to work out ·arious solutions íor a better user experience. In this article we will disco·er a
íew wavs to create a waiting page.
Archltecture ef the waltlne paee selutlen
Beíore we start to build a waiting page. we ha·e to decide on architecture oí the solution. 1he
easiest to implement solution is shown at lig. 1.

Iig. J.\aiting page architecture
1he main page starts a new thread and assigns a unique ID to it. then redirects user to the
waiting page. passing ID to the waiting page to enable waiting page to track progress oí the
process running in a thread which was started bv the main page. 1he process submits results
to a special controller object that contains collection oí kev-·alue pairs to identiív results
submitted bv threads with diííerent IDs. 1he waiting page queries the controller to check ií
the process is still running or has alreadv íinished.
$elutlens
Now we are set with the architectural decision and are readv to start building waiting page.
Let`s go írom the easiest solution to the more complex ones.
1he slmplest selutlen
1he simplest solution to implement is which does not require tracking real progress oí
asvnchronous process. thus showing onlv two states oí the process - still running or alreadv
íinished.
At íirst. we should create a controller object that can simplv pro·ide the waiting page with the
state oí the request.
1he controller
using System;
28 bv Gaidar Magdanuro·
using System.Collections;


public static class SimpleProcessCollection
{
private static Hashtable _results = new Hashtable();

public static string GetResult(Guid id)
{
if (_results.Contains(id))
{
return Convert.ToString(_results[id]);
}
else
{
return String.Empty;
}
}

public static void Add(Guid id, string value)
{
_results[id] = value;
}

public static void Remove(Guid id)
{
_results.Remove(id);
}
}
1he main page should assign the asvnchronous process with an unique ID and pass this ID
to the waiting page. then waiting page will querv SimpleProcess(ollection íor the result with
the gi·en ID. 1he process. in turn. should add the result to the SimpleProcess(ollection to
notiív waiting page that the process ended.
1he main page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start"
%>

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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Simple Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button ID="btnStart" runat="server" Text="Start Long-Running
Process"
OnClick="btnStart_Click" />
</div>
</form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;

public partial class Simple_Start : System.Web.UI.Page
Implementing \aiting Pages in ASP.NL1 29

{
protected Guid id;

protected void btnStart_Click(object sender, EventArgs e)
{
// assign an unique ID
id = Guid.NewGuid();
// start a new thread
ThreadStart ts = new ThreadStart(LongRunningProcess);
Thread th = new Thread(ts);
th.Start();
// redirect to waiting page
Response.Redirect("Status.aspx?ID=" + id.ToString());
}

// this is a stub for a asynchronous process
protected void LongRunningProcess()
{
// do nothing actually, but there should be real code
// for instance, there could be a call for a remote web service
Thread.Sleep(9000);
// add result to the controller
SimpleProcessCollection.Add(id, "Some result.");
}
}
1he waiting page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status"
%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Simple Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<p align="center">
<asp:Image ID="ImageStatus" ImageUrl="~/Images/Wait.gif"
runat="server"
/></p>
<h1>
<asp:Label ID="lblStatus" runat="server"
Text="Working... Please
wait..."></asp:Label>
</h1>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Simple_Status : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// chech if the page was called properly
30 bv Gaidar Magdanuro·
if (Request.Params["ID"] != null)
{
Guid id = new Guid(Request.Params["ID"]);
// check if there is a result in the controller
if (SimpleProcessCollection.GetResult(id) == String.Empty)
{
// no result - let's refresh again
Response.AddHeader("Refresh", "3");
}
else
{
// everything's fine, we have the result
lblStatus.Text = "Job is done.";
ImageStatus.Visible = false;
}
}
else
{
Response.Redirect("Start.apsx");
}
}
}
\ou can see that the solution is ·erv simple and required just a dozen lines oí code. As we are
íree to use an animated gií on the waiting page. the user will ha·e íun while waiting íor the
process to complete. 1he waiting page discussed is presented at líig. 2.

Iig. 2. 1he simplest waiting page.
Waltlne fer mere than ene precess
Ií there is more than one process running in the background waiting page should wait íor.
then it is necessarv to implement some kind oí progress bar control and extend the sample
shown abo·e to handle more than one process. 1o do that. we can implement a simple
counter oí processes that are still running as shown in the íollowing code snippet.
1he controller
public static class MultiProcessCollection
{
private static Dictionary<Guid, int> _results =
new Dictionary<Guid, int>();

public static int GetProgress(Guid id)
{
if (_results.ContainsKey(id))
Implementing \aiting Pages in ASP.NL1 31

{
return _results[id];
}
else
{
return 0;
}
}

public static bool IsCompleted(Guid id)
{
return (GetProgress(id) == 0);
}

public static void Add(Guid id)
{
if (!_results.ContainsKey(id))
{
_results.Add(id, 0);
}
_results[id] = _results[id] + 1;
}

public static void Remove(Guid id)
{
if (_results.ContainsKey(id) && _results[id] > 0)
{
_results[id] = _results[id] - 1;
}
}
}
1his time controller increments the counter then a process is registered and decrements the
counter as a process notiíies the controller that it is íinished. 1hus. code íor the main page
and íor the waiting page will be a little bit more complex.
1he main page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start"
%>

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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Progress Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:CheckBox ID="cbProgress" runat="server" Text="Show Progress" />
&nbsp;
<asp:Button ID="btnStart" runat="server"
Text="Start Three Long-Running Processes" OnClick="btnStart_Click"
/>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
32 bv Gaidar Magdanuro·
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;

public partial class Simple_Start : System.Web.UI.Page
{
protected Guid id;

protected void btnStart_Click(object sender, EventArgs e)
{
id = Guid.NewGuid();

MultiProcessCollection.Add(id);
ThreadStart ts = new ThreadStart(LongRunningProcess1);
Thread th = new Thread(ts);
th.Start();

MultiProcessCollection.Add(id);
ts = new ThreadStart(LongRunningProcess2);
th = new Thread(ts);
th.Start();

MultiProcessCollection.Add(id);
ts = new ThreadStart(LongRunningProcess3);
th = new Thread(ts);
th.Start();

if (cbProgress.Checked)
{
Response.Redirect("Progress.aspx?ID=" + id.ToString());
}
else
{
Response.Redirect("Status.aspx?ID=" + id.ToString());
}
}

protected void LongRunningProcess1()
{
Thread.Sleep(3000);
MultiProcessCollection.Remove(id);
}
protected void LongRunningProcess2()
{
Thread.Sleep(7000);
MultiProcessCollection.Remove(id);
}
protected void LongRunningProcess3()
{
Thread.Sleep(5000);
MultiProcessCollection.Remove(id);
}
}
1he waiting page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status"
%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Progress Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
Implementing \aiting Pages in ASP.NL1 33

<asp:Label ID="lblStatus" runat="server"
Text="Working... Please
wait..."></asp:Label>
</h1>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Simple_Status : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["ID"] != null)
{
// check for result
Guid id = new Guid(Request.Params["ID"]);
if (!MultiProcessCollection.IsCompleted(id))
{
Response.AddHeader("Refresh", "1");
}
else
{
lblStatus.Text = "Job is done.";
}
}
else
{
Response.Redirect("Start.aspx");
}
}
}
1his time we can make the user experience a little bit better bv showing the progress bar
indicating the real progress. 1o do this we implement a simple progress bar control and use it
on a modiíied waiting page.
1he progress bar control
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Drawing;

namespace MyControls
{

public class SimpleProgressControl : WebControl
{
private int _Max;

public int Max
{
get { return _Max; }
34 bv Gaidar Magdanuro·
set { _Max = value; }
}

private int _Value;

public int Value
{
get { return _Value; }
set { _Value = value; }
}

protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Width,
this.Width.Value.ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
writer.RenderBeginTag(HtmlTextWriterTag.Table);

writer.AddAttribute(HtmlTextWriterAttribute.Height,
this.Height.Value.ToString());
writer.RenderBeginTag(HtmlTextWriterTag.Tr);

for (int i = 0; i < _Max; i++)
{
// background color
if (i < _Value)
writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor,
ColorTranslator.ToHtml(this.ForeColor));
else
writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor,
ColorTranslator.ToHtml(this.BackColor));

writer.RenderBeginTag(HtmlTextWriterTag.Td);
writer.RenderEndTag(); // td
}

writer.RenderEndTag(); // tr
writer.RenderEndTag(); // table
}


}
}
1he more advanced waiting page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Progress.aspx.cs"
Inherits="Multi_Progress"
%>
<%@ Register TagPrefix="my" Namespace="MyControls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Progress Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<my:SimpleProgressControl ID="ctlProgress" runat="server"
BackColor="blue" ForeCOlor="red" Max="3" Value="0" Width="300" Height="30"
/>
<h1>
<asp:Label ID="lblComplete" runat="server" Text="Process complete."

Visible="false"></asp:Label></h1>
</div>
</form>
</body>
</html>
Implementing \aiting Pages in ASP.NL1 35


using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Multi_Progress : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["ID"] != null)
{
Guid id = new Guid(Request.Params["ID"]);
int p = MultiProcessCollection.GetProgress(id);
ctlProgress.Value = 3 - p;
if (p != 0)
{
Response.AddHeader("Refresh", "1");
}
else
{
lblComplete.Visible = true;
}
}
else
{
Response.Redirect("Start.aspx");
}
}
}
1his page looks like the shown one on íig. 3.

Iig. 3. More ad·anced waiting page.
Returnlne custem data ebjects frem the asynchreneus precesses
1he next step on the wav to building more ad·anced waiting page is to modiív the controller
object to work with custom data objects thus enable asvnchronous process to return these
custom data objects and pro·ide the waiting page with more data about the state oí the
process.
36 bv Gaidar Magdanuro·
lor instance. ií the process can be split into a íew diííerent steps it mav notiív the waiting
page about the percentage oí its completeness - this ·alue can be stored in a íield oí custom
data object used.
1he custom data object
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;


public class FeedbackObject
{

private string _result1 = String.Empty;

public string Result1
{
get { return _result1; }
set { _result1 = value; }
}
private string _result2 = String.Empty;

public string Result2
{
get { return _result2; }
set { _result2 = value; }
}

private int _progress = 0;

public int Progress
{
get { return _progress; }
set { _progress = value; }
}


public bool Complete
{
get { return (_progress == 100); }
}
}

1he controller
using System;
using System.Collections;


public static class FeedbackProcessCollection
{
private static Hashtable _results = new Hashtable();

public static FeedbackObject GetResult(Guid id)
{
if (_results.Contains(id))
{
return (FeedbackObject)_results[id];
}
else
{
return null;
}
Implementing \aiting Pages in ASP.NL1 3¯

}

public static void Add(Guid id, FeedbackObject stat)
{
_results[id] = stat;
}

public static void Remove(Guid id)
{
_results.Remove(id);
}
}
1o use this íeatures we ha·e to modiív the waiting page and the main page.
1he main page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start"
%>

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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Feedback Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button ID="btnStart" runat="server" Text="Start Long-Running
Process"
OnClick="btnStart_Click"
/>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;

public partial class Simple_Start : System.Web.UI.Page
{
protected Guid id;

protected void btnStart_Click(object sender, EventArgs e)
{
id = Guid.NewGuid();
ThreadStart ts = new ThreadStart(LongRunningProcess);
Thread th = new Thread(ts);
th.Start();

Response.Redirect("Status.aspx?ID=" + id.ToString());
}

protected void LongRunningProcess()
{
FeedbackObject fo = new FeedbackObject();
FeedbackProcessCollection.Add(id, fo);
for (int i = 0; i <= 100; i++)
38 bv Gaidar Magdanuro·
{
Thread.Sleep(50);
if (i == 100)
{
fo.Result1 = "First result.";
fo.Result2 = "Second result.";
}
fo.Progress = i;
}
}
}
1he waiting page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status"
%>
<%@ Register TagPrefix="my" Namespace="MyControls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Feedback Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<my:simpleprogresscontrol id="ctlProgress" runat="server"
backcolor="blue" forecolor="red" max="100"
value="0" width="300" height="30" />
<h1>
<asp:Label ID="lblComplete" runat="server"
Text="Process complete." Visible="false"></asp:Label></h1>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Simple_Status : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["ID"] != null)
{
// check for result
Guid id = new Guid(Request.Params["ID"]);

ctlProgress.Value = FeedbackProcessCollection.GetResult(id).Progress;

if (!FeedbackProcessCollection.GetResult(id).Complete)
{
Response.AddHeader("Refresh", "1");
}
else
{
FeedbackObject fo = FeedbackProcessCollection.GetResult(id);
lblComplete.Text = fo.Result1 + " " + fo.Result2;
lblComplete.Visible = true;
}
}
Implementing \aiting Pages in ASP.NL1 39

else
{
Response.Redirect("Start.aspx");
}
}
}
Now we are able to get anv data as a result írom an asvnchronous process as well as
percentage oí the process completeness.
Addlne Ajax features
L·entuallv. to make user experience e·en greater we can use Ajax íeatures to our waiting
page. 1hanks to Microsoít Ajax Lxtensions http:´´ajax.asp.net´, we do not need to do much
work. \e will add ScriptManager control and UpdatePanel controls and modiív onlv the
waiting page. Please note. to use Microsoít Ajax Lxtensoins íor ASP.NL1 vou should add a
reíerence to Svstem.\eb.Lxtenstions assemblv and coníigure web.coníig íile íor vour
application. \ou mav look through the sample application coníiguration íile to get íamiliar
with the coníiguration.,
1he waiting page
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status"
%>

<%@ Register TagPrefix="my" Namespace="MyControls" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Feedback Waiting Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePartialRendering="true">
</asp:ScriptManager>
<div>
<asp:UpdatePanel ID="UpdatePanel1" runat="server"

ChildrenAsTriggers="true">
<ContentTemplate>
<asp:Timer ID="Timer1" runat="server" Interval="1000"

OnTick="Timer1_Tick">
</asp:Timer>
<my:SimpleProgressControl ID="ctlProgress" runat="server"
BackColor="blue"
ForeColor="red"
Max="100" Value="0" Width="300" Height="30"
/>
<h1>
<asp:Label ID="lblComplete" runat="server"
Text="Process complete."
Visible="false"></asp:Label></h1>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
40 bv Gaidar Magdanuro·
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class Simple_Status : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["ID"] == null)
{
Response.Redirect("Start.aspx");
}
}
protected void Timer1_Tick(object sender, EventArgs e)
{
// check for result
Guid id = new Guid(Request.Params["ID"]);

ctlProgress.Value = FeedbackProcessCollection.GetResult(id).Progress;

if (FeedbackProcessCollection.GetResult(id).Complete)
lblComplete.Visible = true;
}
}

As this is all we ha·e to change to use Ajax íeatures at our waiting page. Now onlv the small
part oí the page will be updated during the progress control reíreshes.
Cencluslen
In this paper we saw how to quicklv create a waiting page to use in our applications. Ií vou
need onlv to show to user that a process is still running or alreadv íinished. the simplest
solution will do íor vou note that vou also can add Ajax íeatures íor better user experience!,.
Ií vour process should return complex data or mav be split in discrete parts vou mav like to
use the waiting page with íeedback. or ií vou ha·e to run a íew processes vou mav use the
multi-process ·ersion oí the waiting page.
1o understand this stuíí better look through the attached source code.
1oken Replacement in ASP.NL1 41

1UK£N R£PLAC£M£N1 lN A$P.N£1
01 November 2006
by Damon Armstrong
As a web de·eloper. vou will encounter situations that call íor an eííecti·e token replacement
scheme. 1oken replacement is reallv just a technologicallv-sa··v wav oí saving string
replacement. and in·ol·es substituting a "real" ·alue íor a "token" ·alue in a string. 1his
article presents a poweríul approach to token replacement in ASP.NL1 2.0.
1he goal is to create a centralized token replacement mechanism that works in ASP.NL1
controls. l1ML controls. static l1ML markup. and in text placed on the page írom the
code-behind. In other words. in anv situation imaginable.
Ií vou want to skip the background inío and get right into the replacement mechanism. then
jump to the section titled .cqvirivg .tt .´P.^í) Page Covtevt. Otherwise. let's get some
background on the ·arious token replacement options in ASP.NL1.
Baslc teken replacement cencepts
1he ultimate goal oí token replacement is to make the ·alue oí a string dvnamic. \ou begin
with a static string containing tokens. and then update those tokens with replacement ·alues
to produce vour dvnamic string. lor example. vou mav ha·e the íollowing string in an
application that emails users a new password ií theirs has been lost:
Dear [$NAME$],
You recently requested a password reset. Your new password is [$PASSWORD$].
Please keep this password in a safe location and quit losing it.
Thank You
In this scenario. the application has two ·alues to communicate to the user: their name to
personalize the email, and their new password because thev need it to login,. Let's sav we
wish to assign the ·alue "Matt" to the [$NAME$] token and "5ZOS¯6B·" to the
[$PASSWORD$] token. 1he resulting text would look like this:
Dear Matt,
You recently requested a password reset. Your new password is 5ZQS76Bv.
Please keep this password in a safe location and quit losing it.
Thank You
One question that might arise is. whv not just use concatenation to build the string·
(oncatenation is certainlv íaster. but it comes down to a question oí maintainabilitv.
(oncatenation can onlv occur in code. so vou would ha·e to hard-code the majoritv oí the
string. like this:
"Dear " + Name + ",\r\nYou recently requested a password reset.
Your new password is " + password + ". Please keep this password in a
safe location and quit losing it.\r\n\r\nThank You"
\hat happens ií vou wanted to update the email content· \ou would ha·e to update vour
code. recompile it. and redeplov it. Plus. a complex l1ML email built directlv in (4 source is
not the easiest thing to debug and maintain.
1he token replacement approach allows vou to store the content in a separate íile. read it into
vour application. and replace the tokens in code. 1his separates the content írom the
42 bv Damon Armstrong
application and allows vou to make updates in a less con·oluted en·ironment and without
ha·ing to recompile or redeplov the application.
1eken / $trlne Replacement ln A$P.N£1
1here are se·eral wavs to replace strings in ASP.NL1 2.0. but I'm onlv going to touch on the
three that I consider the most popular:
• Inline Script
• 1he String.Format method
• 1he String.Replace and StringBuilder.Replace instance methods
Lach oí these are outlined in more detail below.
lnllne $crlpt
Although ASP.NL1 has mo·ed to a code-behind model. vou can still write inline script just as
vou could in original ASP. Inline script still has its uses and can be extremelv helpíul íor string
replacement scenarios. Ií vou think about it. inline script is essentiallv a reallv poweríul string
replacement mechanism that íinds vour script in a string i.e. the page markup,. runs that
script. and then replaces the script with the ·alue it produces. lere's our original example
modiíied íor inline script:
Dear <%=Name%>,
You recently requested a password reset. Your new password is <%=Password%>.
Please keep this password in a safe location and quit losing it.
Thank You
One issue with inline script is that vou cannot alwavs use it in conjunction with ASP.NL1
controls. lor example. let's sav that vou wanted a standard ASP.NL1 button on the page to
sav "Matt. (lick lere to Lmail \our Password." \ou cannot do the íollowing:
<asp:Button runat="server" id="btnPwd"
text="<%=Name%>, Click Here to Email Your Password" />
Nor can vou use the mechanism írom inside vour code-behind. So. it's useíul íor making a
single page. but not íor our global string-replacement needs.
1he $trlne.Fermat Methed
1he String.Format method accepts a string containing tokens íollowed bv a list oí
replacement ·alues íor the tokens íound in the string. 1he tokens in the string are numbers
surrounded bv curlv brackets. where the number corresponds to the index oí the replacement
·alue passed into the method.
String.Format("Name: {0}, Gender: {1}, Age:{2}, Height:{3}, Weight:{4}",
"Matt", "Male", "25", "5'10\"", "160");

//OUTPUT-->Name: Matt, Gender: Male, Age: 25, Height: 5'10", Weight: 160
1he íirst token. {0}. corresponds to the íirst replacement ·alue "Matt". the second token.
{1}. corresponds to "Male". and so on. \ou can ha·e an iníinite number oí tokens and
replacement ·alues. but vou ha·e to ha·e at least as manv replacement ·alues as vou ha·e
tokens. In other words. when the íunction encounters a number in curlv brackets. {N}. there
had better be a corresponding replacement ·alue íor that token or else the íunction throws an
exception.
1oken Replacement in ASP.NL1 43

// Throws an exception because there are 4 tokens
// but only one replacement value

String.Format("Name: {0}, Gender: {1}, Age:{2}, Height:{3}, Weight:{4}",
"Matt");
1okens do not need to appear in order. vou can use a token more than once in the string. and
vou can ha·e more replacement ·alues than tokens:
String.Format("{1}{3}{2}{2}{0}",
"O","H","L","E","X","Y","Z","!");

//OUTPUT-->HELLO
In this example. the token. {2}. appears twice. the tokens are not in order. and the "X". "\".
"Z". and "!" replacement ·alues are ne·er used.
\ou can easilv put tokens íor the string.lormat method in ASP.NL1 controls. l1ML
controls. static l1ML markup. and code in the code-behind. so it is a candidate íor use in
our global string replacement mechanism. Mv biggest issue is that the tokens are non-
intuiti·e. \hen vou see a token like [$NAME$]. vou ha·e some idea what it represents.
whereas the token. {7}. communicates ·erv little.
1he $trlne.Replace and $trlneBullder.Replace Metheds
Lastlv. we ha·e the Replace methods íound on the String and StringBuilder instances.
Both oí these methods operate using the same basic logic. \ou pro·ide a string containing a
token. identiív the token. supplv a replacement ·alue íor the token. and the method replaces
anv instances oí the token with the replacement ·alue.
\e'll begin bv looking at the Replace method on a String instance. Since the Replace
method is onlv a·ailable on a String instance and not írom the String class itselí. vou start
bv creating a String instance. 1hen vou call the Replace method írom the instance bv
passing in a token and replacement ·alue. and the method returns a string containing the
replacements. \our original string. howe·er. remains unchanged. Ií vou want to update vour
string with the replacement. vou ha·e to assign the result oí the Replace íunction back to
vour string. as demonstrated in the íollowing example:
string myString = "Hello, my name is [$NAME$].";

//This does NOT change the value of myString
myString.Replace("[$NAME$]", "Matt");

//You have to assign the value of the Replace function back
//to the string to change the value
myString = myString.Replace("[$NAME$]", "Matt");
\ou call the Replace method on a StringBuilder instance in the exact same wav as a
string: bv passing in a token and replacement ·alue. lowe·er. the Replace method on a
StringBuilder instance operates directlv on that StringBuilder instance's ·alue. so vou
do not need to assign the result oí the Replace íunction back to vour StringBuilder to
pick up the changes:
StringBuilder myBuilder = new StringBuilder("Hello, my name is [$NAME$].");
myBuilder.Replace("[$NAME]","Matt");
44 bv Damon Armstrong
1okens like [$NAME$] are íairlv intuiti·e. can be placed in ASP.NL1 controls. l1ML
controls. static l1ML markup. and code in vour code behind. So it's the option we're going
to run with íor building the global string replacement mechanism íor our ASP.NL1
application.
Acqulrlne All A$P.N£1 Paee Centent
Our biggest objecti·e in building a global string replacement mechanism is that it needs to
work e·ervwhere. Ií vou put a token directlv in vour l1ML. it needs to be replaced. Ií vou
assign a token to an ASP.NL1 control írom a code-behind. then it needs to be replaced. Ií
vou put a token in a database and a control pulls that ·alue írom the database and displavs it.
then it needs to be replaced. So. how do vou go about doing that·
\hen it comes right down to it. all oí the architecture and code íor ASP.NL1 re·ol·es
around building a giant string to send to the browser. ASP.NL1 controls. l1ML. code.
·alues írom a database. thev all end up as part oí a string containing the source íor a page. All
we ha·e to do is intercept that string beíore ASP.NL1 sends it to the browser and run our
replacements. And it reallv doesn't take all that much code.
Since we want this mechanism to be a·ailable to all oí the pages in an application. we'll create
a new class named TokenReplacementPage that deri·es írom System.Web.UI.Page. Anv
pages requiring token replacement íunctionalitv just need to deri·e írom
TokenReplacementPage instead oí System.Web.UI.Page. lollowing is the code íor the
TokenReplacementPage class:
using System;
using System.IO;
using System.Text;
using System.Web.UI;

public abstract class TokenReplacementPage : Page
{
protected override void Render(HtmlTextWriter writer)
{
//Create our own mechanism to store the page output
StringBuilder pageSource = new StringBuilder();
StringWriter sw = new StringWriter(pageSource);
HtmlTextWriter htmlWriter = new HtmlTextWriter(sw);
base.Render(htmlWriter);

//Run replacements
RunPageReplacements(pageSource);
RunGlobalReplacements(pageSource);

//Output replacements
writer.Write(pageSource.ToString());
}

protected void RunGlobalReplacements(StringBuilder pageSource)
{
pageSource.Replace("[$SITECONTACT$]", "John Smith");
pageSource.Replace("[$SITEEMAIL$]", "john.smith@somecompany.com");
pageSource.Replace("[$CURRENTDATE$]",
DateTime.Now.ToString("MM/dd/yyyy"));
}

protected virtual void RunPageReplacements(StringBuilder pageSource)
{
}

}
(ode Listing 1 - 1okenReplacementPage class
1oken Replacement in ASP.NL1 45

lirst. note that the TokenReplacementPage class is an abstract class that deri·es írom
System.Web.UI.Page. 1his means that it has all the standard Page íunctionalitv as well as
token replacement íeatures. All vou ha·e to do to coníer token replacement íunctionalitv to a
page is inherit írom the TokenReplacementPage class instead oí the Page class.
1here are three methods inside the TokenReplacementPage class:
• 1he Render method - to write the l1ML page source to a StringBuilder
• 1he RunGlobalReplacements method - to períorm global token replacements on
the source
• RunPageReplacements - to allow íor page-speciíic token replacements
Wrltlne the Paee $eurce te $trlneBullder
1he o·erridden Render method has a single HtmlTextWriter parameter named writer. An
HtmlTextWriter allows vou to write l1ML to a stream. In this case. the underlving stream
is sent to the browser. so the writer parameter is vour conduit íor outputting l1ML to the
people ·iewing vour page. Normallv. the Render method iterates through all oí the controls
on the page and passes the writer parameter to the Render method on each indi·idual
control. As each control executes its Render method. it writes the l1ML íor that section oí
the page to the browser.
Since we want to capture the entire page source beíore it gets to the browser. we need to do a
little work to re-route the page source into a stream that we can access. 1o start. we create a
"stream" that we can work with: a StringBuilder instance named pageSource. Next. we
create a new StringWriter named sw and pass pageSource into its constructor. 1his
initializes sw with pageSource as its underlving stream. Anvthing written to sw is output to
pageSource. Next. we create a new HtmlTextWriter named htmlWriter and pass sw
into its constructor. 1his initializes htmlWriter with sw as its underlving TextWriter.
1hus. anvthing written to htmlWriter is written to sw. which is then written to
pageSource. linallv. we pass htmlWriter to the Base.Render method and allow the page
to render as it normallv would. Aíter Base.Render íinishes. the entire page source is
a·ailable íor modiíication in pageSource.
Aíter acquiring the page source. the o·erridden Render method passes pageSource to two
methods that are responsible íor actuallv making the substitutions: RunPageReplacements
and RunGlobalReplacements. \e will discuss these in more detail shortlv.
Once the replacements ha·e been made. the onlv thing leít to do is send the updated content
to the browser. \e do that bv using pageSource to write in the last line oí code in the
o·erridden Render method.
Next. let's take a look at making the actual replacements.
Glebal Replacements
\ou make global replacements. that is the replacements vou want to make on e·erv page that
inherits írom the TokenReplacementPage class. in the RunGlobalReplacements
method. All vou ha·e to do to make a replacement is call the Replace method on the
pageSource parameter and pass in the token and the replacement ·alue:
pageSource.Replace("TOKEN", "REPLACEMENT VALUE");
1he Replace method then searches through the string in pageSource and replaces anv
instances oí the token with the replacement ·alue. Remember to make vour token something
that is unlikelv to normallv appear in the page. lor example. [$NAME$] is a much better
choice than just NAME because it's ·erv unlikelv a normal sentence would contain a word with
46 bv Damon Armstrong
brackets and dollar signs around it. \ou don't want to accidentallv mistake a normal word in a
sentence íor a token.
Paee $peclflc Replacements
1here mav be times when vou want to run page-speciíic replacements. Notice that the
RunPageReplacements method in the TokenReplacementPage class is marked as ·irtual
and contains no code. 1his allows vou to o·erride the RunPageReplacements method on
the page in which vou want to make page-speciíic token replacements. 1he replacements are
made in the exact same íashion as described in the Ctobat Reptacevevt. section. but thev are
onlv applied to that speciíic page:
public partial class PageSpecificReplacementsPage : TokenReplacementPage
{
protected override void RunPageReplacements(
System.Text.StringBuilder pageSource)
{
pageSource.Replace("[$PAGESPECIFICTOKEN$]",
"This replacement only runs on this page!");
}
}
(ode Listing 2 - O·erridden RunPageReplacements Lxample
Checklne eut the deme appllcatlen
Download the demo application írom Simple-1alk http:´´www.simple-
talk.com´content´íile.ashx·íile~2¯9, and extract it to a location oí vour choosing on vour
hard dri·e. Start Visual Studio. and open the web site írom where·er it is that vou chose to
sa·e it. 1here are onlv íour íiles not including code-behinds, in the entire demo:
Iile Name Purpose
App_Code\1okenReplacementPage.cs (ontains the 1okenReplacementPage class that
pro·ides pages with token replacement
íunctionalitv
Default.aspx Demonstrates global token replacement
íunctionalitv
PageSpecificReplacementsPage.aspx Demonstrates global and page-speciíic token
replacement íunctionalitv
Web.config \ebsite coníiguration
1ake a look at the markup in the two ASP.NL1 pages and notice the ·arious tokens that
appear throughout. Also take a look at each page's code behind íiles because vou will see a
token set in code to demonstrate that vou can put a token anvwhere and. as long as it is
output to the page source. it is replaced bv the token replacement mechanism. 1he code-
behind íor PageSpecificReplacementsPage.aspx also contains the
RunPageReplacement o·erride írom (ode Listing 2.
\hen vou run the demo application. vou will see that the tokens are replaced with their
respecti·e ·alues when the page appears in vour browser. Make sure to obser·e the diííerence
between the [$PAGESPECIFICTOKEN$] beha·ior between Default.aspx and
PageSpecificReplacementsPage.aspx. leel íree to add new tokens. replacement ·alues.
and pages to the demo application to get a íeel íor how it all works.
Cencluslen
\hen vou need a token replacement mechanism. vou should be well equipped with this
solution. It gi·es vou the abilitv to replace tokens regardless oí whether thev appear in
ASP.NL1 controls. l1ML controls. static l1ML markup. code. or e·en a content
1oken Replacement in ASP.NL1 4¯

management database. As long as vou can get the token to render on the page. it can be
replaced.
.
48 bv Damon Armstrong
R£GULAR £XPR£$$lUN BA$£b 1UK£N R£PLAC£M£N1 lN
A$P.N£1
10 January 2007
by Damon Armstrong
In mv last article. I co·ered a technique íor 1oken Replacement in ASP.NL1
http:´´www.simple-talk.com´dotnet´asp.net´token-replacement-in-asp.net´,. which works
írom anvwhere in vour application. Simplv put. token replacement is a matter oí searching
through a string íor a token and replacing it with a ·alue oí vour choice. All oí the code íor
making the actual string replacement is nicelv packaged in the Replace method on the String
and StringBuilder objects in the .NL1 lramework. so the real task is getting the webpage to
render as a string with which vou can work
NO1L:
ít yov bare vot atreaay aove .o. ptea.e reaa tbe preriov. )o/ev Reptacevevt articte becav.e tbe .otvtiov
pre.evtea bere arar. ov ror/ ai.cv..ea preriov.ty. ív particvtar. aetait. ov reptacevevt. iv tbe String ava
StringBuilder iv.tavce.. atovg ritb tbe .trivg revaerivg tecbviqve. rere att corerea iv tbe preriov. articte
ava ritt vot be aetaitea agaiv bere.
1his article expands on that technique and introduces Regular Lxpression-based token
replacement into the process. gi·ing vou e·en more power and ílexibilitv in vour
applications.
1his technique is similar to normal token replacement. but instead oí searching íor a static
token in a string. vou can search íor dvnamic text using regular expressions.
\hat ií vou don't know anvthing about Regular Lxpressions· No problem! I'·e got a íairlv
cool regular expression that should allow vou to do just about anvthing vou want to do in
code. so vou don't ha·e to understand regular expressions at all íor this article. I would
suggest. howe·er. that vou trv to read up on them as much as possible because thev are
extremelv useíul in manv circumstances.
What are reeular expresslens?
Regular Lxpressions are an extremelv poweríul tool íor executing complex string searching
routines. \ou work with Regular Lxpressions in .NL1 using the Regex class íound in the
System.1ext.RegularLxpressions namespace.I tend to think oí Regular Lxpressions in
three parts: the engine. the pattern. and the string:
• 1he engine is the component responsible íor searching through and locating matches
in astring. based on a set oí instructions.
• A pattern is the set oí instructions. or rules. which iníorm the engine what to match
in the string.
• 1he string is the set oí character data being searched. \ou pass a string and a pattern
into the regular expression engine and the engine returns iníormation identiíving all
oí the locations in the string that matched the pattern. as well as iníormation about
each match.
Patterns use their own svntax to let the engine know the details oí what is being sought in the
string. and that svntax can look prettv complex and intimidating to people who ha·en't
worked with it beíore. Regular Lxpression patterns are normallv prettv easv to identiív
because thev look like complete gibberish. lere's the one we'll be using later on in the article:
\[\$(?<functionName>[^\$]*?)\((?:(?<params>.*?)(?:,|(?=\))))*?\)\$\]
Regular Lxpression Based 1oken Replacement in ASP.NL1 49

It looks impossiblv complicated. but it's not. It's just long. I will be dissecting it later on in the
article. so don't worrv about it too much right now.
$elutlen evervlew
\e'·e got a static token replacement solution in place írom the last article that renders the
ASP.NL1 page to a StringBulder. and then passes that StringBuilder to two diííerent
íunctions: RunPageReplacements and RunGlobalReplacements. So we ha·e the string
that we need to search alreadv setup and readv to go. we just need to search though it and run
our Regular Lxpression based replacements.
One oí our design goals is to keep Regular Lxpression searching to a minimum. Although
Regular Lxpressions are eííicient. considering what thev do. running too manv oí them on
each page request can quicklv impact períormance. So. our goal is to run onlv one search per
page request.
\e should also stri·e to make the token replacement mechanism ílexible enough to handle a
·irtuallv unlimited number oí diííerent token replacement scenarios. 1his mav seem like a
conílicting goals considering we onlv want to run one regular expression search per page. but
Regular Lxpressions are cool enough to handle it.
lere's the plan: allow íor the deíinition oí 1oken Iunctions inside the string where
replacements are to be made. A token íunction is essentiallv a íunction name and parameters
surrounded bv brackets and dollar signs.
NOTE:
1he term "1oken lunction" is mv own in·ention. bv the wav. just in case vou go searching íor
it on Google or \ikipedia!
lere are a couple oí examples:
[$FunctionName(param1,param2,paramX)$]
[$Add(1,5,10,15,20,25,30)$]
[$GetContent(ContentGroupName,ContentKey)$]
\e then search the page use a single regular expression pattern to locate all oí the 1oken
lunctions on the page. One oí the cool things about Regular Lxpressions is that vou can
execute a single search. locate multiple matches. and return those matches along with speciíics
about each match. In this case. the speciíics include where in the page a 1oken lunction was
located. the name oí the token íunction. and e·en a list oí the parameters deíined in the
1oken lunction.
Armed with this iníormation. we can then create "handlers" in the code-behind that are
responsible íor generating the replacement text íor the 1oken lunction based on the 1oken
lunction name and parameters. 1his pushes the work oí deíining 1oken lunction names and
replacement text logic into the code-behind. and keeps vou írom ha·ing to update the
Regular Lxpression pattern when vou want to add new 1oken lunction deíinitions.
Matches and captures
1erminologv-wise. it's important to understand the diííerence matches and captures. \hen
vou run a regular expression against a string. vou are searching íor parts oí the string that
"match" the regular expression as a whole. Matching answers a \es´No question: did vou íind
a section oí the string that matched the entire regular expression.
Remember. a regular expression íinds matches based on rules. 1hus. each match can ha·e a
diííerent text ·alue behind it. \hen the regular expression engine íinds a match. it "captures"
50 bv Damon Armstrong
iníormation about that matched text so vou can quicklv access it later without ha·ing to go
back through the string.
\ou can also deíine capturing groups inside oí a regular expression. A capturing group is a
section oí a pattern íor which the regular expression engine acquires capture iníormation. In
other words. a capturing group tells the regular expression engine to capture the text íor a
particular sub-section oí the regular expression. 1his allows vou to easilv acquire ·alues íor
speciíic items inside the matched text. In our case. we'll use capturing groups to expose the
íunction name and parameters in our token íunction. lor example. this is a generic íorm
letter containing a series oí 1oken lunctions:
[$GetContent(FormLetter, Salutation)$]

Thank you for registering on our website.
Please use the following information to login to your account:
Username: [$UserName()$]
Password: [$Password()$]
If you have any problems, please feel free to email us at:

[$GetContent(ContactInfo, CustomerSupportEmail)$].

Thank You,
Customer Service
\hen we run our Regular Lxpression against this string. we get the íollowing breakdown oí
match and capturing group iníormation:
• Match - [$GetContent(FormLetter, Salutation)$]
o Capture Group - functionName
GetContent
o Capture Group – params
FormLetter
Salutation
• Match – [$UserName()$]
o Capture Group – functionName
UserName
o Capture Group – params
• Match – [$Password()$]
o Capture Group – functionName
Password
o Capture Group – params
• Match – [$GetContent(ContactInfo, CustomerSupportEmail)$]
o Capture Group – functionName
GetContent
o Capture Group – params
ContactInfo
CustomerSupportEmail

Notice that the match itselí captures the entire token íunction. including the surrounding
brackets and dollar signs. the íunction name. parenthesis. parameters. and commas that
separate the parameters. (apturing group iníormation contains a better breakdown oí the
items that make up the token íunction including the íunction name and an indi·idualized list
oí parameter ·alues.
Regular Lxpression Based 1oken Replacement in ASP.NL1 51

Reeular expresslen cencepts used ln the selutlen
\ou don't need an in depth knowledge oí regular expressions íor this article. but it does help
to ha·e a basic understanding oí the regular expression pattern we're going to use to identiív
1oken lunctions. In pursuit oí that understanding. we'll do two things: co·er some oí the
regular expression svntax used in the pattern. and break the pattern down into its indi·idual
components to discuss what each component does. lirst. let's touch on svntax:
Backslash (\) medlfler
A backslash \, is a modiíier that iníorms the regular expression engine to handle the
character immediatelv íollowing the backslash in a manner other than it's normal context. lor
example. a dollar sign $, is a special character that normallv represents "match the start oí a
string" in a regular expression pattern. But it's certainlv possible that vou mav want to match
an actual > character. 1o do so. vou escape the > bv placing a backslash in íront it: \$.
Similarlv. the characters w. s. and d are normallv interpreted as the actual characters w. s. and
d. Ií vou modiív them using a backslash \w. \s. and \d,. then thev represent "anv word
character". "anv whitespace character". and "anv numeric character." respecti·elv.
Greuplne censtructs
\ou can create a group bv surrounding anv portion oí an expression with parentheses. 1here
are a number oí diííerent tvpes oí groups in regular expression patterns. but íor todav vou
onlv need to know about íi·e:
• Unnamed groups - anv time vou surround an expression with parentheses it
becomes an unnamed group. \ou can also think oí unnamed groups as the deíault
group. 1he svntax íor an unnamed group is:
(expression)
• Named groups - vou can gi·e an unnamed group a name using the íollowing svntax:
(?<Group Name>expression)
• Capturing groups -. Both unnamed and named groups are capturing groups.
Unnamed groups are accessed bv numbered index. whereas a named group can be
accessed bv numbered index or bv name. A group's numbered index is determined bv
the location oí the group in relation to other groups in the expression. I recommend
using named groups íor readabilitv and maintenance purposes.
• Positive Lookahead - normallv. an element in a regular expression pattern
"consumes" characters that it matches. In other words. the regular expression engine
checks a pattern element against characters in the string. and ií the engine determines
the characters match the pattern element. it mo·es on to the next element in the
pattern and the next set oí characters in the string. 1he positi·e lookahead allows vou
to check íor the existence oí a character or series oí characters, without consuming
those characters. Bv deíinition. a positi·e lookahead group is non-capturing. It's
svntax looks like this:
(?=expression)
• Non-Capturing groups - when the regular expression engine processes a non-
capturing group. it does not capture anv additional iníormation about matches íound
in that group. 1his helps keep processing and data exchange to a minimum ií vou
don't need additional capture iníormation íor the group. \ou can change an unnamed
group into a non-capturing group bv adding a ·: aíter the opening parenthesis. 1he
svntax looks like this:
(?:expression)
52 bv Damon Armstrong
Character sets
(haracter sets allow vou to match against a collection oí characters. and are deíined bv
enclosing the characters vou want included in the set with square brackets. \ou mav speciív
the characters indi·iduallv. as a character range. or as a mix oí indi·idual characters and
ranges. lor example. a character set containing all Lnglish ·owels mav looks like ¡ALIOU¡. A
character set containing all lowercase alphabetic characters mav looks like ¡a-z¡. And a
character set containing onlv consonants mav look like ¡bcdfghj-np-tvwxyz¡.
Placing a ^ aíter the opening bracket oí a character set makes the set exclusionarv. In other
words. the character set includes all characters except the ones in the set. So. ¡^a-z¡ means
anv characters except the letters a-z. \ou ha·e to be a bit careíul in vour thinking with
exclusionarv sets. \ou mav. íor example. be tempted to deíine consonants as ¡^aeiou¡. But
¡^aeiou¡ technicallv includes digits like 0-9 and special characters like (){]¡¡!m4$°^&*.
0uantlflers
Ouantiíiers iníorm the regular expression engine how manv times to match part oí the
regular expression pattern. lor example. ií vou want to íind all numbers in a document. then
vou could use a regular expression like:
\d+
Remember. \d means "all numeric characters" and + is a "quantiíier" that means "1 or more
matches". 1hus. when vou ha·e a sentence like "1here are 10 people in 4 cars. dri·ing 125
miles" the regular expression will íind the numbers 10. 4. and 125 e·en though each number
has a diííerent number oí digits. Other quantiíiers include:
* Zero or more matches
+ One or more matches
*? Zero or more matches. but as íew matches as possible
+? One or more matches. but as íew matches as possible
{n] Lxactlv n number oí matches
{n,] At least n number oí matches iníinite matches possible,
{n,m] Between n and m number oí matches
One oí the most important parts oí understanding regular expressions is knowing about the
"as íew as possible" quantiíiers. Manv times. vour regular expression will "o·ershoot" the text
vou are trving to capture. lor example. let's sav vou want to capture all text inside the
parenthesis ií vou ha·e a string:
Jack (who is a boy) and Jill (who is a girl) ran up the hill
\ou mav initiallv trv a regular expression like:
\(.*\)
1his regular expression savs look íor an open parenthsis \(. then íind zero or more characters
.*. until vou reach a closing parenthsis \). But when vou run this against the string. it
captures:
(who is a boy) and Jill (who is a girl)
Regular Lxpression Based 1oken Replacement in ASP.NL1 53

\hv· Because. at least as íar as the engine is concerned. there are three sets oí open and
close parenthesis in the string:
8. (who is a bov, and Jill who is a girl)
9. who is a bov,
10. who is a girl,
1he * quantiíier tells the engine to match the most characters possible while still meeting the
criteria oí ha·ing an open and close parenthesis around those characters. lence. it captures
the longer string. \hen vou switch to the *? quantiíier. like this:
\(.*?\)
\ou tell the engine to match the íewest characters possible while still meeting the criteria oí
ha·ing an open and close parenthesis around those characters. So vou end up capturing the
two indi·idual parenthetical statements:
(who is a boy)
(who is a girl)
1his is important íor us because we're capturing íunction iníormation inside |> >| characters
and we don't want to accidentallv suck in all the page text between two íunctions.
Breaklne dewn the teken functlen reeular expresslen pattern
Below vou will íind the regular expression pattern used to locate token íunctions that appear
throughout vour ASP.NL1 page:
\[\$(?<functionName>[^\$]*?)\((?:(?<params>.*?)(?:,|(?=\))))*?\)\$\]
1o simpliív discussion oí this pattern. I'm going to break it down into its indi·idual elements
and present each element on its own line. In the íollowing table. vou will íind the pattern
element in the leít column. and a description oí that element in the right column. General
commentarv appears in the shaded rows.
\e start bv looking íor the opening |> that denotes the start oí a token íunction
`| Open bracket literal
`> Dollar sign literal
(apture the íunction name in a named group called íunctionName. 1he íunction name
includes anv characters that appears between the opening |> oí the token íunction and
the íirst parenthesis. excluding the dollar sign character. It is important to exclude the
dollar sign because doing so helps the regular expression a·oid accidentallv matching on a
normal token like |>1OKLN>|.
··íunctionName· Named group start íunctionName,
|` Lxclusionarv character set start
`> Dollar sign literal
54 bv Damon Armstrong
| Lxclusionarv character set end
· Ouantiíier - matches zero or more times. as íew times
as possible
, Named group end
Look íor the opening parenthesis that marks the beginning oí the íunction parameters
` Open parenthesis literal
(reate a named group that acts as a containing group íor:
1., 1he parameter name
2., 1he comma the separates the parameters OR the ending parenthesis
·: Unnamed group start
(apture the parameter name in a named group called params
··params· Named group start param,
. Anv character
· Ouantiíier - matches zero or more times. as íew times
as possible
, Named group end
Next. íind the character that denotes the "end" oí the parameter. 1his could either be a
comma that separates the parameter írom another parameter. or a closing parenthesis
that marks the end oí the parameter list altogether.
\e do not. howe·er. want to consume the ending parenthesis in this section because the
possibilitv exists that a íunction mav ha·e zero parameters. Ií a íunction has zero
parameters. then this section oí the pattern isn't reallv used. Since the ending parenthesis
is alwavs present. so we want to consume it in a section oí the pattern that is alwavs used.
·: Unnamed group start
. (omma literal
| OR operator
·~ Start positi·e lookahead group
`, Open parenthesis literal
, Lnd positi·e lookahead group
, Lnd unnamed group
Lnd the unnamed group that contains the parameter. Use the zero or more times. as íew
times as possible quantiíier aíter the unnamed group to tell the regular expression engine
to capture as manv parameters as are present. and to capture them indi·iduallv instead oí
as one giant parameter string
, Lnd named group
Regular Lxpression Based 1oken Replacement in ASP.NL1 55

· Ouantiíier - matches zero or more times. as íew times
as possible
(onsume the ending parenthesis
`,
linish bv looking íor the closing >|
`> Looks íor the closing >
`| Looks íor the closing |
Now vou should ha·e a prettv good idea oí what the regular expression does. so it should
make a lot more sense when vou're working with it in code.
Addlne reeular expresslens te the 1ekenReplacementPaee class
\e need to do a íew minor things to add regular expression based token replacements to the
1okenReplacementPage class. Again. ií vou ha·e no idea what the
1okenReplacementPage class is. then vou need to read mv last article on 1oken
Replacement in ASP.NL1 because it contains the íoundation íor what we're co·ering here.
Below. vou will íind the updated code íor the 1okenReplacementPage class with the regular
expression additions bolded to make them stand out. I'll discuss each updates below in more
detail:
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Reflection;

public abstract class TokenReplacementPage : Page
{
private static Regex functionRegex = new Regex(
@"\[\$(?<functionName>[^\$]*?)\(" +
@"(?:(?<params>.*?)(?:,|(?=\))))*?\)\$\]",
RegexOptions.IgnoreCase |
RegexOptions.Singleline);

protected override void Render(HtmlTextWriter writer)
{
StringBuilder pageSource = new StringBuilder();
StringWriter sw = new StringWriter(pageSource);
HtmlTextWriter htmlWriter = new HtmlTextWriter(sw);
base.Render(htmlWriter);
RunRegularExpressionReplacements(pageSource);
RunPageReplacements(pageSource);
RunGlobalReplacements(pageSource);
writer.Write(pageSource.ToString());
}

protected void RunGlobalReplacements(StringBuilder pageSource)
{
pageSource.Replace("[$SITECONTACT$]", "John Smith");
pageSource.Replace("[$SITEEMAIL$]", "john.smith@somecompany.com");
pageSource.Replace("[$CURRENTDATE$]",
DateTime.Now.ToString("MM/dd/yyyy"));
}

protected virtual void RunRegularExpressionReplacements(
StringBuilder pageSource)
{
56 bv Damon Armstrong
//Regular Expression Replacements
MatchCollection matches =
functionRegex.Matches(pageSource.ToString());

//Iterate through all the matches
for (int i = matches.Count-1; i>=0; i--)
{
//Pull function name from the group
string functionName = matches[i].Groups["functionName"].Value;
string[] paramList = CreateParamList(matches[i]);
string replacementValue = string.Empty;
//Run different code based on the function name
switch (functionName.ToUpper())
{
case "ADD":
int sum = 0;
for (int i2 = 0; i2 < paramList.Length; i2++)
{
sum += int.Parse(paramList[i2]);
}
replacementValue = sum.ToString();
break;
case "CONTENT":
replacementValue =
ContentManager.GetContent(paramList[0],
paramList[1]);
break;
default:
replacementValue = String.Format(
"<!-- Could not find case statement for {0} -->",
functionName);
break;
}


//Make replacements
pageSource.Remove(matches[i].Index, matches[i].Length);
pageSource.Insert(matches[i].Index, replacementValue);
}
}

//Create string array containing each parameter value
protected string[] CreateParamList(Match m)
{
string[] paramList = new string[m.Groups[2].Captures.Count];
for (int i = 0; i < paramList.Length; i++)
{
paramList[i] = m.Groups["params"].Captures[i].Value;
}
return paramList;
}

protected virtual void RunPageReplacements(StringBuilder pageSource)
{

}
}
$ystem.1ext.Reeular£xpresslens namespace
\ou can íind all oí the regular expression classes in the System.1ext.RegularLxpressions
namespace. \e'·e imported this namespace at the top oí the class to a·oid lots oí tvping.
$tatlc Reeex varlable
\ou can interact with the .NL1 lramework's regular expression engine in one oí two wavs:
vou can execute static methods oíí the Regex class. like this:
Regex.Matches(inputString, regularExpressionPattern)
Regular Lxpression Based 1oken Replacement in ASP.NL1 5¯

\hen vou take this approach. the runtime compiles a Regex object with the speciíied
pattern. executes the regular expression. then abandons that Regex object. 1his is prettv
wasteíul ií vou're going to be reusing the same regular expression o·er and o·er again.
A better option. especiallv íor our situation. is to create a Regex object and maintain a
reíerence to that object so we can use it o·er and o·er again. \e do this with the íollowing
code that appears near the top oí the class:
private static Regex functionRegex = new Regex(
@"\[\$(?<functionName>.*?)\((?:(?<params>.*?)" +
@"(?:,|(?=\))))*?\)\$\]",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
1his creates a new Regex object containing the regular expression discussed earlier. lor
íormatting sake. I split the regular expression pattern out onto two lines. \ou can also see that
we're creating the Regex object with two regular expression options speciíied: IgnoreCase
and SingleLine. IgnoreCase means that the regular expression is not case sensiti·e. It's a
good option to know about. though it doesn't matter in this case because we're not looking
íor anv alphabetic characters. 1he second option. SingleLine. means that the regular
expression engine should process the input string as single line. Normallv. when a string
contains line breaks. the engine processes the input string as multiple lines.
RunReeular£xpresslenReplacements methed
RunRegularLxpressionReplacements accepts a StringBuilder object named pageSource
containing the raw l1ML output íor the page. It begins bv passing the page source into the
Matches method on the íunctionRegex ·ariable. 1he Matches method executes our regular
expression pattern. íinds all the token íunctions in the page source. stores iníormation about
each match in a Match object. then packages those Match objects into a MatchCollection
object which it returns as the result oí the íunction. \e then store that MatchCollection in
the matches ·ariable. 1he method also creates a ·ariable named offset and sets its ·alue to
zero.
Next. the method iterates backwards through each Match object in the MatchCollection
using a íor loop. I'll talk about whv it goes backwards in a minute. Inside the íor loop. we do a
íew diííerent things. lirst. we parse out the name oí the íunction in the token íunction using
the íollowing code:
string functionName = matches[i].Groups["functionName"].Value;
Remember. we made a named group called functionName that captures the name oí the
íunction. Accessing the íunction name is as easv as passing "íunctionName" into the Group
propertv and pulling back the Value oí the text captured bv that named group.
Second. we create a string arrav containing a list oí the parameters íor the token íunction
using the CreateParamList method. \e'll go o·er this in more detail in a second. but
understand íor now that runs through the Match object. checks the params group. and
places the ·alues íor anv captured parameters into a string arrav.
Next. we create a ·ariable named replacementValue to store the ·alue used to o·erwrite the
token íunction.
Aíter that. we ha·e a switch statement that allows us to implement whate·er íunctionalitv we
want íor regular expression based tokens. All vou ha·e to do to include additional
íunctionalitv is add the íunction name to the switch statement. implement whate·er logic vou
want íor that particular íunction. and set the replacementValue ·ariable to whate·er it is vou
want the íunction token to ha·e as its replacement text.
58 bv Damon Armstrong
\ou can see that I'·e thrown in two íunctions íor demonstration purposes. 1he ADD
íunction runs through each parameter. con·erts it into an integer. and adds up all the ·alues
as it mo·es along. 1he CON1LN1 íunction accepts two parameters: a group and a kev. It
passes those ·alues to the ContentManager.GetContent íunction. which returns the
appropriate content.
And lastlv. the method uses index and length iníormation írom the Match object to remo·e
the token íunction and insert the ·alue írom replacementValue in its place. completing the
process.
I mentioned earlier that we iterate through the Match iníormation backwards. and here's whv.
Lach Match object has an Index propertv containing the location where we need to make a
replacement. It's ·erv likelv that the replacement text and the text being replaced will not be
the same length. so updating the string means all index iníormation írom that point on is
oííset bv the diííerence in string lengths. \e could keep track oí the oííset. but it's easier to
a·oid the situation bv starting at the back oí the string and mo·ing towards the íront. \hen
vou work in this direction. the index iníormation is still ·alid because vou are accessing part
oí the string which has vet to be lengthened or shortened bv replacements.
CreateParamLlst methed
1he CreateParamList method is an extremelv simple method. \ou pass in a Match object.
and it looks through it and creates a string arrav containing anv parameter ·alues the objected
captured. It begins bv creating a string arrav whose size matches the number oí captures
íound in the params group. 1hen it iterates through each parameter ·alue and assigns it to
the appropriate index oí the arrav. It then returns that arrav.
Notice the slight diííerence in acquiring ·alues írom the params group as compared to the
functionName group. \hen vou were accessing the functionName group. vou could get
the ·alue using:
MatchObject.Groups["functionName"].Value
Because vou were onlv looking íor a single ·alue. Ií vou are looking íor something that can
be captured multiple times. then vou can access a list oí the captured ·alues ·ia the Captures
propertv. like this:
MatchObject.Groups["params"].Captures[i].Value
\ou can still call something like this:
MatchObject.Groups["params"].Value
But vou will onlv get the last parameter the regular expression engine located.
Updatlne the Render methed
And íinallv. vou call RunRegularLxpressionReplacements írom the Render method. I
ha·e it setup to run beíore executing the normal token replacement call because it seems
more ílexible. \ou mav want to return a "standard token" írom the (ontent Management
svstem and ha·e it replaced when vou run standard replacements.
Regular Lxpression Based 1oken Replacement in ASP.NL1 59

Checklne eut the beme Appllcatlen
Download the demo application and extract it to a location oí vour choosing on vour hard
dri·e. Start Visual Studio. and open the web site írom where·er it is that vou chose to sa·e it.
1here are íi·e íiles not including code-behinds, in the demo:
Iile Name Purpose
App_(ode`1okenReplacementPage.cs (ontains the 1okenReplacementPage class that
pro·ides pages with token replacement íunctionalitv
App_(ode`(ontentManager.cs (ontains code íor a "smoke-and-mirrors" e.g. íake,
content management svstem íor use in the demo
Deíault.aspx Demonstrates regular expression based token
replacement íunctionalitv
\eb.coníig \ebsite coníiguration
1ake a look at the markup in the Deíault.aspx page and notice the ·arious token íunctions
and standard tokens that appear throughout. Also take a look at the code behind íile because
vou will see a token íunction set in code to demonstrate that vou can put a token anvwhere
and. as long as it is output to the page source. it is replaced bv the regular expression token
replacement mechanism.
\hen vou run the demo application. vou will see that the token íunctions are replaced with
their respecti·e ·alues when the page appears in vour browser.
Cencluslen
Regardless oí whether vou íullv understand regular expressions or not. this approach should
pro·ide vou with a íairlv poweríul regular-expression based token replacement mechanism.
Plus. can easilv add new token íunctions without ha·ing to rework the regular expression.
though I would recommend placing the logic íor those íunctions in their own methods to
a·oid ha·ing a ton oí logic directlv in one giant case statement.
lappv coding!

60 bv Gaidar Magdanuro·
A CUMPL£1£ URL R£WRl1lNG $ULU1lUN FUR A$P.N£1 2.0
01 March 2007
by Gaidar Magdanurov
1his article describes a complete solution íor URL rewriting in ASP.NL1 2.0. 1he solution
uses regular expressions to speciív rewriting rules and resol·es possible diííiculties with
postback írom pages accessed ·ia ·irtual URLs.
Why use URL rewrltlne?
1he two main reasons to incorporate URL rewriting capabilities into vour ASP.NL1
applications are usabilitv and maintainabilitv.
Usablllty
It is well-known that users oí web applications preíer short. neat URLs to monstrous
addresses packed with diííicult to comprehend querv string parameters. lrom time to time.
being able to remember and tvpe in a concise URL is less time-consuming than adding the
page to a browser's ía·orites onlv to access later. Again. when access to a browser's ía·orites
is una·ailable. it can be more con·enient to tvpe in the URL oí a page on the browser address
bar. without ha·ing to remember a íew kevwords and tvpe them into a search engine in order
to íind the page.
(ompare the íollowing two addresses and decide which one vou like more:
1, http:´´www.somebloghost.com´Blogs´Posts.aspx·\ear~2006&Month~12&Dav~10
2, http:´´www. somebloghost.com´Blogs´2006´12´10´
1he íirst URL contains querv string parameters to encode the date íor which some blog
engines should show a·ailable postings. 1he second URL contains this iníormation in the
address. gi·ing the user a clear idea oí what he or she is going to see. 1he second address also
allows the user to hack the URL to see all postings a·ailable in December. simplv bv
remo·ing the text encoding the dav '10': http:´´www.somehost.com´Blogs´2006´12´.
Malntalnablllty
In large web applications. it is common íor de·elopers to mo·e pages írom one directorv to
another. Let us suppose that support iníormation was initiallv a·ailable at
bttp:´´rrr..ovebtogbo.t.cov´ívto´Copyrigbt.a.p· and bttp:´´rrr..ovebtogbo.t.cov´´vpport´Covtact..
a.p·. but at a later date the de·elopers mo·ed the Copyright.aspx and Contacts.aspx
pages to a new íolder called Help. Users who ha·e bookmarked the old URLs need to be
redirected to the new location. 1his issue can be resol·ed bv adding simple dummv pages
containing calls to Response.Redirectver tocatiov,. lowe·er. what ií there are hundreds
oí mo·ed pages all o·er the application directorv· 1he web project will soon contain too
manv useless pages that ha·e the sole purpose oí redirecting users to a new location.
Lnter URL rewriting. which allows a de·eloper to mo·e pages between ·irtual directories just
bv editing a coníiguration íile. In this wav. the de·eloper can separate the phvsical structure oí
the website írom the logical structure a·ailable to users ·ia URLs.
Natlve URL mapplne ln A$P.N£1 2.0
ASP.NL1 2.0 pro·ides an out-oí-the-box solution íor mapping static URLs within a web
application. It is possible to map old URLs to new ones in web.coníig without writing anv
lines oí code. 1o use URL mapping. just create a new urlMappings section within the
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 61

system.web section oí vour web.config íile and add the required mappings the path ~/
points to the root directorv oí the web application,:
<urlMappings enabled="true">
<add url="~/Info/Copyright.aspx" mappedUrl="~/Help/Copyright.aspx" />
<add url="~/Support/Contacts.aspx" mappedUrl="~/Help/Contacts.aspx" />
</urlMappings>
1hus. ií a user tvpes bttp:´´rrr..ovebtogbo.t.cov´´vpport´Covtact..a.p·. he can then see the page
located at bttp:´´rrr..ovebtogbo.t.cov´íetp´Covtact..a.p·. without e·en knowing the page had
been mo·ed.
1his solution is íine ií vou ha·e onlv two pages that ha·e been mo·ed to other locations. but
it is completelv unsuitable where there are dozens oí re-located pages. or where a reallv neat
URL needs to be created.
Another possible disad·antage oí the nati·e URL mapping technique is that ií the page
Contacts.aspx contains elements initiating postback to the ser·er which is most
probable,. then the user will be surprised that the URL
bttp:´´rrr..ovebtogbo.t.cov´´vpport´Covtact..a.p· changes to bttp:´´rrr..ovebtogbo.t.cov´íetp´
Covtact..a.p·. 1his happens because the ASP.NL1 engine íills the action attribute oí the form
l1ML tag with the actual path to a page. So the íorm renders like this:
<form name="formTest" method="post"
action="http://www.simple-talk.com/Help/Contacts.aspx" id="formTest">
</form>
1hus. URL mapping a·ailable in ASP.NL1 2.0 is almost alwavs useless. It would be much
better to be able to speciív a set oí similar URLs in one mapping rule. 1he best solution is to
use Regular Lxpressions íor o·er·iew see \ikipedia http:´´en.wikipedia.org
´wiki´Regular_expression, and íor implementation in .NL1 see MSDN -
http:´´msdn.microsoít.com´en-us´librarv´ms9¯2966.aspx,. but an ASP.NL1 2.0 mapping
does not support regular expressions. \e thereíore need to de·elop a diííerent solution to
built-in URL mapping.
1he URL rewrltlne medule
1he best wav to implement a URL rewriting solution is to create reusable and easilv
coníigurable modules. so the ob·ious decision is to create an l11P Module íor details on
l11P Modules see MSDN Magazine - http:´´msdn.microsoít.com´en-us´magazine
´deíault.aspx, and implement it as an indi·idual assemblv. 1o make this assemblv as easv to
use as possible. we need to implement the abilitv to coníigure the rewrite engine and speciív
rules in a web.config íile.
During the de·elopment process we need to be able to turn the rewriting module on or oíí
íor example ií vou ha·e a bug that is diííicult to catch. and which mav ha·e been caused bv
incorrect rewriting rules,. 1here should. thereíore. be an option in the rewriting module
coníiguration section in web.config to turn the module on or oíí. So. a sample
coníiguration section within web.config can go like this:
<rewriteModule>
<rewriteOn>true</rewriteOn>
<rewriteRules>
<rule source="(\d+)/(\d+)/(\d+)/"
destination="Posts.aspx?Year=$1&amp;Month=$2&amp;Day=$3"/>
<rule source="(.*)/Default.aspx"
destination="Default.aspx?Folder=$1"/>
</rewriteRules>
62 bv Gaidar Magdanuro·
</rewriteModule>
1his means that all requests that run like: bttp:´´tocatbo.t´!eb´200ó´]2´]0´ should be
internallv redirected to the page Posts.aspx with querv string parameters.
Please note that web.config is a well-íormed XML íile. and it is prohibited to use the
svmbol & in attribute ·alue strings. In this case. vou should use &amp: instead in the
destination attribute oí the rule element.
1o use the rewriteModule section in the web.config íile. vou need to register a section
name and a section handler íor this section. 1o do this. add a configSections section to
web.config:
<configSections>
<sectionGroup name="modulesSection">
<section name="rewriteModule" type="RewriteModule.
RewriteModuleSectionHandler,
RewriteModule"/>
</sectionGroup>
</configSections>
1his means vou mav use the íollowing section below the configSections section:
<modulesSection>
<rewriteModule>
<rewriteOn>true</rewriteOn>
<rewriteRules>
<rule source="(\d+)/(\d+)/(\d+)/"
destination="Post.aspx?Year=$1&amp;Month=$2&amp;Day=$3"/>
<rule source="(.*)/Default.aspx"
destination="Default.aspx?Folder=$1"/>
</rewriteRules>
</rewriteModule>
</modulesSection>
Another thing we ha·e to bear in mind during the de·elopment oí the rewriting module is
that it should be possible to use '·irtual' URLs with querv string parameters. as shown in the
íollowing: bttp:´´rrr..ovebtogbo.t.cov´200ó´]2´]0´.´ort~De.cc´ortßy~Date. 1hus we ha·e to
de·elop a solution that can detect parameters passed ·ia querv string and also ·ia ·irtual URL
in our web application.
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 63


So. let`s start bv building a new (lass Librarv. \e need to add a reíerence to the System.Web
assemblv. as we want this librarv to be used within an ASP.NL1 application and we also want
to implement some web-speciíic íunctions at the same time. Ií we want our module to be
able to read web.config. we need to add a reíerence to the System.Configuration
assemblv.
Randllne the cenfleuratlen sectlen
1o be able to read the coníiguration settings speciíied in web.config. we ha·e to create a
class that implements the IConfigurationSectionHandler interíace see MSDN íor
details,. 1his can be seen below:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Web;
using System.Xml;


namespace RewriteModule
{
public class RewriteModuleSectionHandler : IConfigurationSectionHandler
{

private XmlNode _XmlSection;
private string _RewriteBase;
private bool _RewriteOn;

public XmlNode XmlSection
{
get { return _XmlSection; }
}

public string RewriteBase
{
get { return _RewriteBase; }
}

public bool RewriteOn
{
64 bv Gaidar Magdanuro·
get { return _RewriteOn; }
}
public object Create(object parent,
object configContext,
System.Xml.XmlNode section)
{
// set base path for rewriting module to
// application root
_RewriteBase = HttpContext.Current.Request.ApplicationPath + "/";

// process configuration section
// from web.config
try
{
_XmlSection = section;
_RewriteOn = Convert.ToBoolean(
section.SelectSingleNode("rewriteOn").InnerText);
}
catch (Exception ex)
{
throw (new Exception("Error while processing RewriteModule
configuration section.", ex));
}
return this;
}
}
}
1he (lass RewriteModuleSectionHandler will be initialized bv calling the Create
method with the rewriteModule section oí web.config passed as XmlNode. 1he
SelectSingleNode method oí the XmlNode class is used to return ·alues íor module
settings.
Uslne parameters frem rewrltten URL
\hen handling ·irtual URLS such as http:´´rrr. .ovebtogbo.t.cov´ßtog.´gaiaar´.´ort~..c that
is. a ·irtual URL with querv string parameters,. it is important that vou clearlv distinguish
parameters that were passed ·ia a querv string írom parameters that were passed as ·irtual
directories. Using the rewriting rules speciíied below:
<rule source="(.*)/Default.aspx" destination="Default.aspx?Folder=$1"/>,
vou can use the íollowing URL:
http:´´www. somebloghost.com´gaidar´·lolder~Blogs
and the result will be the same as ií vou used this URL:
http:´´www. somebloghost.com´Blogs´gaidar´
1o resol·e this issue. we ha·e to create some kind oí wrapper íor '·irtual path parameters'.
1his could be a collection with a static method to access the current parameters set:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Web;

namespace RewriteModule
{

public class RewriteContext
{
// returns actual RewriteContext instance for
// current request
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 65

public static RewriteContext Current
{
get
{
// Look for RewriteContext instance in
// current HttpContext. If there is no RewriteContextInfo
// item then this means that rewrite module is turned off
if(HttpContext.Current.Items.Contains("RewriteContextInfo"))
return (RewriteContext)
HttpContext.Current.Items["RewriteContextInfo"];
else
return new RewriteContext();
}
}

public RewriteContext()
{
_Params = new NameValueCollection();
_InitialUrl = String.Empty;
}

public RewriteContext(NameValueCollection param, string url)
{
_InitialUrl = url;
_Params = new NameValueCollection(param);

}

private NameValueCollection _Params;

public NameValueCollection Params
{
get { return _Params; }
set { _Params = value; }
}

private string _InitialUrl;

public string InitialUrl
{
get { return _InitialUrl; }
set { _InitialUrl = value; }
}
}
}
\ou can see írom the abo·e that it is possible to access '·irtual path parameters' ·ia the
RewriteContext.Current collection and be sure that those parameters were speciíied in
the URL as ·irtual directories or pages names. and not as querv string parameters.
Rewrltlne URLs
Now let's trv some rewriting. lirst. we need to read rewriting rules írom the web.config íile.
Secondlv. we need to check the actual URL against the rules and. ií necessarv. do some
rewriting so that the appropriate page is executed.
\e create an HttpModule:
class RewriteModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{}
}
\hen adding the RewriteModule_BeginRequest method that will process the rules
against the gi·en URL. we need to check ií the gi·en URL has querv string parameters and
66 bv Gaidar Magdanuro·
call HttpContext.Current.RewritePath to gi·e control o·er to the appropriate
ASP.NL1 page.
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Configuration;
using System.Xml;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.IO;
using System.Collections.Specialized;

namespace RewriteModule
{
class RewriteModule : IHttpModule
{

public void Dispose() { }

public void Init(HttpApplication context)
{
// it is necessary to
context.BeginRequest += new EventHandler(
RewriteModule_BeginRequest);
}

void RewriteModule_BeginRequest(object sender, EventArgs e)
{

RewriteModuleSectionHandler cfg =
(RewriteModuleSectionHandler)
ConfigurationManager.GetSection
("modulesSection/rewriteModule");

// module is turned off in web.config
if (!cfg.RewriteOn) return;

string path = HttpContext.Current.Request.Path;

// there us nothing to process
if (path.Length == 0) return;

// load rewriting rules from web.config
// and loop through rules collection until first match
XmlNode rules = cfg.XmlSection.SelectSingleNode("rewriteRules");
foreach (XmlNode xml in rules.SelectNodes("rule"))
{
try
{
Regex re = new Regex(
cfg.RewriteBase + xml.Attributes["source"].InnerText,
RegexOptions.IgnoreCase);


Match match = re.Match(path);
if (match.Success)
{
path = re.Replace(
path,
xml.Attributes["destination"].InnerText);


if (path.Length != 0)
{
// check for QueryString parameters
if(HttpContext.Current.Request.QueryString.Count != 0)
{
// if there are Query String papameters
// then append them to current path
string sign = (path.IndexOf('?') == -1) ? "?" : "&";
path = path + sign +
HttpContext.Current.Request.QueryString.ToString();
}
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 6¯

// new path to rewrite to
string rew = cfg.RewriteBase + path;
// save original path to HttpContext for further use
HttpContext.Current.Items.Add(
"OriginalUrl",
HttpContext.Current.Request.RawUrl);
// rewrite
HttpContext.Current.RewritePath(rew);
}
return;
}
}
catch (Exception ex)
{
throw (new Exception("Incorrect rule.", ex));
}
}
return;
}

}
}

\e must then register this method:
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(RewriteModule_BeginRequest);
}
But this is just halí oí the road we need to go down. because the rewriting module should
handle a web íorm's postbacks and populate a collection oí '·irtual path parameters'. In the
gi·en code vou will not íind a part that does this task. Let's put '·irtual path parameters' aside
íor a moment. 1he main thing here is to handle postbacks correctlv.
Ií we run the code abo·e and look through the l1ML source oí the ASP.NL1 page íor an
action attribute oí the íorm tag. we íind that e·en a ·irtual URL action attribute contains a
path to an actual ASP.NL1 page. lor example. ií we are using the page ~/Posts.aspx to
handle requests like bttp:´´rrr. .ovebtogbo.t.cov´ßtog.´200ó´]2´]0´Detavtt.a.p·. we íind the
action~"´Posts.aspx". 1his means that the user will be using not the ·irtual URL on
postback. but the actual one: bttp:´´rrr. .ovebtogbo.t.cov´ßtog.a.p·. 1his is not what we want
to use here! So. a íew more lines oí code are required to achie·e the desired result.
lirst. we must register and implement one more method in our HttpModule:
public void Init(HttpApplication context)
{
// it is necessary to
context.BeginRequest += new EventHandler(
RewriteModule_BeginRequest);
context.PreRequestHandlerExecute += new EventHandler(
RewriteModule_PreRequestHandlerExecute);
}

void RewriteModule_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if ((app.Context.CurrentHandler is Page) &&
app.Context.CurrentHandler != null)
{
Page pg = (Page)app.Context.CurrentHandler;
pg.PreInit += new EventHandler(Page_PreInit);
}
}
68 bv Gaidar Magdanuro·
This method checks if the user requested a normal ASP.NET page and adds a handler for
the PreInit event of the page lifecycle. This is where RewriteContext will be populated
with actual parameters and a second URL rewriting will be performed. The second rewriting
is necessary to make ASP.NET believe it wants to use a virtual path in the action attribute of
an HTML form.
void Page_PreInit(object sender, EventArgs e)
{
// restore internal path to original
// this is required to handle postbacks
if (HttpContext.Current.Items.Contains("OriginalUrl"))
{
string path = (string)HttpContext.Current.Items["OriginalUrl"];

// save query string parameters to context
RewriteContext con = new RewriteContext(
HttpContext.Current.Request.QueryString, path);

HttpContext.Current.Items["RewriteContextInfo"] = con;

if (path.IndexOf("?") == -1)
path += "?";
HttpContext.Current.RewritePath(path);
}
}
linallv. we see three classes in our RewriteModule assemblv:

Reelsterlne RewrlteMedule ln web.cenfle
1o use RewriteModule in a web application. vou should add a reíerence to the rewrite
module assemblv and register HttpModule in the web application web.config íile. 1o
register HttpModule. open the web.config íile and add the íollowing code into the
system.web section :
<httpModules>
<add name="RewriteModule" type="RewriteModule.RewriteModule, RewriteModule"/>
</httpModules>
Uslne RewrlteMedule
1here are a íew things vou should bear in mind when using RewriteModule:
• It is impossible to use special characters in a well-íormed XML document which is
web.config in its nature. \ou should thereíore use l1ML-encoded svmbols
instead. lor example. use &amp: instead oí &.
• 1o use relati·e paths in vour ASPX pages. vou should call the ResolveUrl method
inside l1ML tags: <img src="<°=ResolveUrl("~/Images/1est.jpg")°>" />.
Note. that ~´ points to the root directorv oí a web application.
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 69

• Bear in mind the greediness oí regular expressions and put rewriting rules to
web.config in order oí their greediness. íor instance:
<rule source="Directory/(.*)/(.*)/(.*)/(.*).aspx"
destination="Directory/Item.aspx?
Source=$1&amp;Year=$2&amp;ValidTill=$3&amp;Sales=$4"/>
<rule source="Directory/(.*)/(.*)/(.*).aspx"
destination="Directory/Items.aspx?
Source=$1&amp;Year=$2&amp;ValidTill=$3"/>
<rule source="Directory/(.*)/(.*).aspx"
destination="Directory/SourceYear.aspx?
Source=$1&amp;Year=$2&amp;"/>
<rule source="Directory/(.*).aspx"
destination="Directory/Source.aspx?Source=$1"/>
• Ií vou would like to use RewriteModule with pages other than .aspx. vou should coníigure IIS to
map requests to pages with the desired extensions to ASP.NL1 runtime as described in the next
section.
ll$ Cenfleuratlen: uslne RewrlteMedule wlth extenslens ether
than .aspx
1o use a rewriting module with extensions other than .aspx íor example. .html or .xml,. vou
must coníigure IIS so that these íile extensions are mapped to the ASP.NL1 engine
ASP.NL1 ISAPI extension,. Note that to do so. vou ha·e to be logged in as an
Administrator.
Open the IIS Administration console and select a ·irtual directorv website íor which vou
want to coníigure mappings.
Windows XP (IIS 5)
Virtual Directory "RW"
¯0 bv Gaidar Magdanuro·

Windows 2003 Server (IIS 6)
Default Web Site
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 ¯1


1hen click the Configuration… button on the Virtual Directorv tab or the lome
Directorv tab ií vou are coníiguring mappings íor the website,.
Windows XP (IIS 5)
¯2 bv Gaidar Magdanuro·

Windows 2003 Server (IIS 6)
A (omplete URL Rewriting Solution íor ASP.NL1 2.0 ¯3


Next. click on the Add button and tvpe in an extension. \ou also need to speciív a path to an
ASP.NL1 ISAPI Lxtension. Don't íorget to uncheck the option Check that file
exists.

Ií vou would like to map all extensions to ASP.NL1. then íor IIS 5 on \indows XP vou ha·e
onlv to map . extension to the ASP.NL1 ISAPI extension. But íor IIS 6 on \indows 2003
vou ha·e to do it in a slightlv diííerent wav: click on the Insert… button instead oí the Add…
button. and speciív a path to the ASP.NL1 ISAPI extension.
¯4 bv Gaidar Magdanuro·

Cencluslens
Now we ha·e built a simple but ·erv poweríul rewriting module íor ASP.NL1 that supports
regular expressions-based URLs and page postbacks. 1his solution is easilv implemented and
gi·es users the abilitv to use short. neat URLs íree oí bulkv Ouerv String parameters. 1o start
using the module. vou simplv ha·e to add a reíerence to the RewriteModule assemblv in
vour web application and add a íew lines oí code to the web.config íile. whereupon vou
ha·e all the power oí regular expressions at vour disposal to o·erride URLs. 1he rewrite
module is easilv maintainable. because to change anv '·irtual' URL vou onlv need to edit the
web.config íile. Ií vou need to test vour application without the module. vou can turn it oíí
in web.config without modiíving anv code.
1o gain a deeper insight into the rewriting module. take a look through the source code and
example attached to this article. I belie·e vou'll íind using the rewriting module a íar more
pleasant experience. than using the nati·e URL mapping in ASP.NL1 2.0.
1ake Row-Le·el (ontrol oí \our GridView ¯5

1AK£ RUW-L£v£L CUN1RUL UF ¥UUR GRlbvl£W
27 September 2007
by Jonas Stawski
The GridView in ASP.NET seems to give the programmer few configurable options at first, but
when you start using the DataBound events, then it starts to become surprisingly versatile.
Manlpulate the Grldvlew centrel ln A$P.N£1 te dlsplay yeur data
the rleht way
1he GridView in ASP.NL1 is a ·erv poweríul control that pro·ides an easv interíace to add.
update. delete. and displav data. 1he GridView also lets vou períorm other tasks ·erv easilv.
such as paging and sorting vour data. but not e·ervthing is as straightíorward as it seems.
Most oí the time. we would displav the data exactlv as it comes out oí our datasources. but
sometimes we mav need to manipulate the text. cells. and rows to íit our needs. In this article
I will explain how to use the e·ents and properties oí the GridView to allow us to customise
the wav the data appears.
1aklne advantaee ef the Grldvlew events and prepertles
GridView is great íor ·erv simple tables. but the real world is not alwavs as straightíorward as
we would like it to be. Sometimes we need to speciív the íormat oí the data. and the wav it is
rendered in the table. more exactlv. Manv people will tell vou to use other tvpe oí controls
such as the DataList íor this. because it gi·es the user more choices in the wav that the grid is
rendered. Uníortunatelv the DataList. unlike the Grid·iew. does not ha·e all the íeatures such
as paging and sorting that are commonlv required. So ií vou still need or want to use the
GridView. but also need more control on the wav that the table is rendered. vou can use the
GridView e·ents and properties.
1he most used e·ent oí the GridView is the RowDataBound. 1his e·ent is íired e·erv time
a row is bound to data. \hat does this mean to us. the de·elopers· 1his means that.
whene·er this e·ent is íired. we will ha·e access to the current row and all oí its data. so we
can manipulate the table. row. cells. and or controls oí the table accordinglv. I will come back
to this later.
Other important e·ents are the DataBound and the Load e·ent. 1he Load e·ent íires when
the GridView is loaded and has not been attached to anv data vet. In this e·ent the user can
set properties such as the color oí the border. themes. or anv other rendering options that are
not dependent on the data itselí. 1he DataBound is similar to the RowDataBound in that
both are íired aíter a bound e·ent has happened. 1he diííerence is that DataBound is íired
once aíter the entire Grid has been bound: while the RowDataBound is íired e·erv time a
row is bound. meaning it will almost alwavs be íired more than once. So vou can use the
DataBound to manipulate the table based on the data contained in it.
¯6 bv Jonas Stawski

IIGURL 4: L·ents oí the GridView
1he RewbataBeund ls yeur frlend.
Let`s look at the parameters needed íor the RowDataBound e·ent. Like e·erv .NL1 e·ent it
has two parameters: sender oí tvpe object and e oí tvpe GridViewRowL·entArgs. 1he
sender parameter contains a reíerence oí the GridView that is íiring the e·ent and e contains
a ·erv important propertv named Row which reíerences the row that is being bound. 1he
Row propertv is ·erv important. 1able 1 contains the most used properties oí the
GridViewRow taken írom the MSDN documentation.
Property Description
Attributes Gets the collection oí arbitrarv attributes íor
rendering onlv, that do not correspond to properties
on the control.inherited írom \eb(ontrol,
(ells Gets a collection oí 1able(ell objects that represent
the cells oí a row in a 1able control.inherited írom
1ableRow,
DataItem Gets the underlving data object to which the
GridViewRow object is bound.
RowIndex Gets the index oí the GridViewRow object in the
Rows collection oí a GridView control.
Row1vpe Gets the row tvpe oí the GridViewRow object.
1able J: Most used properties oí the Row
Imagine that vour boss asks vou to create a table oí all the products with their price and Units
in Stock and Units on Order. So vou would simplv create a GridView with a SqlDataSource
with the íollowing querv: select ProductName. UnitPrice. UnitsInStock. UnitsOnOrder írom
Products. 1his is a ·erv easv and straightíorward task. ligure 5 displavs the end result.
1ake Row-Le·el (ontrol oí \our GridView ¯¯


IIGURL 5: Lnd result oí the GridView displaving the products.
\our boss sees the page and savs he would lo·e to be able to quicklv distinguish all the
products that need to be reordered. and also the products that ha·e alreadv been reordered.
So vou could then simplv tell him that not onlv will vou displav the numbers as thev are. but
also displav the products that need to be reordered in red and the ones that ha·e been
reordered in blue. \our boss lo·es the idea. but vou ha·e no clue how to do that with a
GridView. 1he wav to do this is to use the RowDataBound e·ent. 1he code on ligure 6
shows how to accomplish this simple. but not intuiti·e task.
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

//We're only interested in Rows that contain data

//get a reference to the data used to databound the row
DataRowView drv = (DataRowView)e.Row.DataItem;
if (Convert.ToInt32(drv["UnitsInStock"]) == 0)

{

//The current product has 0 items in stock
e.Row.Font.Bold = true; //Make the font bold
e.Row.ForeColor = System.Drawing.Color.Red; //Set the text color red
if (Convert.ToInt32(drv["UnitsOnOrder"]) > 0)

{

//The current out of stock item has already been ordered
//Make it blue
e.Row.ForeColor = System.Drawing.Color.Blue;

}

}

}

}
IIGURL 6: (ode íor setting íore color oí each row.
¯8 bv Jonas Stawski
1he GridView íires the RowDataBound e·ent on e·erv row. including the header and
íooter. 1hereíore we need to make sure. when the e·ent is íired. that it is íired on a DataRow.
Ií it is. then we get a reíerence to the DataRowView. which represents the row oí the
datasource to which the row oí the GridView was tied to. In our case this represents a row
írom the database result set. Please note that the e.Row.DataItem returns an object. 1he
reason íor that is that vou can bind a GridView to anv item that implements the I(ollection
interíace: Data1able. DataSet. List. Arrav. etc. 1he tvpe oí the DataItem will ·arv with the
DataSource used. Once we get a reíerence to the DataRowView. we then check to see ií that
Product is out oí stock or ií it is in the process oí restocking and set the lore(olor oí the
row equal to the correct color. ligure ¯ shows the end result.

Iigure 7: 1he new GridView with the color change
\our boss is now in a good mood. and knows that vou can do a lot íor him: le thereíore
wants a new report. 1his new report will include all the products bv (ategorv. le does not
want to see the categorv repeated e·erv time. \our data comes in the íormat displaved in
ligure 8.

IIGURL 8: Data írom datasource
1he idea is the same as in the pre·ious example. \e need to implement the RowDataBound
e·ent and check when the CategoryName changes. Ií it does. then we will displav it. ií it
does not. then we hide it. But to make it more complicated. not onlv are we going to hide it.
1ake Row-Le·el (ontrol oí \our GridView ¯9

but we are going merge the rows together. ligure 9 displavs the code needed to make this
happened and ligure 10 displavs the end result.
string previousCat = "";

int firstRow = -1;

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

//We're only interested in Rows that contain data
//get a reference to the data used to databound the row
DataRowView drv = ((DataRowView)e.Row.DataItem);



if (previousCat == drv["CategoryName"].ToString())

{

//If it's the same category as the previous one
//Increment the rowspan
if (GridView1.Rows[firstRow].Cells[0].RowSpan == 0)

GridView1.Rows[firstRow].Cells[0].RowSpan = 2;

else
GridView1.Rows[firstRow].Cells[0].RowSpan += 1;

//Remove the cell
e.Row.Cells.RemoveAt(0);

}

else //It's a new category
{

//Set the vertical alignment to top
e.Row.VerticalAlign = VerticalAlign.Top;

//Maintain the category in memory
previousCat = drv["CategoryName"].ToString();

firstRow = e.Row.RowIndex;

}

}

}
IIGURL 9: (ode to get rid oí the repeated categorv
80 bv Jonas Stawski

IIGURL J0: Products bv (ategorv
1he code is ·erv similar to the pre·ious example. 1his time we are using the help oí two
global ·ariables: previousCat and firstRow. 1he ·ariable previousCat is used to sa·e the
categorv oí the pre·ious row. so ií the categorv is the same we increment the row span oí
the row containing the categorv and then delete the íirst cell oí the current row. \hene·er a
new categorv arri·es we lea·e the row intact and sa·e the previousCat and firstRow to their
corresponding ·alues. Please note that this code will onlv work correctlv ií the data is sorted
bv the categorv name.
\our boss is now ecstatic: he knows he had made a great in·estment bv hiring vou. le knows
vou are on a roll and that is whv he wants to change the íirst report bv adding an image right
next to the discontinued products. ligure 11 shows the code to accomplish the task and
ligure 12 shows the end result.
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

//We're only interested in Rows that contain data

//get a reference to the data used to databound the row
DataRowView drv = (DataRowView)e.Row.DataItem;
if (Convert.ToInt32(drv["UnitsInStock"]) == 0)

{

//The current product has 0 items in stock
e.Row.Font.Bold = true; //Make the font bold
e.Row.ForeColor = System.Drawing.Color.Red; //Set the text color red
if (Convert.ToInt32(drv["UnitsOnOrder"]) > 0)

1ake Row-Le·el (ontrol oí \our GridView 81

{

//The current out of stock item has already been ordered
//Make it blue
e.Row.ForeColor = System.Drawing.Color.Blue;

}

}

if ((bool)drv["Discontinued"])

{

//Discontinued product

//Add the image
Image img = new Image();

img.AlternateText = "Discontinued Product";

img.ImageAlign = ImageAlign.AbsMiddle;

img.ImageUrl = "arrow_down.gif";

img.Width = Unit.Pixel(10);

img.Height = Unit.Pixel(11);

e.Row.Cells[0].Controls.Add(img);



//Add the text as a control
e.Row.Cells[0].Controls.Add(new LiteralControl("&nbsp;" +
e.Row.Cells[0].Text));

}

}

}
IIGURL JJ: (ode íor setting íore color and discontinued image.
82 bv Jonas Stawski

IIGURL J2: Lnd result oí the discontinued product
1he code is the same code as in the íirst example with the addition oí the discontinued part.
\e íirst ha·e to check whether the current product is discontinued. Ií it is. then we create a
new image and add it to the controls collection oí the cell. Since we are adding a control to
the collection. the GridView gi·es prioritv to the controls and ignores the text propertv.
which is whv we need to add the text as a control. 1his makes the GridView render the cell as
an image with text right next to it.
Cencluslen
lrom design time to run time íormatting. írom declarati·e programming to imperati·e
programming. the GridView is one oí the most complex and simple controls in the
ASP.NL1 arsenal. It allows vou to create simple tables with ·erv little programming and also
allows vou to ha·e íull control oí its íormatting to create ·erv complicated grids. Not onlv is
the GridView a ·erv poweríul control íor normal dav to dav íunctions such as displaving.
adding. updating. and deleting data or íor nice íeatures such as paging and sorting. but also is
a ·erv poweríul control in that it enables vou to ha·e íull control oí how it renders itselí. I
hope the next time vou are creating a grid íor vour boss vou can plav around with the
GridView L·ents and properties to come up with a neat solution.
lappv Programming!
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 83

£NRANC£ ¥UUR W£B$l1£ Wl1R A$P.N£1 AJAX £X1£N$lUN$
01 May 2007
by Dan Wahlin
AJAX Asvnchronous Ja·aScript and XML, is arguablv one oí the most hvped technologv
acronvms around. 1he primarv ad·antage oí using AJAX is that page reíreshes can be
minimized. allowing users to get the iníormation thev need quicklv and easilv through a more
rich and íunctional interíace. Ajax accomplishes this bv using Ja·aScript and an XmlHttp
object to send data asvnchronouslv írom the browser to the \eb ser·er and back. So.
although AJAX has a lot oí marketing-hvpe surrounding it. the beneíits it oííers can't be
denied.
Microsoít's ASP.NL1 AJAX Lxtensions pro·ide de·elopers with a quick and simple wav to
add AJAX íunctionalitv into anv ASP.NL1 \ebsite. without requiring in-depth knowledge oí
Ja·aScript or other AJAX technologies. 1his article will demonstrate how vou can add AJAX
capabilities into a new or existing \ebsite bv using a new ASP.NL1 AJAX ser·er-side control
called the UpdatePanel. \ou'll see how the UpdatePanel control can be used to allow
portions oí a page to be updated without requiring the entire page to be posted back to the
ser·er and reloaded in the browser. Additional topics co·ered include:
1he role oí the ScriptManager control
Nesting UpdatePanel controls
• 1riggering asvnchronous requests
• Pro·iding progress indicators to end users
• Interacting with the UpdatePanel on the client-side. using the
PageRequestManager class.
Let's get started bv discussing how to get the ASP.NL1 AJAX Lxtensions installed and
coníigured.
lnstalllne the A$P.N£1 AJAX extenslens
Beíore vou can use the ASP.NL1 AJAX UpdatePanel control vou need to install the
ASP.NL1 AJAX Lxtensions. a·ailable írom http:´´ajax.asp.net/. on vour de·elopment
machine. Once installed. a new \ebsite template titled "ASP.NL1 AJAX-Lnabled \ebsite"
will appear when vou íirst create a new \ebsite using Visual Studio .NL1 2005 or \eb
De·eloper Lxpress. Select this template when vou want to add ASP.NL1 AJAX íunctionalitv
into \eb pages.
1he ASP.NL1 AJAX Lxtensions relv upon special l11P handlers and modules to handle
and respond to asvnchronous requests sent írom a browser. Bv creating a new ASP.NL1
AJAX \ebsite in Visual Studio .NL1. a web.coníig íile will automaticallv be created that
contains reíerences to a ScriptResource.axd handler as well as a ScriptModule module. 1he
ScriptResource handler dvnamicallv loads Ja·aScript íiles into pages that le·erage AJAX
íeatures while ScriptModule manages l11P module íunctionalitv that is related to request
and response messages.
Ií vou'd like to upgrade an existing \ebsite and add ASP.NL1 AJAX íunctionalitv into it
vou'll need to ensure that vou manuallv update vour site's web.coníig íile with the proper
entries. 1he easiest wav to do this is to create a new ASP.NL1 AJAX-Lnabled \ebsite as
mentioned earlier, and then copv the AJAX-speciíic portions oí web.coníig to vour original
web.coníig íile. \ou'll oí course need to ensure that the ASP.NL1 AJAX Lxtensions are also
installed on vour production ser·er beíore deploving the updated site.
84 bv Dan \ahlin
Addlne AJAX functlenallty lnte A$P.N£1 Web Ferms
Beíore the ASP.NL1 AJAX Lxtensions were released. de·elopers had to relv on custom
AJAX libraries to AJAX-enable a \eb site. \hile these libraries were quite poweríul and
worked in cross-browser scenarios. thev oíten required a íamiliaritv with Ja·aScript. and e·en
XML or \eb Ser·ice technologies. Some oí the írameworks were´are susceptible to (SRl
(ross Site Request lorgerv, attacks. wherebv a hacker could hi-jack AJAX messages and
potentiallv steal iníormation. lortunatelv. the ASP.NL1 AJAX Lxtensions oííer a secure
AJAX íramework that includes a new ser·er control called the UpdatePanel that hides
Ja·aScript complexities.
1he UpdatePanel control allows vou to íocus on the íunctionalitv oí vour application rather
than on programming and understanding AJAX-speciíic technologies. It períorms
asvnchronous postback operations that update a portion oí a page rather than the entire page
itselí. 1his technique is oíten reíerred to as "partiat·page vpaate.". 1he UpdatePanel control
works bv intercepting postback requests triggered bv the page and con·erting them into
asvnchronous postback calls. which are then sent to the ser·er using the browser's XmlHttp
object. It relies on client-side scripts managed bv the ASP.NL1 AJAX íramework to períorm
this asvnchronous íunctionalitv.
Beíore using the UpdatePanel control vou must íirst add into vour page an important
ASP.NL1 AJAX control. called the ScriptManager. 1he ScriptManager handles loading all
oí the necessarv client-side scripts that are required bv the UpdatePanel and other AJAX
controls in order to make asvnchronous AJAX calls. \ou can drag a ScriptManager control
onto a page írom the VS.NL1 toolbox or add it directlv into the source code as shown next:
<asp:ScriptManager ID="ScriptManager1" runat="server" />
Once a ScriptManager control is added. an UpdatePanel control can then be deíined in the
page. 1he UpdatePanel acts as a container in much the same wav as the standard ASP.NL1
Panel control. lowe·er. content embedded within the UpdatePanel is wrapped within a
template named Content1emplate. Anv content placed within the Content1emplate is
automaticallv AJAX-enabled. 1his means that button click or other postback e·ents triggered
bv controls in the template will be intercepted and con·erted into asvnchronous AJAX calls
to the ser·er.
Listing 1 demonstrates how to use an UpdatePanel control in a page in order to AJAX
enable a GridView, which allows paging through customer data:
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:GridView ID="GridView1" runat="server" CellPadding="4"
DataSourceID="SqlDataSource1" ForeColor="#333333"
GridLines="None" AllowPaging="True" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="CustomerID">
<Columns>
<asp:BoundField DataField="CompanyName"
HeaderText="CompanyName" SortExpression="CompanyName" />
<asp:BoundField DataField="ContactName"
HeaderText="ContactName" SortExpression="ContactName" />
<asp:BoundField DataField="ContactTitle"
HeaderText="ContactTitle" SortExpression="ContactTitle" />
</Columns>
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
Listing 1. Using the UpdatePanel control and (ontent1emplate.
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 85

As users page through customer records in the GridView control. or sort diííerent columns.
the UpdatePanel control automaticallv handles AJAX-enabling the call resulting in a partial-
page update rather than a complete page reíresh.
blsplaylne preeress
Although the UpdatePanel is simple to use. vou must take the end user into account when
using it. especiallv ií vou don't know how long an asvnchronous postback mav take to
complete. Long requests mav cause an end user to think that their request has íailed or hung
and thev mav start the request again. na·igate to a diííerent page or e·en close the browser.
1he solution is to pro·ide them with a ·isual progress indicator so that thev know that their
request is being processed.
1he ASP.NL1 AJAX lramework includes the UpdateProgress control that can be used to
pro·ide users with a ·isual indication oí whether or not their request is still being processed.
Listing 2 shows an example oí using the UpdateProgress control to displav an animated
image to an end user. while a \eb Ser·ice is called that retrie·es album iníormation.
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
&nbsp;Artist:&nbsp;
<asp:TextBox ID="txtArtist" runat="server" />
<asp:Button ID="btnSubmit" runat="server" Text="Get Albums" />

<asp:UpdateProgress ID="upProgress" runat="server"
DynamicLayout="false">
<ProgressTemplate>
<img src="Images/loading_animation_liferay.gif" />
</ProgressTemplate>
</asp:UpdateProgress>

<asp:GridView ID="GridView1" runat="server">
-- GridView contents omitted for brevity --
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
Listing 2. Using the UpdateProgress control.
\ou'll see that the UpdateProgress control has a Progress1emplate that contains the
content to show to the end user. while the UpdatePanel asvnchronous postback is
processed. Anv content tvpe oí content images. ílash mo·ies. ·ideos. etc., can be placed
inside oí the template. In cases where vou'd like the content to take up a íixed amount oí
space on the page as opposed to dvnamicallv being added. vou can set the DynamicLayout
propertv to íalse.
1he UpdateProgress control shown in Listing 2 is embedded directlv within the target
UpdatePanel. lowe·er. it can be embedded elsewhere in the page and linked to the
appropriate UpdatePanel bv setting its AssociatedUpdatePanelID propertv to the ID oí
the UpdatePanel. In cases where quick partial-page updates mav occur. and vou don't want
the UpdateProgress control to show its content. vou can set the DisplayAfter propertv to
the number oí milliseconds that vou'd like it to wait beíore displaving progress content.
DisplayAfter deíaults to a ·alue oí 500 milliseconds.
ligure 1 demonstrates the end-user eííect oí using the UpdateProgress control. As the
UpdatePanel is being reíreshed with album iníormation írom a call to the Amazon.com
\eb Ser·ice. a progress indicator is displaved directlv below the Artist textbox.
86 bv Dan \ahlin

Iigure J. Using the UpdateProgress control to gi·e ·isual íeedback to end users as a call is
made to the Amazon.com \eb Ser·ice.
Nesting UpdatePanel controls
Multiple UpdatePanel controls can be added into a page in cases where diííerent sections
need to make AJAX calls to the ser·er to a·oid reloading the entire page. In addition to
ha·ing multiple UpdatePanels in a page. vou can also nest UpdatePanels to pro·ide more
granular AJAX íunctionalitv. lor example. vou mav want to show a GridView control that
displavs iníormation and allows a user to drill-down into additional details to achie·e a
master-details stvle ·iew. ligure 2 shows an example oí doing this using two GridView
(ontrols.
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 8¯


Iigure 2. (reating a master-details ·iew oí customer and order data.
1o accomplish this tvpe oí master-details ·iew. an UpdatePanel is nested inside oí a
GridView's Item1emplate. as shown in Listing 3. As a user clicks the "View Orders"
LinkButton. within each row shown in ligure 2. the GridView within the nested
UpdatePanel is made ·isible. Anv paging or sorting requests made within the nested
UpdatePanel control will cause it to reload new data bv making asvnchronous AJAX
requests. while data in the parent GridView control is leít untouched.
<asp:UpdatePanel ID="up" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:GridView ID="GridView1" runat="server" CellPadding="4"
DataSourceID="SqlDataSource1" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False"
DataKeyNames="CustomerID" OnRowCommand="GridView1_RowCommand">
<Columns>
-- Bound fields omitted for brevity --
<asp:TemplateField ItemStyle-Width="300px">
<ItemTemplate>
<asp:LinkButton ID="lbOrders" runat="server"
Text="View Orders" CommandName="ViewOrders"
CommandArgument='<%# Container.DataItemIndex %>' />
<asp:UpdatePanel ID="upChild" runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<asp:GridView ID="gvOrders"
AllowPaging="true" PageSize="5"
Visible="false" runat="server"
AllowSorting="True"
AutoGenerateColumns="False">
<Columns>
-- Bound fields omitted --
</Columns>
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
<asp:SqlDataSource ID="sdsOrders" runat="server"
88 bv Dan \ahlin
ConnectionString="<%$ ConnectionStrings:ConnStr %>"
>
<SelectParameters>
<asp:Parameter Name="CustomerID"
DefaultValue="NULL" />
</SelectParameters>
</asp:SqlDataSource>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
Listing 3. (reating a master-details ·iew oí data using nested UpdatePanel control.
1he UpdatePanel control exposes an UpdateMode propertv that deíaults to a ·alue oí
"Alwavs". 1his means that anv asvnchronous request triggered anvwhere within the page will
cause the UpdatePanel to reíresh itselí. In cases where this beha·ior isn't desired. the
UpdateMode propertv can be assigned a ·alue oí "(onditional" so that onlv triggers
associated with the control or child controls embedded within the control's
Content1emplate can cause it to be reíreshed through an asvnchronous postback. Other
controls outside oí the UpdatePanel will not cause it to be reíreshed. Additional
iníormation about UpdatePanel triggers is co·ered in the next section.
1he parent UpdatePanel control in Listing 3 has its UpdateMode set to (onditional so that
anv asvnchronous postback operations caused bv the nested UpdatePanel or bv other
controls in the page do not cause the parent GridView control to be reíreshed. 1his
minimizes the number oí asvnchronous postback requests made to the ser·er.
Uslne trleeers
\hile controls inside oí an UpdatePanel control can cause it to períorm asvnchronous
postbacks. other controls deíined outside oí the UpdatePanel can also act as "triggers" that
cause the UpdatePanel to reíresh itselí with new data. 1wo tvpes oí triggers exist íor
UpdatePanels including the AsynchronousPostBack1rigger control and PostBack-
1rigger control.
An AsynchronousPostBack1rigger causes an UpdatePanel's content to be updated
asvnchronouslv when a speciíic control's e·ent is íired such as a Button's click e·ent or a
DropDownList's SelectedIndexChanged e·ent. A PostBack1rigger causes a regular
postback operation to occur that reloads the entire page. \hen AJAX-enabling vour \ebsites
vou'll normallv want to use the AsynchronousPostBack1rigger to stop postback operations
írom occurring.
Listing 4 shows how to deíine a DropDownList control as a trigger that can períorm a
reíresh oí the UpdatePanel's contents. 1his is done bv using the AsynchronousPostBack-
1rigger control. Notice that the ID oí the DropDownList control is deíined using the
ControlID propertv. and the e·ent that causes the partial-page update oí the UpdatePanel
is deíined using the LventName propertv. \hen the SelectedIndexChanged e·ent íires. an
asvnchronous postback is made to the ser·er and the UpdatePanel's content is reloaded.
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
AutoGenerateColumns="False"
DataKeyNames="CustomerID" DataSourceID="sdsCustomers">
<Columns>
-- Column definitions omitted for brevity --
</Columns>
</asp:GridView>
</ContentTemplate>
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 89

<Triggers>
<asp:AsyncPostBackTrigger ControlID="DropDownList1"
EventName="SelectedIndexChanged" />
</Triggers>
</asp:UpdatePanel>
Listing 4. Deíining triggers to períorm partial-page updates on the contents oí an
UpdatePanel.
In cases where vou'd like to pre·ent controls deíined within an UpdatePanel's
Content1emplate írom triggering an asvnchronous postback operation. vou can set the
UpdatePanel's ChildrenAs1riggers propertv to false and the UpdateMode to
"(onditional". Anv e·ents raised bv child controls in the Content1emplate oí the control
will be ignored. while e·ents raised bv triggers such as the one shown in Listing 4 will cause a
partial-page update to occur. ií needed.
Listing 5 shows an example oí setting the ChildrenAs1riggers propertv to íalse to pre·ent
LinkButtons clicked within a DataList control írom updating the contents oí an
UpdatePanel. \hile the LinkButtons don't cause the UpdatePanel to reíresh itselí. thev do
cause another UpdatePanel deíined in the page to be reíreshed so that additional details
about emplovees can be shown to the end user see ligure 3,.
<div style="width: 400px">
<div style="float: left; width: 200px;">
Territories:<br />
<asp:UpdatePanel ID="upTerritories" runat="server"
ChildrenAsTriggers="false" UpdateMode="conditional">
<ContentTemplate>
<asp:DataList ID="dlTerritories" runat="server"
CellPadding="4" DataKeyField="TerritoryID"
DataSourceID="sdsTerritories" ForeColor="#333333"
OnItemCommand="DataList1_ItemCommand">
<ItemTemplate>
<asp:LinkButton runat="server" ID="lbTerritories"
Text='<%# Eval("TerritoryDescription") %>'
CommandName="TerritoryClick"
CommandArgument='<%#Eval("TerritoryID") %>'
Style="text-decoration: none;" /><br />
</ItemTemplate>
</asp:DataList>
</ContentTemplate>
</asp:UpdatePanel>
<asp:SqlDataSource ID="sdsTerritories" runat="server"
ConnectionString="<%$ ConnectionStrings:ConnStr %>"
SelectCommand="SELECT TOP 20 [TerritoryID], [TerritoryDescription]
FROM [Territories] ORDER BY [TerritoryDescription]">
</asp:SqlDataSource>
</div>
<div style="float: right; width: 200px">
Employees:<br />
<asp:UpdatePanel ID="upEmployees" runat="Server">
<ContentTemplate>
<asp:DataList ID="dlEmployees" runat="server" CellPadding="4"
DataSourceID="sdsEmployees" ForeColor="#333333">
<ItemTemplate>
<asp:Label ID="NameLabel" runat="server"
Text='<%# Eval("Name") %>'></asp:Label><br /><br />
</ItemTemplate>
</asp:DataList>
</ContentTemplate>
</asp:UpdatePanel>
<asp:SqlDataSource ID="sdsEmployees" runat="server"
ConnectionString="<%$ ConnectionStrings:ConnStr %>"
SelectCommand="SELECT Employees.FirstName + ' ' +
Employees.LastName AS Name FROM Employees INNER JOIN
EmployeeTerritories ON Employees.EmployeeID =
EmployeeTerritories.EmployeeID WHERE
90 bv Dan \ahlin
(EmployeeTerritories.TerritoryID = @TerritoryID)">
<SelectParameters>
<asp:Parameter Name="TerritoryID" />
</SelectParameters>
</asp:SqlDataSource>
</div>
</div>
Listing 5. Using the (hildrenAs1riggers propertv to stop child control's oí an UpdatePanel
írom triggering an asvnchronous postback operation.
1he results oí this are displaved in ligure 3. \hen a territorv is clicked. emplovees in that
territorv will be shown.

Iigure 3. LinkButtons deíined in an UpdatePanel trigger a separate UpdatePanel to
displav additional details about emplovees. 1he UpdatePanel where the controls are deíined
is not updated since the ChildrenAs1riggers propertv is set to íalse.
Handling UpdatePanel events on the client
1he UpdatePanel control handles all asvnchronous requests to the ser·er. so vou don't ha·e
to worrv about writing Ja·aScript code. 1his is great írom a producti·itv and maintenance
standpoint but there will be times when vou want to know when an UpdatePanel request is
going to start. or when data has returned and is about to be updated in the page. lor example.
vou mav want to access data returned bv an UpdatePanel and use it to make another
asvnchronous postback request. Or. vou mav want to animate the UpdatePanel as a request
is started so that the end user sees what is happening and knows when content in a particular
area oí a page has been reíreshed. All oí this can be done bv using a Ja·aScript class pro·ided
bv the ASP.NL1 AJAX script librarv. called the PageRequestManager.
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 91

1he PageRequestManager li·es in the Sys.WebIorms namespace in the ASP.NL1 AJAX
script librarv and allows vou to tie into requests and responses processed bv one or more
UpdatePanels in a page. It's responsible íor managing partial-page updates that occur within
a page as well as managing the client page liíe-cvcle. Bv using it vou can tie into se·eral
diííerent e·ents and act upon them. PageRequestManager also exposes properties and
methods that can be used to check ií asvnchronous requests are in process. It can also be
used to abort existing requests and e·en cancel pending requests.
L·ents exposed bv the PageRequestManager class include initializeRequest.
beginRequest. pageLoading. pageLoaded and endRequest. 1he íollowing table pro·ides
more details about these e·ents and when thev are íired.
Lvent Description
initializeRequest Raised when an asvnchronous postback is íirst initialized.
1his e·ent can be used to cancel requests in cases where
another request is alreadv in process.
beginRequest Raised when an asvnchronous postback begins. 1his e·ent
can be used to animate an UpdatePanel container within a
page to pro·ide users with a ·isual cue that an asvnchronous
postback request is starting.
pageLoading Raised aíter data is recei·ed írom an asvnchronous postback
request but beíore the data is updated in the page. 1his
e·ent can be used in cases where vou'd like to change data
returned írom the ser·er using Ja·aScript or pro·ide an
eííect on the UpdatePanel to signiív that content is about
to be updated.
pageLoaded Raised when data in a page is updated aíter a svnchronous
or asvnchronous request.
endRequest Raised aíter an asvnchronous postback request is completed.
Anv errors that occurred during the request can be
processed here.
1o access the PageRequestManager vou can call its getInstance() method on the client-
side as shown next:
var prm = Sys.WebForms.PageRequestManager.getInstance();
Once the PageRequestManager object is a·ailable. vou can deíine e·ent handlers íor the
diííerent e·ents that it exposes and attach to them. Listing 6 shows how to attach an e·ent
handler to the endRequest e·ent and access data returned írom the asvnchronous postback
request.
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequest);


function EndRequest(sender, eventArgs)
92 bv Dan \ahlin
{
if (eventArgs.get_error() != undefined &&
eventArgs.get_error().httpStatusCode == '500')
{
var errorMessage = eventArgs.get_error().message;
eventArgs.set_errorHandled(true);
alert(errorMessage);
}
else
{
GetMap($get("hidField").value);
}
}
Listing 6. 1his code shows how to attach an e·ent handler to the endRequest e·ent oí the
PageRequestManager. Once a request is completed. errors are checked and ií none are
íound. data returned bv the request is accessed and passed to another method íor processing.
Notice that the parameter signature íor the LndRequest() e·ent handler mirrors the one
íound in the .NL1 íramework where the sender oí the e·ent as well as anv e·ent arguments
are passed as parameters. 1he eventArgs parameter can be used to check ií anv errors
occurred during the request bv calling the error propertv which can be used to access the
l11P status code oí the request. Ií a 500 error is íound then the code accesses the error
message bv calling the message propertv. marks that the error has been handled and shows
the error message to the end user. Ií no error occurs. a hidden íield. named hidIield. which
is returned írom the asvnchronous postback. is accessed to get a ·alue needed bv a GetMap()
method. which is used to displav a Virtual Larth map.
1he PageRequestManager can also be used to abort or cancel asvnchronous postback
requests bv handling the initRequest e·ent. Listing ¯ shows an example oí canceling a
request in cases where an UpdatePanel is in the process oí making a request and the end
user is impatientlv clicking a Reíresh button.
Sys.Application.add_init(Init);
var prm = null;

function Init(sender)

{
prm = Sys.WebForms.PageRequestManager.getInstance();
//Ensure EnablePartialRendering isn't false which will prevent
//accessing an instance of the PageRequestManager
if (prm)
{
if (!prm.get_isInAsyncPostBack())
{
prm.add_initializeRequest(InitRequest);
}
}
}


function InitRequest(sender,args)
{
if (prm.get_isInAsyncPostBack() & args.get_postBackElement().id ==
'btnRefresh') {
//Could abort current request by using: prm.abortPostBack();
Lnhance vour \ebsite with ASP.NL1 AJAX Lxtensions 93

//Cancel most recent request so that previous request will complete
//and display
args.set_cancel(true);
alert("A request is currently being processed. Please " +
"wait before refreshing again.");
}
}
Listing 7. 1his code shows how the PageRequestManager's initRequest e·ent can be used
to cancel a pending asvnchronous postback request.
1he code in Listing ¯ starts bv accessing an instance oí the PageRequestManager in the
Application's init phase and ensuring that an asvnchronous postback isn't alreadv in progress
on the page. Ií no asvnchronous postback is occurring. the InitRequest e·ent handler is
attached to the initializeRequest e·ent. Once the InitRequest handle is called. a call is
made to the PageRequestManager's isInAsyncPostBack propertv and the e·ent
argument's postBackLlement propertv the e·ent argument is oí tvpe
InitializeRequestLventArgs,. 1he isInAsyncPostBack propertv returns a Boolean ·alue
indicating ií an asvnchronous postback is currentlv in progress and postBackLlement
propertv pro·ides access to the control that triggered the request.
Ií an asvnchronous postback is alreadv in progress and a new request is triggered bv a button
with an ID oí btnRefresh. the pending request is cancelled bv assigning a ·alue oí true to
set_cancel. A message is then displaved to the end user letting them know that a request is
alreadv being processed and that thev need to wait. 1he PageRequestManager class can be
used to períorm se·eral other actions such as animating an UpdatePanel beíore a request is
made and aíter the response is processed.
Cencluslen
1he ASP.NL1 AJAX lramework pro·ides a simple and producti·e wav to add AJAX
íunctionalitv into new or existing \ebsites. \ith a minimal amount oí eííort vou can make
vour applications períorm more eííicientlv and pro·ide end users with a richer experience
than traditional \eb applications bv using partial-page updates.
In this article vou'·e seen how the ScriptManager and UpdatePanel controls can be used to
make asvnchronous postbacks írom the browser to the ser·er and how diííerent tvpes oí
triggers can initiate the requests. \ou'·e also seen how UpdatePanel controls can be nested
to pro·ide a master-details stvle ·iew oí data and how the UpdateMode and
ChildrenAs1riggers properties can control how and when an UpdatePanel's content is
updated. linallv. vou'·e seen how the PageRequestManager client-side class can be used to
notiív vou oí partial-page update requests and responses.

94 bv (hris Ullman
CALLlNG CRU$$ bUMAlN W£B $£RvlC£$ lN AJAX
29 December 2006
by Chris Ullman
One oí the current ·ogues in web applications is the creation oí mashups. 1his in·ol·es the
marrving oí content and´or íunctionalitv írom two diííerent sources. O·er the last íew vears.
the opening up oí íormerlv proprietarv APIs írom the likes oí Google. \ahoo. Last.ím.
llickr. \ou1ube and Amazon has allowed de·elopers to implement in their own applications.
with simple one line calls to the requisite APIs. íeatures such as adding photos. maps.
booklists. ·ideos and plavlists.
1he APIs allow the de·eloper to select content through a series oí íilters such as location.
date. user id. or membership oí a particular group. 1he APIs are almost alwavs thinlv
disguised wrappers íor a web ser·ice.
\ith a lot oí modern applications now emploving Ajax techniques with indeed whole web
sites such as http:´´ajaxpatterns.org´ dedicated to Ajax patterns. and http:´´www.-
programmableweb.com´ dedicated to mashup applications which utilize a lot oí Ajax
techniques,. it makes sense to call the web ser·ices írom within vour own Ja·aScript.
lowe·er. there is one big hitch to this theorv. 1he XMLHttpRequest object is pre·ented
írom calling web ser·ices írom outside its own domain. 1his is sensible gi·en that ií vou
called a script in one place and it. in turn. called a script on another ser·er. it could lea·e an
applicationopen to all sorts oí malicious scripts. hacks and exploits.
lowe·er in the case oí web ser·ices. it isn't so sensible. \eb ser·ices are called with either
SOAP or l11P-GL1´POS1 requests and return iníormation in the same wav. 1hev are
designed to be called írom other domains. and in some wavs it's an inherent contradiction to
pre·ent them being called this wav. So. gi·en that there are plentv oí mashups out there. and
plentv oí applications de·eloped calling cross-domain web ser·ices. what do de·elopers do to
get around this limitation·
1here isn't a single answer to this question. but in this article I'll discuss the current most
popular techniques and what se·eral de·elopers are proposing to do get around this problem
more neatlv.
1he XMLRttpRequest ebject
At the heart oí the problem lies the XMLHttpRequest object. 1his object is central to
manv Ajax applications. although not an essential íeature. 1he XMLHttpRequest object was
introduced to IL5 as an Acti·eX control. although the original ·ersion was concei·ed in
Outlook \eb Access 2000. 1his was an Outlook web-mail ser·ice that allowed people to
access íunctionalitv such as download email. check calendars. and update contacts on the go.
bv allowing the application to issue its own client-side l11P requests. It was introduced as a
nati·e object to Mozilla íor release 1.0 and ended up in Saíari 1.2 and Opera ¯.60.
1he XMLHttpRequest object doesn't allow calls to be made írom code in one domain to a
web ser·ice in another. 1he latest craze íor mashups in·ol·es calling \eb Ser·ices írom APIs
made publiclv a·ailable bv companies such as Google. llickr. \ahoo. Last.ím and \ou1ube.
1his means that a call will alwavs ha·e to be made cross-domain. otherwise vou can't use
them. 1he best wav to illustrate the problem is to build an example.
1he currency cenverter example
In the example the application calls the íreelv a·ailable currencv con·ersion ser·ices írom
webservicex.net. 1here are two drop down lists containing the list oí currencies. So íor
example. vou can select US Dollar in the íirst and Great British Pound Sterling in the second
and then click on a button. to get the current rate. \hen the button is click. Ja·aScript is used
(alling (ross Domain \eb Ser·ices in AJAX 95

to call the web ser·ice. 1he result is returned to the XMLHttpRequest object and ultimatelv
rendered in the page.
Our application must ha·e at least two sections. the íirst is the l1ML page and the second is
the script that calls the ser·ice. Our deíault.htm page is as íollows:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Ajax Currency Convertor</title>
<script type="text/javascript" src="Ajax.js"></script>
</head>
<body>
Currency To Convert From:
<select id="FromBox">
<option value="USD" selected="true">USD - U.S. Dollar</option>
<option value="GBP">GBP - British Pound</option>
<option value="EUR">EUR - Euro</option>
<option value="JPY">JPY - Japanese Yen</option>
</select>
Currency To Convert To:
<select id="ToBox">
<option value="USD">USD - U.S. Dollar</option>
<option value="GBP" selected="true">GBP - British Pound</option>
<option value="EUR">EUR - Euro</option>
<option value="JPY">JPY - Japanese Yen</option>
</select>
<br /><br />
<input id="button1" type="button" value="Click to convert currency"
onclick="initiateConversion()" />
<br /><br />
<table id="table1">
</table>
</body>
</html>
Our second section is the script ILAjax.js. Ií vou create both oí these items in Visual Studio.
vou'll be able to trv this íor vourselí. Our application passes the web ser·ice URL straight to
the XMLHttpRequest object using Ja·aScript code. ·ia three íunctions:
function initiateConversion()
{
xmlhttprequest = createRequestObject();
var url = "http://www.webservicex.net/CurrencyConvertor.asmx/
ConversionRate?FromCurrency=?FromCurrency="
+ document.getElementById("FromBox").value
+ "&ToCurrency="
+ document.getElementById("ToBox").value ;
xmlhttprequest.open("GET", url, true);
xmlhttprequest.onreadystatechange = getData;
xmlhttprequest.send(null);
}

function createRequestObject()
{
if (window.XMLHttpRequest)
{
return xmlhttprequest = new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
return xmlhttprequest = new ActiveXObject("Microsoft.XMLHTTP");
}
}
function getData()
{
if ((xmlhttprequest.readyState == 4) &&( xmlhttprequest.status == 200))
{
96 bv (hris Ullman
var myXml = xmlhttprequest.responseXML;
var xmlobject = null;
var XMLText = null;
if (window.ActiveXObject)
{
XMLText = myXml.childNodes[1].firstChild.nodeValue;
}
else
{
XMLText = myXml.childNodes[0].firstChild.nodeValue;
}

var table = document.getElementById("table1");
var row = table.insertRow(table.rows.length);
var tablecell = row.insertCell(row.cells.length);
tablecell.appendChild(document.createTextNode
(document.getElementById("FromBox").value
+ " to "
+ document.getElementById("ToBox").value));
var tablecell = row.insertCell(row.cells.length);
tablecell.appendChild(document.createTextNode(XMLText));
table.setAttribute("border", "2");
}
}
1he code starts with initiateConversion which creates an XMLHttpRequest object in the
íunction createRequestObject. then it sets the URL to point at the web ser·ice. and add
two ·ariables írom the drop down list boxes. one íor the "lrom" currencv and one íor the
"1o" currencv. \ou call the open method oí the XMLHttpRequest object. passing it the
URL. \ou then prime the onreadystatechange e·ent handler to run the getData íunction
when a response is returned írom the XMLHttpRequest object. 1he getData íunction
recei·es the XML. extracts it ·ia the DOM,. and injects it into a table.

Oí course this code won't work bv deíault due to the cross domain restrictions. so let's look
at the simplest. but least satisíactorv solution íirst.
lnternet £xplerer werkareund
Ií vou can be sure that vour target audience will onlv be using Internet Lxplorer. and won't
be too íussv about a securitv work-around. then there is an immediate solution a·ailable. \ou
can add the site to vour trusted sites. and reduce the securitv bv enabling the "Access data
sources across domains" option.
\ou can add a trusted site in IL ·ia the 1ools | Internet Options | Security tab. 1hen vou
can set the securitv íor trusted site ·ia the Custom level | Miscellaneous section. \ou'll see
that. bv deíault. the "Access data sources across domains" option is set to Prompt. lowe·er.
Prompt is unsatisíactorv because it will interrupt anv current application that is running with
a message box. asking the user to coníirm that thev want access to the ser·ice. Instead. set the
option to Lnable.
lowe·er. quite apart írom the problem that each user oí the application will need to
coníigure IL beíore thev can use it. lireíox doesn't e·en allow vou to períorm this
workaround. and the deíault beha·ior oí lireíox is not e·en to return an error. \ou'd ha·e to
(alling (ross Domain \eb Ser·ices in AJAX 9¯

check the error console to disco·er that an error had been raised. and to locate the source. So.
as vou can see. this is a íar írom uni·ersal solution.
Appllcatlen prexles
A common theme oí manv oí the solutions. instead. is getting Ja·aScript to call a proxv
program either on the client or the ser·er, which. in turn. calls the web ser·ice íor vou. 1he
output can be written to the response stream and then is a·ailable. ·ia the normal channels.
such as the response1ext and responseXML properties oí XMLHttpRequest.
Flash - Cressbemaln.xml
Ií vou ha·e a llash application as part oí the setup. then vou can make use oí an XML íile
that allow llash applications to call web ser·ices. and which can be legallv called. 1he Adobe
site e·en speciíies that one tvpical usage oí crossdomain.xml is when
"a mo·ie wishes to make a request to content hosted on a public ser·er. such as web ser·ices.
but is prohibited bv the llash Plaver cross-domain data access restrictions."
1he crossdomain.xml íile speciíies other domains that can be called. and is placed at the
root le·el oí the application on anv ser·er to which the llash mo·ie plaver has access. \ou
can then make the call to the web ser·ice ·ia ActionScript.
An example Crossdomain.xml íile is as íollows:
<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-access-from domain="*.macromedia.com" secure="false" />
<allow-access-from domain="*.adobe.com" secure="false" />
</cross-domain-policy>
Oí course this technique depends on vou using somewhere llash in vour application.
Another drawback is that it onlv works with the most recent ·ersions oí llash ·ersion ¯
onwards,. so ií one oí these two criteria isn't met vou will need another solution.
$erver-slde prexy
lor the ASP.NL1 de·eloper. probablv the best solution is to load the XML content using
ASP.NL1 and then return the content to the client side as pure XML. 1he downside oí this
is that vou are adding an extra laver oí indirection to vour Ja·aScript code. and it requires vou
to ha·e a ser·er hosting ASP.NL1 to call on. Oí course this is part and parcel oí ASP.NL1
de·elopment. but ií vou ha·e been de·eloping vour application onlv with Ja·aScript. then
ha·ing IIS might not ha·e been on vour list oí requirements.
In order to create an ASP.NL1 ser·er side proxv. vou ha·e to amend vour Ja·aScript code to
add another laver oí indirection. Instead oí calling the web ser·ice directlv. vou now use the
XMLHttpRequest object to call an ASP.NL1 page instead. and pass on anv rele·ant
·ariables.
lere is some sample Ja·aScript code:
var xmlhttprequest = null;

function initiateConversion()
{
xmlhttprequest = createRequestObject();
var url = "callservice.aspx?FromBox="
+ document.getElementById("FromBox").value
98 bv (hris Ullman
+ "&ToBox="
+ document.getElementById("ToBox").value ;
xmlhttprequest.open("GET", url, true);
xmlhttprequest.onreadystatechange = getData;
xmlhttprequest.send(null);
}
In turn the ASP.NL1 code can load the URL as an XML document and then vou can simplv
write the contents oí the InnerXML propertv back to the response stream
XmlDocument wsResponse = new XmlDocument();
string url = "http://www.webservicex.net/CurrencyConvertor.asmx/
ConversionRate?FromCurrency="
+ Request.QueryString["FromBox"].ToString()
+ "&ToCurrency="
+ Request.QueryString["ToBox"].ToString();
wsResponse.Load(url);
string XMLDocument = wsResponse.InnerXml;
Response.ContentType = "text/xml";
Response.Write(XMLDocument);
1he Ja·aScript callback íunction getData remains unchanged írom the pre·ious section.
cURL prexy
Ií vou ha·e access to PlP - or alternati·elv can't use ASP.NL1 - then there is another
alternati·e: vou can use the cURL client URL, librarv extensions in PlP. 1he Ja·aScript code
can be amended as íollows:
var path = 'http:// "http://www.webservicex.net/CurrencyConvertor.asmx/
ConversionRate?FromCurrency='
+ document.getElementById("FromBox").value
+ "&ToCurrency="
+ document.getElementById("ToBox").value;
var url = 'http://localhost/curl_proxy.php?ws_path='
+ encodeURIComponent(path);
xmlhttp.open('GET', url, true);
\our PlP code makes the call on behalí oí the Ja·aScript. bv adding the hostname to vour
URL. and using the cURL to períorm the in·ocation oí the web ser·ice. Although we're
passing the querv string to the PlP code. it can accept the data as either a GL1 or a POS1
request:
define ('HOSTNAME', 'http://www.webservicex.net/');

$path = ($_POST['ws_path']) ? $_POST['ws_path'] : $_GET['ws_path'];
$url = HOSTNAME.$path;

// Open the Curl session
$session = curl_init($url);

if ($_POST['ws_path']) {
$postvars = '';
while ($element = current($_POST)) {
$postvars .= key($_POST).'='.$element.'&';
next($_POST);
}
curl_setopt ($session, CURLOPT_POST, true);
curl_setopt ($session, CURLOPT_POSTFIELDS, $postvars);
}

// Return the call not the headers
curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

(alling (ross Domain \eb Ser·ices in AJAX 99

// call the data
$xml = curl_exec($session);

header("Content-Type: text/xml");

echo $xml;
curl_close($session);
1he principle is the same as the ASP.NL1 code: the proxv opens a request to the web ser·ice
íor us and returns the response as XML. 1he XML is written to the response stream where it
can be picked up bv the XMLHttpRequest object.
bynamlc $crlpt 1ae hack
Ií vou ha·e a web ser·ice that returns JSON Ja·aScript Object Notation íormat,. then
another option is open to vou. \ou can dvnamicallv create a script tag and assign the src
attribute to the location oí the web ser·ice. 1his will onlv work ií the web ser·ice can return
JSON. which is a íormat íor data exchange based on a subset oí Ja·aScript. JSON cannot
represent íunctions or expressions: it can onlv represent data. It can be easilv con·erted into a
Ja·aScript ·alue. and so is easv to reíerence ·ia a script.
Uníortunatelv the (urrencv (on·ersion web ser·ice oí webservicex.net doesn't oííer this
option. lowe·er all oí the \ahoo \eb ser·ices currentlv return JSON and one such example
is the ImageSearchService. 1his is a simple "Image Search Lngine". wherebv vou supplv the
name oí the person or item, oí whom vou photographs and it will return an object íor each
"hit".
lor example. the íollowing URLS web ser·ice calls, can be used to retrie·e a list oí all the
images oí Linstein on the web:
http://api.search.yahoo.com/ImageSearchService/V1/
imageSearch?appid=YahooDemo&query=Einstein&output=json
and:
http://api.search.yahoo.com/ImageSearchService/V1/
imageSearch?appid=YahooDemo&query=Einstein&output=
json&callback=ws_results
It's possible. in a íew lines oí code. to add this call to an Ajax application to create an image
search engine so that whoe·er vou tvpe the name oí. it will return the íirst 10 images it íinds
on the \eb in order. Our l1ML´ASPX page would look as íollows:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Dynamic.aspx.cs" Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Dynamic Script Tag Example</title>
<script type="text/javascript" src="ajax2.js"></script>
</head>
<body>
Find Images:
<input id="userinput" type="text" />
<input type="button" onclick="dynamicTag();" value="Search" />
<br />
<div id="PlaceImages"></div>
</body>
</html>
100 bv (hris Ullman
\hen the button is clicked. we make the call to the Ja·aScript íunction. dynamic1ag. which
dvnamicallv creates a <script> element. 1his script element has no src attribute. so we
dvnamicallv create one and we assign the src attribute's URL as a call to the web ser·ice. so
that when the page is opened. the web ser·ice is automaticallv called.
function dynamicTag()
{
var userinput = document.getElementById("userinput").value;
var request = "http://api.search.yahoo.com/ImageSearchService/V1/
imageSearch?appid=YahooDemo&query="
+ userinput
+ "&output=json&callback=getImages";
var head = document.getElementsByTagName("head").item(0);
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", request);
head.appendChild(script);
}
\e pass as a parameter to our web ser·ice the ·alue the user supplied to the text box. \e also
speciív that the output should be JSON in the parameters. and the name oí the callback
íunction that the thread oí execution should pick up at when control is returned to the client.
1he callback íunction is as íollows:
function getImages(JSONData) {
if (JSONData != null)
{
var div = document.getElementById("PlaceImages");
for (i=0; i<10; i++)
{
var image = document.createElement("image");
image.setAttribute("src", JSONData.ResultSet.Result[i].Url);
image.setAttribute("width", 100);
image.setAttribute("height", 100);
div.appendChild(image);
}
}
}
1he JSONData object has a ResultSet íormat. which returns a separate object íor each
result that is returned. 1he object returns 1itle. Summary. Url. ClickUrl. RefererUrl,
IileSize. IileIormat. Height. Width and 1humbnail properties. So. íor the íirst item in
our ResultSet in the example. the properties would be returned as íollows:
Title: "einstein.jpg"
Summary: "Selon Einstein, 98 % de la population ne serait pas capable
de résoudre l'énigme suivante. Ferez-vous partie des 2 %
les plus intelligents ? A vous de"
Url: "http://www.ac-creteil.fr/Colleges/93/jmoulinmontreuil/
mathematiques/enigmes/anciennes_enigmes2/gifs/einstein.jpg"
ClickUrl: "http://www.ac-creteil.fr/Colleges/93/jmoulinmontreuil/
mathematiques/enigmes/anciennes_enigmes2/gifs/einstein.jpg"
RefererUrl: "http://www.ac-creteil.fr/Colleges/93/jmoulinmontreuil/
mathematiques/enigmes/anciennes_enigmes2/enigme24.htm"
FileSize: "25828"
FileFormat: "jpeg"
Height: "291"
Width: "281"
Thumbnail: {...}
1he application then dvnamicallv creates an image and assigns the URL propertv as the src
attribute. \e loop through íor the íirst 10 images displaving each to the page as íollows:
(alling (ross Domain \eb Ser·ices in AJAX 101


1here are se·eral disad·antages to this technique. though. 1his solution is reliant on JSON
and so excludes quite a íew web ser·ices that don't ha·e a JSON option. Also. when vou use
dvnamic scripts. vou can't tell ií the script had loaded correctlv or not. as there is no return
írom the script: either it works or it doesn't. A íurther problem is that. in IL. the dvnamic
loading oí scripts stops all other processing. Most seriouslv. using this technique with Internet
IL5 and IL6 causes a potential memorv leak.
Future dlrectlens
Ií vou íind none oí these solutions completelv satisíving. and íeel that thev all in·ol·e hacks
or incomplete solutions. then vou're not alone here: so do I. 1here are a lot oí iís and buts
in·ol·ed. and vour choice oí solution will oíten be dictated not bv what is best. but what is
a·ailable to vou.
As a consequence there are se·eral other solutions being proposed bv diííerent de·elopers
and it's worth taking a brieí look at each.
FlashXMLRttpRequest
1his is a project bv Julian (ou·reur. which le·erages the existing crossdomain.xml íiles but
instead uses a IlashXMLHttpRequest proxv. 1his is alreadv in beta. although se·eral links
to it are now dead. It uses a small S\l íile to make GL1 and POS1 requests on vour behalí.
1he reason that this is more interesting than just using crossdomain.xml is that this object.
in addition. has S\l ·ersions that support earlier ·ersions oí llash and enables almost anv
browser that has llash installed, to make use oí the Crossdomain.xml íiles.
1he IlashXMLHttpRequest object has some limitations. placed upon it bv the ·ersion oí
llash it is using,. such as not being able to send or recei·e l11P status codes or headers. or
handle l11P Puts and Deletes. It's a pitv that links to the beta were dead at the time oí
publication oí this article. because the proposal is an interesting one.
CentextAenestlcXMLRttpRequest
(hris lolland's proposal at:
http:´´chrisholland.blogspot.com´2005´03´contextagnosticxmlhttprequest-iníormal.html
in·ol·es creating a second object. ContextAgnosticXMLHttpRequest. which would be
used instead oí the XMLHttpRequest in situations that in·ol·e cross domain calling.
1he ContextAgnosticXMLHttpRequest object would be able to pass securitv details to the
application allowing the application permission to call up speciíic ser·ices in other domains.
1his proposal runs in tandem with debate írom other de·elopers. and Microsoít. who
propose passing an extra l11P leader. which is notionallv called "X-Allow-loreign-losts."
1he ContextAgnosticXmlHttpRequest object would not expose anv data írom a ser·ice
without this header.
1his proposal is no íurther íorward than the iníormal Rl( stage. but indicates that manv
people are thinking along similar lines.
102 bv (hris Ullman
J$UNRequest
Last is JSONRequest which is Doug (rockíord's proposal at
http:´´json.org´JSONRequest.html.
le proposes a new browser ser·ice that will initiate a two-wav data exchange between the
browser and a JSON data ser·er. 1his relies on íuture ·ersions oí browsers incorporating this
íacilitv into the browser.
Doug proposes the creation oí a new object. JSONRequest not to be coníused with the
one we created earlier,. which can initiate GL1. POS1 or CANCLL requests. 1he data that
is sent and recei·ed ·ia the object is serialized into the JSON íormat bv the web ser·ice. 1he
object would be restricted to using JSON encoded ·alues.
As the proposal excludes the use oí cookies or passwords. it neatlv a·oids the problems oí
the browser being gi·en íalse authorization. It looks perhaps the most promising oí the three
alternati·es. gi·en that JSON is alreadv being ·iewed as a possible alternati·e to XML in some
situations. It also coníers certain beneíits such as being simpler. more readable and better
suited to data exchange. as opposed to document exchange which is what XML deals in.
Cencluslen
1here is no one períect wav to call web ser·ices cross domain using Ajax. (urrentlv I relv on
an ASP.NL1 ser·er-side proxv as a solution: although ultimatelv a solution in·ol·ing JSON
looks to be preíerable. As things stand vou are not supposed to be able call a web ser·ice
cross domain using Ajax and anv attempt to do so in·ol·es some sort oí hackerv or
underhandedness. lowe·er ií web ser·ices popularitv is to continue to prosper. then a de-
íacto solution will ha·e to emerge. and more likelv than not. it will ha·e come írom the dustv
backrooms oí de·elopers. rather than as the latest add-on írom the browser makers.

Using \ebSer·ices with ASP.NL1 103

U$lNG W£B$£RvlC£$ Wl1R A$P.N£1
14 December 2007
by Daniel Penrod
Webservices without tears? Popup maps in your websites? Ajax-based Search-engines? No
problems, with Daniel's sample application to guide you.
SOAP \ebser·ices pro·ide a simple wav oí pro·iding public iníormation in websites.
Directories. maps. translations. searches and zipcode´postcode lookup all suddenlv become
quick and easv to implement. especiallv in .NL1. \indows Li·e \ebser·ice ha·e now joined
the market with a whole range oí ser·ices which are likelv to change the wav we use the \eb.
In this article I will show oíí the abilities oí ASP.NL1 2.0 to consume the \indows Li·e
Search \ebser·ice and use AJAX 1.0 and the Ajax(ontrol1oolkit to replicate some oí the
íeatures in Microsoít`s search engine Li·e Search. I am going to take a more ad·anced
approach to the solution I wrote íor the (ode Project and pro·ide a richer o·erall experience
in this demo. I'·e pro·ided the source too so vou can trv it vourselí. and experiment
1his article will demonstrate the íollowing:
• (onsumption oí the \indows Li·e Search \ebser·ice and the abilitv to bind these
results to an ASP.NL1 Grid·iew control
• 1he use oí AJAX 1.0 to search and page through the search results
• Using the Ajax(ontrol1oolkit and an online dictionarv web ser·ice to create an auto
complete íunction that aids in íinding search words
• 1apping into the Li·e Search spelling suggestion ser·ice to displav a polite message to
the user oí a possible spelling suggestion íor their misspelled words,
• Lnhancing the search experience with a Li·e.Local lookup to displav listings out oí
the vellow pages with their associated local addresses
• 1aking the local addresses and pro·iding a dvnamic Microsoít Virtual Larth map oí
the location to include all the power and enhanced íeatures associated with the Virtual
Larth API.
Some oí the ad·antages oí using these web ser·ices include:
• A íull blown SOAP \ebser·ice allowing vou to take control oí the data
• 1he abilitv to search multiple websites írom vour companv and ´ or its partners
• Not ha·ing to worrv about indexing the content oí vour companv`s global websites,
• larnessing the power oí Li·e Search`s ad·anced search íeatures in vour queries
• Using Virtual Larth to map out streets in vour local area.
Some oí the limitations include:
• A limit oí 10.000 queries a dav to use the ser·ice íor íree. Note: Normallv each page
in the Grid·iew (ontrol will íire a new querv e·en though the search querv is the
same. 1his demo makes use oí sessions to pre·ent this írom happening. - Optional
in web.coníig,
• \ou can onlv return a maximum oí 50 results per querv.
104 bv Daniel Penrod
Gettlne started
$ettlne up the envlrenment
1he íirst steps are to set up vour demo´de·elopment en·ironment. 1his demo was created in
\indows XP Pro using IIS. 1he íollowing actions need to occur in order to run this demo
locallv.
Install ASP.NL1 2.0 http:´´www.windowsupdate.com´,
Install ASP.NL1 2.0 AJAX Lxtensions 1.0 http:´´asp.net´ajax´downloads´archi·e´,
11. Download and Setup the demo application in IIS and choose the ASP.NL1 2.0
íramework
12. Obtain a Kev írom Microsoít to use this \ebser·ice
http:´´search.msn.com´de·eloper,
13. Add the kev to the appropriate app setting in web.coníig
14. Optional, lollow the additional instructions in web.coníig to customize vour output.
It is suggested that vou open this website up in Visual Studio 2005 or greater and take
ad·antage oí IntelliSense and explore the methods. properties and ·arious classes that make
up the Li·e \ebser·ice and this demo.
$ettlne up web.cenfle
1he app key for this web service
Get the kev írom here ... http:´´search.msn.com´de·eloper
and insert it into Add the appropriate app setting in web.config
<add key="SearchLic" value=""/>
1he dictionary service for autocomplete
(hoose a dictionarv írom here: Aona\are's DictSer·ice
In this demo I use \ordNet r, 2.0 wn,
<add key="DictService" value="wn" />
1he Windows Live Search expression
Bv deíault I lea·e this kev blank to search the entire Internet but vou could use an
expression such as:
{frsh=100} {popl=100} (site:www.ci.fayetteville.nc.us OR
site:jobs.ci.fayetteville.nc.us OR site:www.faypwc.com OR site:www.fcpr.us OR
site:flyfay.ci.fayetteville.nc.us OR site:police.ci.fayetteville.nc.us OR
site:www.ccbusinesscouncil.org OR site:www.co.cumberland.nc.us) -
www.ci.fayetteville.nc.us/portal/council_meeting_minutes/
1his expression can be built using the Ad·ance Search leature oí \indows Li·e
http:´´www.li·e.com´,. It basicallv states to make the search ha·e 100° íresh content
with a popularitv oí 100° and search the sites in parenthesis with the exception oí the
council meeting minute`s directorv.
Using \ebSer·ices with ASP.NL1 105

<add key="SearchSites" value=""/>
1he Results Size
1he limit is 50 results. Bv deíault I set the results size to 25.
<add key="ResultsSize" value="25"/>
Logging
Most errors get encapsulated in the AJAX 1.0 abvss. 1o make things easier on me when
de·eloping this application I created an optional logging íeature. Bv deíault I ha·e this
turned oíí. but ií vou decide to set this to true then vou must pro·ide a phvsical path to
write the log íiles.
<add key="IsLogging" value="false"/>
<add key="ErrorLogPath" value="C:\Inetpub\wwwroot\Windows.Live.Search\Logs\"/>
Location for Local.Live Services
Add the longitude and latitude oí vour local area. 1he deíault location oí this demo is
Seattle. \A with a radius oí 25 miles. 1he ·alues should be written as doubles.
<add key="Latitude" value="47.603828" />
<add key="Longitude" value="-122.328567" />
<add key="Radius" value="25.0"/>
Sessions
Use this kev to turn sessions on or oíí. Sessions will help vou sa·e vour a·ailable
queries. MS gi·es vou a 10.000 dailv limit. \ith paging turned on in the GridView
(ontrol. each time a user selects a new page it will constitute a new search querv ií
sessions are turned oíí. 1urn the session switch on to a·oid this. Bv deíault I ha·e
sessions turned on.
<add key="IsSearchSession" value="true"/>
£xplerlne the WlndewsLlve$earch class
ltems te censlder
On a securitv note. the possibilitv oí cross site scripting getting stored in the results became a
real problem. 1his would not onlv wreck ha·oc on vour presentation laver markup. but more
importantlv it could ultimatelv send vour users to dangerous and inappropriate places on the
Internet. 1o sol·e this dilemma I reíerenced Microsoít`s AntiXssLibrary.dll in the class. I
then called the AntiXss.HtmlLncode() íunction íor each result returned bv the webser·ice.
1his will strip cross site scripts and secure vour output íor the client.
\hen consuming the results írom the \ebser·ice I immediatelv ran into null reíerence
exceptions íor íields that ultimatelv did not exist. lor example. a result set usuallv contained
the usual íields such as the title. URL. description. etc. Ií bv chance no iníormation was
indexed íor the description íield. the webser·ice returned null íor that particular item. I
created a íunction called CheckforNull that simplv returned an emptv string ií a null was
present. 1hat sol·ed that problem.
106 bv Daniel Penrod
Another item oí consideration was the use oí sessions. Storing objects in sessions isn`t a
good idea íor a number oí reasons. but when vou weigh the alternati·e it doesn`t seem so bad
anvmore. 1he limit oí íree queries a dav bv Microsoít is 10.000. Not so bad. huh· \ell let`s
sav vou ha·e one user who reallv wants to íind something and makes íi·e queries and vou
displav íi·e results a page íor the maximum 50 results total. 1his user also decides to look at
all 10 pages oí results íor each oí the íi·e queries. \ell instead oí íi·e queries. vou now ha·e
used 50 íor just that one person. And ií that person wanted to page backwards because thev
thought thev missed something then it would onlv add to the queries because each page
clicked in the Grid·iew control íires another instance oí the search and creates another querv.
1hat is deíinitelv a worse case scenario. but websites that recei·e a lot oí traííic ha·e to take
these things into consideration. \ith sessions acti·e. íi·e queries will remain íi·e queries. lor
those who ultimatelv hate the idea oí storing an object in a session then vou can alwavs turn
sessions oíí in web.coníig as stated earlier in this article.
1he $earch Methed
Oí course the main method is the search method.
Figure 1 – The main entry point of the class
public IList<LiveSearchResults> Search(string searchQuery)
{
// Basic checks
if ((searchQuery == null) ||
(searchQuery.Length == 0) ||
(searchQuery.Trim() == ""))
return null;

IList<LiveSearchResults> webCollection = new
List<LiveSearchResults>();

using (MSNSearchService s = new MSNSearchService())
{
SearchRequest searchRequest = new SearchRequest();
searchRequest = SetUpRequest(searchRequest, searchQuery,
SearchProperties);
SearchResponse searchResponse;
try
{
// If the searchQuery is the same
// Session flag checked in function
if (IsSameSearch(searchQuery))
searchResponse =
(SearchResponse)HttpContext.Current.Session["searchResponse"];
else // Use Live Search to get the Response
searchResponse = s.Search(searchRequest);

// Session flag checked in function
GenerateSession(searchResponse, searchQuery);
webCollection = CaptureWebResults(searchResponse);
}
catch (Exception e)
{
ErrorMsg = e.ToString();
}
finally
{
// If there was an error
// Logging flag checked in function
if (ErrorMsg.Length > 0)
LogMessage("There was an error with searchQuery: " +
searchQuery);
else
LogMessage("A successful search was made with
searchQuery: " +
searchQuery);
}
}

Using \ebSer·ices with ASP.NL1 10¯

return webCollection;
}
1his is all the client application is concerned with. In the order oí operations. this is how the
webser·ice is consumed ·ia this íunction.
15. It creates an instance oí the MSNSearchSer·ice
16. It creates a SearchRequest object
1¯. It calls a local method to set up and store all the setup iníormation into the newlv
created SearchRequest object
18. It creates a SearchResponse object
19. It queries and stores the search results into the searchResponse object. s.Search is the
querv itselí,
20. It sends the searchResponse oíí to another local method called (apture\ebResults to
make a generic list oí the results.
21. 1hen it hands that list oíí to a local list object called web(ollection and returns the
web(ollection to the client.
22. In this case. the client then binds the web(ollection to a Grid·iew control.
1hose eight items are essentiallv all vou need to know about the logic oí consuming the
MSNSearchSer·ice in ASP.NL1.
\ou then ha·e the optional session logic in there and the optional logging logic. 1he
IsSameSearch method is a local method to check whether the querv is the same. At the same
time there is a check with web.coníig to see ií sessions are on. Ií all is a go. it pulls the
searchResponse írom the session instead oí íiring s.Search again. which will create another
querv. 1his will ultimatelv spare vour 10.000 querv dailv limit írom getting consumed so íast!
1he LogMessage method has a ílag that checks whether logging is turned on. LrrorMsg is a
public propertv oí the class and is exposed in the LogMessage íunction.
1he main local methods oí this class are:
• SetUpRequest - lere vou ha·e the opportunitv to let \indows Li·e know what
and how vou want vour results to be returned. I set up three main Requests in this
íunction. 1he íirst is the ob·ious web request. 1he second request is the spelling
request. which will return the misspelled words. 1he third request is the Phonebook
request íor all those Li·e.Local results.
• CaptureWebResults - 1his is the heart oí the class. It takes e·ervthing in the web
ser·ice response and stores the data in generic lists. lere we also secure the data and
ensure there are no null ·alues. 1his method íires up another method to handle anv
spelling suggestions.
• HandleSpellingSuggestion - 1his method I got straight írom the Li·eSearch
sdk. It pulls the response írom the response object and then turns it into a readable
string that`s readv íor printing.
• CheckForNull - As mentioned earlier in this article. a search response mav contain
null íields. 1his method ensures there will be no null ·alues stored in the list.
1he Web Cllent
1here are manv wavs a client can go about handling the search method. I decided to go the
ObjectDataSource method oí handling it. I also decided to use the AJAX 1.0 librarv íor
searching and paging and the AJAX(ontrol1oolkit íor the autocomplete íunctionalitv.
108 bv Daniel Penrod
1he Ubjectbata$eurce
Just writing that makes me want to cringe. I guess it wasn`t as scarv as I originallv thought.
1he main challenge was íiguring out how to expose the public properties to the web client íor
the right instance oí the class.
So aíter much trial and error I tried:
Figure 2 – Getting the instance of the class WindowsLiveSearch thisObject = new
WindowsLiveSearch();
protected void ObjectDataSource1_ObjectCreated
(object sender, ObjectDataSourceEventArgs e)
{
thisObject = (WindowsLiveSearch)e.ObjectInstance;
}
Now I ha·e the instance oí the class used bv the ObjectDataSource. and I can expose the
public properties to the \eb (lient.
Another road block came when I wanted to get the total count oí the results set. 1he
ObjectDataSource has a Rows.(ount method but that onlv returns the count oí the ·isible
rows on the screen. not the entire results set.
So to sol·e this I used this logic in the ObjectDataSource1_Selected method:
Figure 3 – Getting the return value try
{
IList<LiveSearchResults> resultsCollection = new List<LiveSearchResults>();
resultsCollection = (IList<LiveSearchResults>)e.ReturnValue;
resultsTotal = resultsCollection.Count;
if (resultsTotal == 0)
Instructionlbl.Text = "Your search provided no results.";
}
catch (System.NullReferenceException)
{
Instructionlbl.Text = "Please enter a search term.";
}
I íirst created a local list object oí the Li·eSearchResults 1vpe. 1his tvpe contains public
properties that represent the web results returned bv the webser·ice. I used the
ObjectDataSourceStatusL·entArgs e to get the return ·alue. 1he return ·alue is what the
Search method returns. a generic list. So then I take that list and get the count.
1his not onlv ga·e me the opprotunitv to get the total results count írom the generic list. but
now I can detect whether a search pro·ided results or whether a search was e·en períormed
at all! So I threw an instruction label on the web íorm to print out those messages to the user.
Figure 4 – Handling Spelling if (thisObject.SpellingSuggestion.Length > 0)
{
Spellinglbl.Text = "Did you mean "
+ "<a href='javascript:void(0);' onclick='submitSpellingSuggestion()'
title='"
+ thisObject.SpellingSuggestion
+ "'>"
+ thisObject.SpellingSuggestion
+ "?</a>";
SpellingSuggestion.Text = thisObject.SpellingSuggestion;
}
else
Spellinglbl.Text = "";
Using \ebSer·ices with ASP.NL1 109

Still in the ObjectDataSource1_Selected method I check to see ií the spelling suggestion has
length. Ií there is a suggestion. I throw it inside a label..
Figure 5 – Handling the Phone Book Results// Get the Phone Results and bind them
PhoneResultsView.DataSource = thisObject.PhoneResults;
PhoneResultsView.DataBind();
thisObject.Dispose(); // Dispose of this instance
Since I ha·e access to mv public properties. I decided to store the PhoneResults in one ·ia the
class. I then bind that propertv to the PhoneResults Grid·iew (ontrol. I am now done with
the class object so I dispose oí it..
AJAX AJAX AJAX
Mv mother thinks I in·ented a wav to clean websites when I mention that I use AJAX in mv
web applications. Mv wiíe can tell anvone I don`t like to clean! So what am I reíerring to· \ell
the AJAX librarv írom Microsoít oí course! \ith a íew ser·er tags vou can be implementing
some poweríul AJAX íeatures. In this application all I did was surround the portion oí the
page I wanted updated in the AJAX 1.0 UpdatePanel tag.
Figure 6 – The AJAX 1.0 UpdatePanel tag<asp:UpdatePanel ID="UpdatePanel1"
runat="server">
... The Gridview for the web results
... The Gridview for the phonebook results
... The instruction label
... The spelling label
... yada yada yada!
</asp:UpdatePanel>

1hat is all there is to that! 1hanks Microsoít!
Figure 7 - Auto(omplete

1he AJAXCentrel1eelKlt and the AuteCemplete functlen
1his was the íun part oí the application. \hat happens when vou mix an online dictionarv
web ser·ice with the AJAX(ontrol1oolKit· \ell nothing. unless vou take the
Auto(ompleteLxtender and put it to use. I basicallv just copied o·er the example Microsoít
made in the toolkit itselí and consumed an online dictionarv web ser·ice instead. So the logic
is this. \hen vou íinallv tvpe at least 3 letters in the search íield the Auto(ompleteLxtender
will call a local webser·ice to handle the operation. 1his local ser·ice then calls the dictionarv
web ser·ice o·er the Internet and returns words based upon the 3 letter preíix vou pro·ided.
(ool. huh· \ell to make things e·en cooler vou can choose diííerent dictionaries to get
diííerent results. 1his setting is coníigured in web.coníig.
110 bv Daniel Penrod
vlrtual £arth APl
1o top things oíí. I decided to add one more piece to this solution: Virtual Larth. 1he Virtual
Larth API oííers a wav íor map enthusiast to displav maps ·ia Ja·aScript. \hat I wanted to
do was present the Li·e.Local results in a wav that a user could mouse o·er them and get a
map oí the address in the results. So where would I displav this map· 1o sol·e this problem I
downloaded the latest ·ersion oí o·erlibmws. which was spawned írom the original o·erlib
librarv created bv Lrik Bosrup.
ligure 8 - 1he o·erlibmws popup displaving a ·irtual earth map

Aíter mousing o·er a result on the right. a Ja·aScript call throws the address up to the
o·erlibmws handler. 1his simple Ja·aScript íunction throws an iírame in the popup bubble
which contains all the logic necessarv to search íor the local address and displav the map. 1he
iírame is actuallv a .NL1 webíorm that takes the address in the íorm oí a OuervString and
inserts it into the client side Virtual Larth code. On the screen vou can take ad·antage oí all
the poweríul íeatures Virtual Larth pro·ides. including a 3d ·iew Aíter a small installation,
and Bird`s eve ·iew oí the location.
\ou can easilv use Google Maps API instead. but I íound Virtual Larth to be more poweríul
and useíul than Google Maps and at the last minute I made a call to use Virtual Larth.
$ummary
1here are a number oí wavs to use Li·e \ebser·ices to customize vour own solutions.
Because the Search API is a íull blow SOAP webser·ice vou ha·e control oí the data. 1he
interoperabilitv oí \indows Li·e Ser·ices and ASP.NL1 allows vou to easilv incorporate a
number oí íeatures in a short amount oí de·elopment time. In short. there are a number oí
wavs these ser·ices can beneíit businesses and organizations bv helping users íind data íaster
and helping de·elopers looking íor a quick solution to sa·e a íew kevstrokes.

Gathering RSS leeds using Visual Studio and RSS.NL1 111

GA1R£RlNG R$$ F££b$ U$lNG vl$UAL $1UblU ANb
R$$.N£1
04 May 2007
by John Papa
Integrating RSS íeeds into applications has become ·erv popular. 1here are se·eral tools
such as Microsoít Outlook 200¯, a·ailable to read blogs and news írom RSS íeeds. 1here are
also some tools a·ailable such as RSS.NL1 and the ASP.NL1 team's RSS 1oolkit, that can
aid vou in the de·elopment oí customizing íeeds in vour applications. 1his article will use the
RSS.NL1 assemblv. which can be íreelv downloaded and used írom http:´´www-
.rssdotnet.com´.
Appllcatlen evervlew
1his article will explain how to create a custom \indows Ser·ice that retrie·es posts írom
multiple RSS íeeds and then stores them in a SOL Ser·er database. Once vou store the posts
in the database vou can then retrie·e them and displav them on a web site. so vou can
combine multiple íeeds and their posts in a list on a web page. or perhaps in a \inlorm
application. \hen the \indows Ser·ice retrie·es the posts írom each RSS íeed. it íilters out
anv duplicate posts beíore sa·ing them to the database.
.tt .ovrce coae tor tbe !ivaor. ´errice i. araitabte to be aorvtoaaea a. rett a. tbe ´Oí .cript. to geverate
tbe aataba.e iv ´Oí ´errer. trov bttp:´´rrr..ivpte·tat/.cov´covtevt´tite.a.b·.tite~:0]
ligure 1 shows a high le·el o·er·iew oí the application. 1he \indows ser·ice will poll a list
oí RSS íeeds using the RSS.NL1 toolkit at a deíined inter·al. 1he list oí RSS íeeds and their
URLs are stored in a local SOL Ser·er DB. Using the RSS.NL1 librarv. each íeed's posts are
retrie·ed and examined. Duplicate posts are ignored. but new posts are inserted into a SOL
Ser·er database.

ligure 1 - Gathering posts írom íeeds
$ettlne up the database
\ou can execute the entire database setup script írom the DBScript.sql íile in the zipped
code íile attached to this article. 1he database is used to store two tables:
• leed - a list oí íeeds that will be polled íor their posts
• Post - a list oí the posts íor each íeed
1he tables and their basic schema are shown in ligure 2.
112 bv John Papa

ligure 2 - 1he BlogRoll Database
1he DBScript.sql íile íirst sets up the SOL Ser·er database and names it BlogRoll. \ou will
want to adjust the location oí the data and log íiles on vour P( to a place besides (:`. Once
the BlogRoll database is created. the Ieed and Post tables are created. 1he Ieed table stores
the title oí the íeed. the sequence in which the íeed should be polled in regard to other íeeds.
as well as the URL oí the íeed.
1he Ieed table also stores a LastPostGuidName column. 1his column represents a unique
identiíier íor the last post that was retrie·ed íor this íeed. \hen the íeed is polled again. this
column's ·alue is compared against each post that is retrie·ed. \hen a match is íound. the
\indows Ser·ice will know that the rest oí the posts ha·e alreadv been retrie·ed so it will
ignore the remaining duplicate posts. 1his column helps keep onlv unique posts in the Post
table. 1his column allows NULL ·alues because the íirst time a íeed is polled. there are no
posts alreadv in the database.
I started the Post table with a list oí two RSS íeeds which point to mv blog
http:´´codebetter.com´blogs´john.papa´rss.aspx, and the ADO.NL1 team's blog
http:´´blogs.msdn.com´adonet´rss.xml,. 1he DBScrpt.sql íile inserts these two íeeds as
samples that vou can start with. \ou can add more RSS íeeds to this list bv adding records to
the Post table.
1he Post table stores a reíerence to the Ieed table. so the posts can be associated with the
íeed írom which thev came. It also stores the title oí the post. the date the post was
published. its author. the bodv oí the post and a link directlv to the post.
linallv. the DBScript.sql íile creates a SOL Ser·er login and user named BlogRollUser. It
gi·es the BlogRollUser permission to access the BlogRoll database and grants it permission
to períorm (RUD (reate. Read. Update. Delete, operations on the Ieed and Post tables.
Gathering RSS leeds using Visual Studio and RSS.NL1 113

Oí course. vou can set up vour application to use anv user that vou want ií vou do not want
to use the BlogRollUser that the script generates íor vou.
Gatherlne the feed llst
1he next step is to create the \indows Ser·ice that will poll the RSS íeeds. I started bv
creating a \indows Ser·ice project through Visual Studio.NL1 and naming it
IeedGatheringService. I then reíerenced the System.1ransactions assemblv as well as the
RSS.NL1 assemblv.
I added a \indows Ser·ice with the same name as the project and created a
System.1imers.1imer. named feedGatherer1imer. in it. In the Ser·ice's constructor I
initialize the timer to run e·erv 60 seconds. I also add an e·ent handler íor the timer's
Llapsed e·ent. 1his e·ent will be used to grab the list oí íeeds and to poll them íor their
new posts.
public FeedGatheringService()
{
InitializeComponent();
feedGathererTimer = new System.Timers.Timer();
feedGathererTimer.Interval = 30000; //900000;
feedGathererTimer.Enabled = false;
feedGathererTimer.Elapsed += new ElapsedEventHandler(feedGathererTime
r_Elapsed);
}
1he feedGatherer1imer_Llapsed e·ent grabs a list oí íeeds írom the SOL Ser·er database.
I created a Ieed class which has a static method called GetList. which queries the database's
Ieed table íor all oí the íeeds. Lach íeed row that is retrie·ed is used to populate a
List<Ieed>. 1he íollowing listing shows how a connection is opened to the database using
ADO.NL1. 1he íeed list is grabbed and each row is used to create a Ieed entitv through the
Ieed entitv's constructor. Lach entitv is added to the List<Ieed> and íinallv the list oí
returned so the ser·ice can poll them.
public static List<Feed> GetList()
{
List<Feed> feedList = new List<Feed>();
using (SqlConnection cn = new SqlConnection(Settings.default.BlogRoll
ConnectionString))
{
cn.Open();
string sql

= "SELECT ID, Title, Url, LastPostGuidName, DateCreated FROM Feed ORDER BY Sequen
ce";
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.CommandType = CommandType.Text;
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseCo
nnection);
while (rdr.Read())
{
feedList.Add(new Feed(Convert.ToInt32(rdr["Id"]),
Convert.ToDateTime(rdr["DateCreated"].ToString()),
rdr["Title"].ToString(), rdr["Url"].ToString(),
rdr["LastPostGuidName"].ToString()));
}
}
}
return feedList;
}
114 bv John Papa
Pelllne the feeds fer pests
Once the List<Ieed> has been retrie·ed. the Ser·ice iterates through each oí them and
polls the íeeds íor their posts. 1his is where a librarv. such as RSS.NL1 or the ASP.NL1
team's RSS toolkit. comes in handv. 1hese tvpes oí tools make interacting with RSS íeeds
much simpler than ií vou hit them directlv and parse the results. 1here is a considerable
amount oí exception handling and data cleanup that is handled bv these libraries. and thev are
both íreelv a·ailable to use.
RSS.NL1 has a RssIeed class that exposes a public method called Read. which accepts a
URL oí a íeed. It requests a list oí posts írom the íeed and returns them to the application.
1he posts are contained within the RssIeed class's Channels collection's Items collection.
rssFeed.Channels[0].Items
1he íollowing code listing demonstrates how the RSS.NL1 RssIeed class polls the íeed's
URL and returns the posts. Lach post represented bv the rssItem ·ariable, is then examined
to see ií its Guid.Name matches the last post that was retrie·ed íor this íeed in pre·ious
polling attempts,. 1he íirst time this code is executed íor a íeed the LastPostGuidName
·alue will be NULL. lowe·er once a post has been inserted into the Post table. the
LastPostGuidName ·alue is updated íor the Ieed table. Ií a match is íound then the
processing íor this íeed stops as the rest oí the posts alreadv exist in the Post table.
public static List<Post> GetNewRssPostsFromUrl(Feed feed)
{
string mostRecentPostGuidName = null;
List<Post> postList = new List<Post>();
RssFeed rssFeed = RssFeed.Read(feed.Url);
if (rssFeed.Channels.Count == 0)
return postList;

// Get Channel 0
RssChannel channel = rssFeed.Channels[0];

foreach (RssItem rssItem in channel.Items)
{
// If we already have this post, exit. This means the rest of pos
ts are old.
if (feed.LastPostGuidName == rssItem.Guid.Name)
break;

// Grab the first Post's Guid
if (mostRecentPostGuidName == null)
mostRecentPostGuidName = rssItem.Guid.Name;

Post post = new Post();
post.FeedID = feed.Id;
post.Author = rssItem.Author;
post.Link = rssItem.Link.ToString();
post.PostDate = rssItem.PubDate;
post.Body = rssItem.Description;
post.Title = rssItem.Title;
postList.Add(post);
}

// Update the Feed's last post setting
if (mostRecentPostGuidName != null)
feed.LastPostGuidName = mostRecentPostGuidName;

return postList;
}
1he code then shows that once the post is determined not to be a duplicate that a Post entitv
is created. 1he Post entitv is created írom the ·alues retrie·ed írom the rssItem and then
added to a List<Post>.
Gathering RSS leeds using Visual Studio and RSS.NL1 115

1he íinal step beíore returning the List<Post> is to update the LastPostGuidName
propertv. 1his is set so that the next time the polling process occurs that it will know what the
identiíier is íor the last post that was retrie·ed. Notice that the posts are not vet inserted into
the database. 1he entire process merelv gathers the posts and sets the LastPostGuidName.
lnsertlne the pests
Once the List<Post> has been returned to the ser·ice. thev must be inserted into the
database's Post table. 1he íollowing code in 5 demonstrates how the posts are inserted. lirst
a 1ransactionScope is instantiated using the System.1ransactions librarv. I create a
transaction since I want all oí the posts íor a gi·en íeed to be inserted together. Ií anv post
íails to insert then I want all oí the posts to rollback. Using the System.1ransactions
1ransactionScope class I can maintain a 2-phase commit that will implement this atomic
transaction. Since this uses a 2-phase commit. it requires that the MS Distributed 1ransaction
(oordinator ser·ice is running. Oí course. ií vou wish to eliminate the transactions or write
them a diííerent wav. vou can simplv replace the System.1ransactions code with vour own
transactional code.,
using (TransactionScope ts = new TransactionScope())
{
// if the Feed and its Guid changed, update the feed
if (feed.LastPostGuidNameChanged)
feed.Update();

// If it has posts, add them
if (postList.Count > 0)
foreach (Post post in postList)
post.Add();

ts.Complete();
}
1he íirst part oí the transaction is to update the LastPostGuidName column in the Ieed
table. 1hen. each post is inserted into the Post table. linallv. ií all goes well and no exception
is thrown. the transaction is marked complete and it is committed to the database. A new
transaction is created íor each íeed. so each íeed is separate írom the next íeed.
Creatlne an lnstaller
\hen creating the \indows Ser·ice. I set the timer's inter·al to 60 seconds so I could easilv
debug and watch the íeeds populate. lowe·er. the realitv is that I probablv do not want this
ser·ice running e·erv minute since íeeds do not usuallv change that oíten. I suggest that vou
change the timer's inter·al to something less írequent such as e·erv 15 minutes an inter·al oí
900000,.
Ií vou want to debug the application. vou can adjust the inter·al back down to 60 seconds íor
testing purposes.
Once the \indows ser·ice was created I needed a wav to install the ser·ice on mv machine to
trv it out and to debug it,. 1o do this I opened the IeedGatheringService.cs íile's designer
and right-clicked in open space to bring up the context menu shown in ligure 3. I selected
the Add Installer option írom this menu. which adds a ProjectInstaller.cs íile to the project.
116 bv John Papa

ligure 3 -Adding the Installer
1he ProjectInstaller pro·ides a component that will be used to install the ser·ice process
and a component that will be used to install the ser·ice itselí. \ou can examine these
components bv opening the ProjectInstaller in design ·iew. I set the
serviceProcessInstallerJ component's Account propertv to LocalSystem. I could ha·e
speciíied a speciíic user. and in a production application I recommend doing just that.
lowe·er. íor demonstration purposes. I will use LocalSystem so it is easier to debug the
application.
I also set the serviceInstallerJ component`s ServiceName propertv to IeedGathering-
Service and set its Start1ype to Automatic. 1his tells the ser·ice to start itselí automaticallv
when the computer is started. I set the description to "Gathers RSS leeds" so the ser·ice will
ha·e some basic description when ·iewing it in the Ser·ices list.
lnstall the Wlndews $ervlce
Once the project is compiled the ser·ice can be installed on the computer bv íollowing a íew
simple steps. Open a Visual Studio.NL1 command prompt and change to the bin\Debug
directorv oí the IeedGatheringService project. \ou will want to use the bin\Release
directorv when ií vou compile a release ·ersion., Lxecute the íollowing command to install
the ser·ice on the computer:
InstallUtil.exe FeedGatheringService.exe
1he InstallUtil command will install the ser·ice and create the appropriate registrv entries
íor the ser·ice. Ií vou want to uninstall the ser·ice vou can execute the InstallUtil command
again with the ´u argument., It will not start the ser·ice immediatelv. Keep in mind that the
next time the computer is restarted that the ser·ice will start automaticallv since I set the
Start1ype propertv to Automatic in the ServiceInstaller component. Lxecute the íollowing
command in the Visual Studio.Net command window to start the ser·ice:
net start FeedGatheringService
Another option to start the ser·ice is to right-click on Mv (omputer. select Manage írom the
context menu. open the Ser·ices node in the tree. locate the IeedGatheringService in the
list oí ser·ices and start it írom there.
bebueelne the $ervlce
Debugging a \indows ser·ice is not diííicult. but is does require a íew additional steps
bevond what is in·ol·ed with a \inlorm or ASP.NL1 application. lirst. set a breakpoint in
the feedGatherer1imer_Llapsed e·ent handler so vou can watch the code execute. 1hen.
go to the Debug menu and select Attach to Process. Make sure that the checkboxes are
Gathering RSS leeds using Visual Studio and RSS.NL1 11¯

selected so vou can see all processes. Next. select the IeedGatheringService írom the list oí
a·ailable processes and click the Attach button shown in ligure 4,.

ligure 4 - Attaching to the \indows Ser·ice
Ií vou set the breakpoint in the feedGatherer1imer_Llapsed e·ent handler then the code
will hit the breakpoint and vou will be able to walk through the code as it executes. 1he
breakpoint will be hit once the timer's inter·al has been reached.
Wrapplne Up
At this point vou now ha·e a \indows ser·ice that will gather posts írom a list oí RSS íeeds
and store them in a database. 1he code included with this article points to a speciíic database
ser·er instance oí local,`SOL2005. Remember to change this to reílect the name oí vour
SOL Ser·er instance. 1he posts can now be read using standard ADO.NL1 data access
objects and displaved in anv oí vour applications.
118 bv Jesse Libertv and Alex loro·itz
G£11lNG $1AR1£b Wl1R XAML
16 March 2007
by Jesse Liberty and Alex Horovitz
Creatlne .N£1 3 Appllcatlens wlth XAML
Microsoít's .NL1 3 introduces the option oí using markup to create \indows applications.
much as we'·e used l1ML and related markup to create web applications íor the past
decade. 1his is a major break-through and a distinct step on the path towards merging the
de·elopment process oí creating web and desktop applications.
1his new markup language is called eXtensible Application Markup Language. or XAML
pronounced ´'zæml´ to rhvme with camel,. L·erv XAML object represents a (LR object.
and e·erv (LR object mav be created declarati·elv in XAML.
In this article we'll create a meaningíul. working XAML application. \e'll take vou through
the creation process. with the goal oí demonstrating the ílexibilitv oí the language and
acquainting vou with the íundamentals oí creating a working \indows application
declarati·elv.
Gettlne $tarted Wlth XAML
L·erv \indow is declared as a panel and e·erv propertv is declared as an element. or an
attribute oí an element. In its simplest íorm. this looks ·erv similar to l1ML:
<Button Width="20" Height="10">OK Button</Button>
In íact. XAML is an XML-based superset oí l1ML. combining the power oí l1ML to
deíine content. with that oí (SS to deíine lavout careíullv separating the two. to the relieí oí
experienced (SS programmers,.
Note: 1o íollow along with the examples in this article. vou will need to install the release
·ersion oí .NL1 3. which will run happilv and harmlesslv side-bv-side with .NL1 2 on anv
computer running Vista. \indows XP SP2,. or \indows Ser·er 2003 SP1.
Cere XAML £lements
\hile we won't e·en trv to co·er e·ervthing in XAML in this article. we will co·er some core
elements. both to gi·e vou a íeel íor how XAML applications work and to lav the ground
work íor walking vou through a robust sample application.
A panel element is a container. Panels come in se·eral diííerent shapes and sizes. Depending
on their íeatures thev are useíul íor containing other elements.
Root elements are special deri·ations oí panel elements and ser·e as the íundamental
container íor the page. Lach page will ha·e exactlv one root element.
Control elements are user-manipulated objects that help with data or user interactions.
1he Peleten$hack Preject
\ith these elements. we are going to build a data-bound page that will oííer a catalog oí
bicvcle parts írom the Ad·enture\orks2000 database. On the leít. there will be a scrolling
displav oí images taken írom the database. As the user scrolls o·er each image. it that is. the
image. not the user, will grow slightlv and come into íocus. Ií the user clicks on the image. it
will be highlighted in the larger area on the right and its details will be displaved in text. as
shown in ligure 1.
Getting Started with XAML 119


Iigure J: in which the selected image not the user, is shown in detail in the right hand panel
Creatlne the Web Appllcatlen
1o create this application. begin bv opening Visual Studio and creating a new Project.
(hoose the \indows Application \Pl, template. set vour directorv and set the solution
name to PelotonShack. as shown in ligure 2.

Iigure 2: creating the web application
\indows will create a de·elopment en·ironment and establish vour íirst window. which it will
also name íor vou:
<Window x:Class="PelotonShack.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PelotonShack" Height="300" Width="300">
<Grid>
...
</Grid>
</Window>
120 bv Jesse Libertv and Alex loro·itz
\hile there will certainlv come a time and soon, when Visual Studio will oííer a complete
and working toolkit íor building \Pl applications. íor now we're going to ha·e the most
success and least írustration ií we write directlv in XAML. in the lower markup window.
Start bv setting the height oí \indow1 to 600 and the width to 900 vou can íine-tune these
numbers later,.
\ithin the Grid. we'll want to add Grid.Resources to manage stvles. GradientBrushes.
etc., and we'll want to add column deíinitions.
1he Header
An easv place to start is bv adding the header. as shown in ligure 3:

ligure 3: adding the header
1o create this. we'll use a text block and stvle it. 1he text block is prettv straightíorward:
<TextBlock Style="{DynamicResource TitleText}">
<Span>The Peloton Shack: </Span>
<Span FontStyle="Italic"> keeping you in the pack!</Span>
</TextBlock>
Notice. howe·er. that the TextBlock element is marked with a stvle reíerence to the
DynamicResource 'TitleText'. Resources are separated out in their own section. so the
complete XAML at this point looks as shown in example 1:
Example 1
<Window x:Class="PelotonShack.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PelotonShack" Height="600" Width="900">
<Grid>
<Grid.Resources>
<Style x:Key="TitleText" TargetType="{x:Type TextBlock}" >
<Setter Property="FontFamily" Value="Segoe Black" />
<Setter Property="FontSize" Value="24px" />
<Setter Property="Foreground" Value="MidnightBlue" />
</Style>
</Grid.Resources>

<TextBlock Style="{DynamicResource TitleText}">
<Span>The Peloton Shack: </Span>
<Span FontStyle="Italic"> keeping you in the pack!</Span>
</TextBlock>
</Grid>
</Window>
lrom such humble beginnings. all the rest will íollow in just a íew easv steps.
Adding the List Box
1o add the list box oí products. we need to accomplish a íew tasks all at the same time.
1. Add a panel to hold the list box in a speciíic place in our window.
2. Add a list box to hold the images.
3. Include stvle iníormation to tell the list box how to render the data as images,.
Getting Started with XAML 121

4. Bind the list box to a querv oí the images írom the database.
1he panel we'll use to hold the list box will. ultimatelv. ha·e the list box on the leít. the larger
image on the right and text below. 1hat is. it needs to ha·e two columns and a row. A stack
panel can hold items one abo·e the other or one next to another. but when vou need to ha·e
both rows and columns. the panel vou need is a grid:
<Grid x:Name="MainGrid" Margin="125,99,127,118" Width="Auto"
Height="Auto" RenderTransformOrigin="0.5,0.5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.33*"/>
<ColumnDefinition Width="0.66*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentControl x:Name="MasterPane" HorizontalAlignment="Center"
VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5">
<ListBox x:Name="MasterList"
Width="Auto" Height="Auto"
RenderTransformOrigin="0.5,0.5"
ItemsSource="{Binding Mode=Default}"
ItemTemplate="{DynamicResource ProductDataTemplate}"
ItemContainerStyle="{DynamicResource ProductTemplateItemStyle}"
HorizontalAlignment="Center"
SelectedIndex="0"
IsSynchronizedWithCurrentItem="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
>
</ListBox>
</ContentControl>
<ContentControl x:Name="DetailsPane" Grid.Column="1" />
</Grid>
1he íirst two lines set up the grid's attributes margin. width. etc.,. 1his is íollowed bv the
Grid column deíinitions. in which we deíine two columns: one will occupv 1´3 oí the grid's
width. while the second will occupv the remaining 2´3 oí the grid's width. One row is
deíined in the Row deíinitions.
lollowing the Row and Column deíinitions are two content controls: MasterPane and
DetailsPane. 1he second content control. DetailsPane. is leít emptv íor now. 1he íirst
content control holds the list box. 1he kev attribute íor the list box is that the ItemSource is
set to bind using the deíault mode. Since this is placed within a window where the
DataContext was set to 'theTable'. the list box will use 'theTable' as its binding context.
1he stvle íor binding is set bv the ItemTemplate.
ItemsSource="{Binding Mode=Default}"
ItemTemplate="{DynamicResource ProductDataTemplate}"
The ProductDataTemplate must be defined up in the Grid.Resources section.
<DataTemplate x:Key="ProductDataTemplate">
<StackPanel HorizontalAlignment="Center"
Background="{DynamicResource ListBoxGradient}"
Height="Auto" Width="150">
<Image Source="{Binding Path=ThumbNailPhotoFilePath}"
ContextMenuService.IsEnabled="True"
ContextMenuService.ContextMenu="{Binding Path=Name}"
HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
It consists oí a stack panel whose horizontal alignment is set to center the items and whose
image will be retrie·ed írom the binding where the path is set to 1humbNailPhotolilePath .
1his is a column that will be retrie·ed íor e·erv row as a result oí the select statement that
will generate the DataTable we'll be using as the DataContext íor this list box.
122 bv Jesse Libertv and Alex loro·itz
All oí the work íor generating the DataTable. and associating it with the context. is
accomplished in the code-behind íile. the constructor íor the \indow class:
public partial class Window1 : System.Windows.Window
{
DataTable theTable = new DataTable();
public Window1()
{
InitializeComponent();
String connString = @"Data Source=<Your Server>;Initial
Catalog=AdventureWorks2000;Integrated Security=True";
String query = @"SELECT
Product.ProductID,
Product.Name,
Product.ProductNumber,
Product.ListPrice,
Product.Color,
Product.Size,
Product.Style,
ProductPhoto.ThumbNailPhotoFilePath,
ProductPhoto.LargePhotoFilePath
FROM
Product
INNER JOIN
ProductPhoto
ON
Product.ProductPhotoID = ProductPhoto.ProductPhotoID
WHERE
(Product.ProductPhotoID =
ProductPhoto.ProductPhotoID
AND Product.ListPrice < 100)";
using ( SqlConnection conn = new SqlConnection( connString ) )
{
SqlDataAdapter da = new SqlDataAdapter( query, conn );
da.Fill( theTable );
}
DataContext = theTable;
}
1he net result is a scrollable list oí images extracted írom the database. as demonstrated in
ligure 4:
Getting Started with XAML 123


ligure 4: a scrollable list oí images
Adding Events
\e'd like to be able to mouse o·er the list and make the image currentlv highlighted bv the
mouse stand out more than the others. \e'll do this bv reducing the opacitv oí the images to
60° and then increasing that to 100° when the mouse ho·ers o·er the image. 1o accomplish
this eííect. we add animation. using a storv board.
\e'll indicate that the starting opacitv is .60 60°,: and we`ll designate that o·er the course oí
two tenths oí a second that opacitv should increase to 100° when the e·ent MouseEnter is
registered. 1his opacitv will also return to 60° when the MouseLeave e·ent is raised. \e
declare these requirements bv creating a ProductTemplateItemStyle íor the
ItemContainer and then creating that stvle within the Resources section:
<Style x:Key="ProductTemplateItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Opacity" Value=".60" />
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="Opacity"
To="1.0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
124 bv Jesse Libertv and Alex loro·itz
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
1he eííect is poweríul: As vou mouse o·er each item. it appears to light up.
Creatlne the betall vlew
\e can now add the second container control to hold the larger image oí the selected item
and its description. 1he most interesting thing we'll do here is add a 'reílection':
<ContentControl x:Name="DetailsPane" Grid.Column="1"
Margin="15,0,0,0" Width="Auto" Height="Auto"
RenderTransformOrigin="0.5,0.5">
<Grid x:Name="DetailsGrid" Width="Auto" Height="Auto"
RenderTransformOrigin="0.5,0.5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<Image x:Name="ProductImage"
RenderTransformOrigin="0.5,0.5"
Width="Auto" Height="200" Source="{Binding
Path=LargePhotoFilePath}"/>
<Rectangle Fill="{DynamicResource ReflectionBrush}"
Margin="0,0,0,0" Stroke="{x:Null}"
Width="{Binding Path=ActualWidth,
ElementName=ProductImage, Mode=Default}"
Height="200" RenderTransformOrigin="0.5,0.5">
<Rectangle.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="-1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Rectangle.LayoutTransform>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0.5,1.5"
EndPoint="0.5,-0.5">
<GradientStop Color="#7FFFFFFF" Offset="0.21"/>
<GradientStop Color="#00FFFFFF" Offset="0.392"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</StackPanel>
<TextBlock x:Name="Name" Margin="0,0,2,19" HorizontalAlignment="Left"
VerticalAlignment="Bottom" Width="64" Height="26"
RenderTransformOrigin="0.5,0.5" TextWrapping="Wrap"
Text="{Binding Path=Name}" TextAlignment="Left"/>
<TextBlock x:Name="ProductNumber" Margin="0,0,2,19"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Width="64" Height="26" RenderTransformOrigin="0.5,0.5"
TextAlignment="Center" Text="{Binding Path=ProductNumber}"/>
<TextBlock x:Name="ListPrice" Margin="0,0,2,19"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="64"
Height="26" RenderTransformOrigin="0.5,0.5"
TextAlignment="Right" Text="{Binding Path=ListPrice,
Converter={StaticResource myNumberFormatter},
ConverterParameter='$#,###.00'}"/>
</Grid>
</ContentControl>
Getting Started with XAML 125

Creatlne the Reflectlen
1he reílection is created bv adding a grid within the content control. \ithin that grid we will
add a stack panel. 1he image is stacked on top oí a rectangle that uses a LayoutTransform
to ílip the image upside down Scale\ ~ -1,. 1he rectangle then uses an OpacityMask with a
LinearGradientBrush that starts out at 0.5.1.5 and ends at 0.5. -0.5: that is. the image
appears to íade awav quicklv:
<Rectangle.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="-1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Rectangle.LayoutTransform>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0.5,1.5" EndPoint="0.5,-0.5">
<GradientStop Color="#7FFFFFFF" Offset="0.21"/>
<GradientStop Color="#00FFFFFF" Offset="0.392"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
1he eííect can be seen in this close-up oí the rendered image in ligure 5:

ligure 5: reílection
1o íinish up. we'll add a GridSplitter. telling it which column we want to split. 1his will
allow the user to reallocate the relati·e space between the list box and the details:
<GridSplitter x:Name="GridSplitter1"
Grid.Column="1" Margin="1.5,1.5,0,3"
HorizontalAlignment="Left" Width="11.5"
Height="Auto" RenderTransformOrigin="0.5,0.5"/>
1he eííect is shown in ligure 6:
126 bv Jesse Libertv and Alex loro·itz

ligure 6: the gridsplitter
Complete Source
1he complete source can be downloaded írom the Simple-1alk site. \ou can also ·isit
http:´´www.gotdotnet3.com´ where a íree support íorum and related support material are
a·ailable. 1he source can be run on anv machine with the .NL1 3 lramework and SDK
installed.

Let there be Sil·erlight 12¯

L£1 1R£R£ B£ $lLv£RLlGR1
05 January 2008
by John Bower
John Bower stays calm, and so will you, whilst guiding you to producing your first Silverlight
application. It may just be 'Hello World' but soon...
Rl-Uh $llverlleht
Mv íirst Simple-1alk article on Sil·erlight is geared towards running through the basic
requirements íor working with Sil·erlight. Microsoít`s latest addition to .NL1. so as to get vou
up and running quicklv
Ií vou ha·e anv experience with llash and´or .NL1 programming languages such as Visual
Basic. or (4. Sil·erlight should be oí some interest. It`s an eííicient and suitable alternati·e to
llash that makes use oí the \indows Presentation loundation. It will appeal to those people
who want more írom their interacti·e experiences. or who are unsettled bv Adobe`s
acquisition. and subsequent Adobiíving`,. oí Macromedia`s most popular piece oí soítware.
It has immediate attractions íor the application programmer because oí the ease with which
one can access data írom SOL Ser·er
L·en with prior llash or .NL1 experience. though. vou will be íorgi·en íor the initial
reaction oí total baíílement with the de·elopment process. It isn`t that intuiti·e.
Unlike llash. Sil·erlight de·elopment requires se·eral applications. \hereas llash authoring
can be achie·ed solelv within the llash De·elopment Ln·ironment. Sil·erlight requires VB or
(4 íor coding. and Microsoít`s Lxpression 1ools íor graphical assets and ·ideo compression.
Sil·erlight is also ·erv diííerent to llash in that an S\l íile is selí-contained. and complex
Sil·erlight projects can require a plethora oí separate code and XAML íiles that coalesce into
the end-user`s experience.
$llverlleht ¥ears Ahead
Because Sil·erlight is still under de·elopment. and so the paint isn't alwavs drv. It also means
that a lot oí possiblv useíul íeatures ha·en`t been included vet: Most oí the time vou will need
to do a certain amount oí code hacking` in order to work around certain issues. Reading
írom external text íiles. íor example. is currentlv impossible unless vou gi·e them a mock
XML íile extension. and vou parse them using XML string commands. I`ll co·er this
workaround in a later article.
Also. because Sil·erlight is so new. it is unlikelv that vour target audience e·en has the 11Mb
runtime installed. It is included in the .NL1 3.5 update but. as I`·e learned írom personal
experience. the little vellow shield icon in the svstem trav. along with all the other bric-a-brac.
remains a mvsterv to a majoritv oí end-users!
Not onlv that. but the web ser·er hosting vour pages online must ha·e .NL1 3.5 extensions
installed too. Later in 2008 this mav be more common. but so íar it`s onlv specialist ser·ers.
such as http:´´www.discountasp.net´.. that can oííer this. 1his will soon change. though
So what`s the point in de·eloping something ií people can`t use it·`. I hear vou wail.
1he point is this: Sil·erlight is considerablv more poweríul than llash. \ou can easilv de·elop
a browser-based application that interacts with SOL Ser·er. and e·ervthing runs a LO1 íaster.
Although it is currentlv unsupported bv the majoritv oí P(s. vou will soon íind it hard to
a·oid. as it will e·entuallv be íorceíullv pushed out in íuture \indows updates.
128 bv John Bower
So. without íurther ado. this is how to get Sil·erlight up and running on vour computer.
Readlne tewards the $llverlleht
Beíore we start installing anvthing. I must point out that Sil·erlight makes use oí hidden
Ja·aScript extensions. which some Anti Virus programs will regard as suspect. and will simplv
not allow to be accessed. Ií vou ha·e anv problems starting a Sil·erlight project. vou should
check vour Anti Virus program íirst. 1here mav be an option to ignore hidden JS extensions
in most pro` ·ersions.
J) Update .NL1 on your computer
It should go without saving reallv. but there is no harm in repeating the íact that vou will
need to make sure vou ha·e .NL1 3.5 installed on vour computer. otherwise Sil·erlight
will not work.
2) Download Visual Studio 2008
Ií vou don`t alreadv ha·e Visual Studio 2008. or Orcas`. as it`s aííectionatelv known
among the programming under-class,. vou simplv need to íollow
http:´´msdn.microsoít.com´en-us´e·alcenter´deíault.aspx to the Visual Studio
website. and download one oí the 180-dav trial ·ersions .Uníortunatelv. Microsoít`s cut-
down Lxpress ·ersions do not currentlv support Sil·erlight. but that is liable to change
in the íuture when Microsoít makes Sil·erlight more accessible to de·elopers.
Both VB and (4 can be used to write Sil·erlight applications. I will supplv VB and (4
example code in íuture Sil·erlight articles but íor the purposes oí this tutorial we`re
sticking to VB.
3) Download the latest Silverlight runtime
As oí the time oí writing this article. the latest Sil·erlight runtime is the Alpha 1.1
Reíresh. which can be downloaded bv clicking http:´´www.microsoít.com´sil·erlight´
resources´install.aspx··~2.0
I assume all Sil·erlight runtime releases will be a·ailable írom the abo·e site. but there is
a link to download it írom the oííicial Sil·erlight website ií in doubt
4) Download the Silverlight project templates for Visual Studio
Again. a·ailable írom the Microsoít website. the templates allow vou to start a new
Sil·erlight project írom the New Project wizard inside Visual Studio 2008. Although not
·ital. thev`re worth downloading as thev sa·e a lot oí eííort in the long run. 1he
templates are a·ailable ·ia http:´´www.microsoít.com´downloads´details.aspx·
lamilvId~25144(2¯-6514-4AD4-8B(B-L2L051416L03&displavlang~en
5) Optional: Download Microsoft Lxpression Blend 2
\ou should be readv to go. but ií vou require graphical assets which would be nice!,.
click http:´´www.microsoít.com´Lxpression´products´download.aspx·kev~
blend2pre·iew to download Lxpression Blend 2. 1his will allow vou to create ·ector-
based assets and export them in XAML íormat to use in vour Sil·erlight application. \e
won`t be using Lxpression Blend íor this tutorial. but it is handv to ha·e íor íuture ones.
Phew! 1hat`s the easv bit out oí the wav! Now to get coding!
¥eur flrst $llverlleht preject (Relle Werldl)
Start Visual Studio 2008 and click lile · New Project
Let there be Sil·erlight 129

Ií all has gone well so íar. vou should be presented with a New Project dialog box that looks
similar to this:

(hoose a suitable location to sa·e vour project íiles in bv clicking the Browse` button. and
pick a name íor the project. 1hen click OK`. and Visual Studio will create the project based
on the íiles contained in the Sil·erlight template Zip íile the location oí this íile is usuallv:
(:`Program liles`Microsoít Visual Studio
9.0`(ommon¯`IDL`Item1emplates`VisualBasic`Sil·erlight`1033`,.
^ote: )bi. i. rbere yovr .vti·1irv. appticatiov vay tbror av error. ít it aoe.. yov ritt veea to
vviv.tatt it ava`or get ove tbat attor. biaaev ¡´ tite e·tev.iov..
Visual Studio will create íi·e íiles and a (lientBin íolder íor vou. and will then list them in the
Solution Lxplorer. Ií vou want to di·e in to the Sil·erlight quagmire oí inter-íile reíerences.
vou can rename them. but íor the purposes oí this tutorial we`ll keep them as thev are.
Page.xaml
contains iníormation about the lavout and design oí the application in a íormat similar
to XML. In this íile vou can speciív such things as the background colour. embedded
controls. and names that vou can reíerence in vour code. 1his íile is reíerenced bv
1estPage.html.js. so ií vou rename it. vou will ha·e to edit the reíerence. Visual Studio
has created a (an·as tag. which is basicallv the container íor vour controls. and all the
controls oí vour application will go within the (an·as tag. It`s worth making a mental
note oí the Loaded` parameter and the x:Name` parameter íor later. I won`t go into
the íull structure oí XAML íiles here because it has been co·ered beíore in other
articles. e.g. Getting started with XAML. and anvone who has written XML or l1ML
should be íamiliar with the tagging svntax alreadv.
Page.xaml.vb
is the code that is interpreted on the ser·er. It contains the logic that makes the XAML
íile interacti·e. 1his is where we do most oí the work in a Sil·erlight application.

\ou will notice that there is one íunction alreadv íilled in íor vou called page_loaded`.
130 bv John Bower
1his íunction is reíerenced bv the (an·as` Loaded` parameter in the XAML íile. and
will run when the (an·as is. loaded!
Silverlight.js
is a Ja·aScript íile that contains helper iníormation íor detecting web browsers and íor
compatibilitv checking. Microsoít ha·e cunninglv written this íile without anv spaces to
make it more diííicult to read. Luckilv. howe·er. vou won`t need to do anv editing to this
under most circumstances. It is reíerenced bv the l1ML page hosting vour application.
1estPage.html.js
is another Ja·aScript íile reíerenced bv the l1ML page hosting vour application. 1he
most useíul íunction in this íile is createSil·erlight,` which. ií vou hadn`t guessed.
creates vour embedded application. In this íile vou can speciív which XAML íile to load
when vour application starts. Ií vou rename this íile vou will need to edit 1estPage.html.
1estPage.html
is a simple l1ML íile that contains vour embedded application. 1his is the page that
needs to be accessed bv vour end user`s web browser. so it is likelv that vou will end up
renaming this to something like index.html` in íuture projects.
Now vou ha·e vour íiles. press l5 or Start Debugging, to see what happens.
Ií Internet Lxplorer runs with a blank page. and Sil·erlight Project 1est Page` as the title.
gi·e vourselí a pat on the back! \ou can change this title in the l1ML íile`s header section ií
vou think vour back deser·es another patting.
Now. let`s make it do something interesting! \e are going to create a 1extBlock in the XAML
íile. gi·e it a name. and then reíerence it in the XAML.VB íile so we can change the text it
displavs.
1. Open Page.xaml. and add ·1extBlock· and ·´1extBlock· tags between the (an·as tags.
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="HelloWorld.Page;assembly=ClientBin/HelloWorld.dll"
Width="640"
Height="480"
Background="White"
>
<TextBlock></TextBlock>
</Canvas>
\ou mav notice that Visual Studio`s context sensiti·e helper will sa·e vou a íew ·aluable
seconds. and possiblv a calorie or two. bv íilling the tag out íor vou. Now vou ha·e vour
1extBlock. vou need to gi·e it some text to displav.
2. Between the 1extBlock tags tvpe Hello World!`.
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="HelloWorld.Page;assembly=ClientBin/HelloWorld.dll"
Width="640"
Height="480"
Background="White"
>
<TextBlock>Hello World!</TextBlock>
</Canvas>
Let there be Sil·erlight 131

Now hit l5 and see what happens.
\ell done! \ou`·e created vour íirst Sil·erlight application that does something! Ok. don`t get
too excited. that`s just scratching the suríace. \hat we`ll do now is write some code that
changes the text aíter the page loads.
1his is where the (an·as` Loaded` parameter. and the 1extBlock`s x:Name` parameter
come into use. lirstlv we will need to create the x:Name` parameter íor the 1extBlock.
3. Add x:Name=”myText” in the 1extBlock`s opening tag
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="HelloWorld.Page;assembly=ClientBin/HelloWorld.dll"
Width="640"
Height="480"
Background="White"
>
<TextBlock x:Name="myText">Hello World!</TextBlock>
</Canvas>
\hen vou`·e done that. sa·e the íile.
Now that the 1extBlock has a name. we need to reíerence it in the Page.xaml.·b íile. and
change the text parameter to something else when the page is loaded.
4. Open Page.xaml.·b and add the íollowing line to the Page_Loaded,` íunction.
Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()

myText.Text = "Hello Silverlight!"

End Sub
lit l5 and see what happens.
132 bv John Bower

1he 1extBlock still displavs lello \orld!`. but once the page has loaded it changes it to
lello Sil·erlight!`. 1his happens beíore the 1extBlock is e·en rendered. so vou can replace
lello \orld!` with an extraordinarilv inappropriate phrase that would bring displeasure and
oííence to anv sensiti·e person ií vou wish. as no one will know!
tía: )be reaaer. ritt revevber ovr accovvt ot tbe tetter tbat revt to tbe be.t cv.tover. ot a vaior
bav/ evtittea Dear Ricb ßa.tara`. ave to a .ivitarty vi.ptacea covtiaevce tbat voboay rovta erer
.ee it)
(ongratulations! \ou are now able to update parameters within vour code. 1his is a
íundamental part oí Sil·erlight that will pro·e ·erv useíul in anv íuture project vou make.
Using this technique vou can update parameters íor anv object within vour application. be it
screen location. colour. íont. or anvthing else íor that matter.
1his application mav seem extraordinarilv tri·ial but. ií vou ha·e íollowed all the instructions.
vou will now ha·e the means to produce real Sil·erlight applications.
In the next tutorial I will help vou to enhance vour applications bv demonstrating how to set
up a loop within Sil·erlight so vou can trigger íunctions repeatedlv íor continuous animations.
games. and data exchanges.
Sil·erlight-Speed Loop 133

$lLv£RLlGR1-$P££b LUUP
18 January 2008
by John Bower
Creatlne a Rleh-speed Leep
John Bower steps up a gear, produces a Lamborghini, and examines the process of using a
high-speed function loop to create a fast-paced Silverlight application.
ligh-speed loops are ·ital íor anv application that needs to be constantlv updated as quicklv
as possible. Generallv speaking. vou will íind this most important ií vou de·elop games or
other interacti·e media: but there are manv other practical uses íor them in applications that
require constantlv updated data.
In "real" game programming. i.e. DirectX or OpenGL,. a high-speed loop is literallv a Do.
Loop with a DoEvents() íunction located inside it. so the user's computer doesn't hang.
1his lets vour application run as íast as possible. usuallv enabling vou to call a íunction or
sub-routine e·erv íew thousandths oí a second. dependant on the (PU and GPU speed.
Needless to sav. vou must be ·erv careíul how vou use high-speed loops. lor example. vou
wouldn't want to run a svnchronous SOL querv on e·erv íunction call because:
23. It would be ·erv slow. thus negating the point oí a high-speed loop.
24. \ou will recei·e death threats írom vour database administrator.
1o create a high-speed loop in Sil·erlight. we need a blank Storvboard that calls an associated
custom e·ent handler in our code íile. 1hanks to Sil·erLight's abilitv to make use oí multi-
core (PUs. this technique can be ·erv íast!
In this article we will ad·ance the "lello \orld!" tutorial írom mv pre·ious 'Let 1here Be
Sil·erlight' article. \e will add an Image and a Storvboard aíter the parent can·as has loaded.
rather than beíore. 1hen we`ll use the Storvboard to create a high-speed loop.
1o demonstrate the loop. we will constantlv update and track the Left and 1op properties oí
the image to make it mo·e around the screen and bounce oíí the edges.
Bv the end oí this article vou will know how to:
• Use and create new Image objects at run-time
• Use and create new Storvboard objects at run-time
• (apture and handle e·ents
• (hange read-onlv properties
Ok. I can sense that vou're qui·ering with anticipation. so let's get started!
Addlne ebjects at run-tlme
Adding objects to the parent can·as in Sil·erlight can be done in two wavs:
25. Manuallv adding tags to vour XAML íile. 1his method was co·ered in the pre·ious
article and is useíul íor applications that retain a static design template.
26. Adding objects through vour code at run-time bv creating new resources in memorv.
1his technique is useíul ií vou want to embellish a pre-designed static template with
dvnamic objects.
134 bv John Bower
Use oí method 2 also means vour (an·as could e·en be completelv emptv when it loads. and
then be dvnamicallv populated. which. as a side-note. is how we make "loading screens" in
Sil·erlight.
1. Gettlne $tarted
Beíore we start getting to the main portion oí the application. we need to set up a reíerence
to the parent can·as. \e can then use this (an·as ·ariable to attach our objects.
So. íirst oí all. open Visual Studio 2008 and create a new Sil·erlight project.
\hen that's done. we need to create a new ·ariable that reíers to the parent can·as in our
XAML íile. \e`ll call this cParent.
Open up "Page.xaml.vb" and then add the íollowing highlighted lines oí code:
Partial Public Class Page
Inherits Canvas

Dim cParent As Canvas

Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()

' set cParent to the canvas with the name "parentCanvas"
cParent = FindName("parentCanvas")

End Sub

End Class
\hen we declare the ·ariable. cParent. notice that we don't use the New kevword because
the (an·as alreadv exists. \e aren't actuallv creating a new (an·as object in memorv. just a
·ariable to reíer to the one created bv the XAML íile when the application loads.
\hen we created the new project. Visual Studio automaticallv created a new (an·as in our
XAML íile with the name "parentCanvas". \ou can call it whate·er vou like. as long as vou
also remember to change appropriatelv the name reíerenced in the IindName method.
Sil·erlight will now know which (an·as to use whene·er we reíer to cParent later on in our
code. \ou can ha·e multiple (an·ases. or is that (an·i·,. just as long as thev ha·e diííerent
names. and so can be located using lindName.
2. Creatlne an lmaee ebject at run-tlme
Images are relati·elv basic objects that simplv displav an image írom a URL: or URI as it's
known in Sil·erlight and in the rest oí this article to a·oid coníusion,. 1he image íile vou
want to displav must be stored on the same ser·er as the application requesting it. 1his is to
pre·ent vou írom blatantlv hijacking another person's bandwidth bv directlv reíerring to
images on their ser·er.
In order to create a basic Image object at run-time. all we reallv need to do is speciív a URI to
our source image. lere's one we can use íor this example:

1he Lamborghini (ountach. Mv personal all-time ía·ourite car
I tend to keep mv bitmap images in a "´images" sub-íolder. but it's entirelv up to vou where
vou store them. Just make sure thev are easv to reíerence in the URI.
Sil·erlight-Speed Loop 135

1he íirst thing we need to do is create a new Image object. called imgSprite. in memorv:
Dim imgSprite As New Image
Notice that we're using the New kevword because our Image object doesn't exist vet. Our
XAML íile still onlv contains a (an·as.
Now we ha·e our object. let's write a íunction that sets its Source propertv to the URI oí the
image we want to displav. and then adds the object to the (an·as:
Private Sub attachImage()

' Set up the new Image object
imgSprite.Source = New Uri("images/car.jpg", UriKind.Relative)

' Add the Image to the Canvas
cParent.Children.Add(imgSprite)

End Sub
1he íirst ·alue in the URI method is simplv the address oí the image we're using. 1he second
·alue tells Sil·erlight that the address is relati·e to the address oí the code calling it.
Once we ha·e our new Image object readv íor creation. we add it to the (an·as'
VisualCollection as a child object. 1his is achie·ed using
Canvas.Children.Add(), which is similar to manuallv adding an ·Image· tag
between the ·(an·as· tags. except we now ha·e much more control!
linallv. we need to call our new íunction once the (an·as loads. Simplv add the íollowing
highlighted lines to vour Page_Loaded() íunction.
Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()

' set cParent to the canvas that called this function
cParent = FindName("parentCanvas")

' Create Image
attachImage()

End Sub

Ií vou hit l5 now. vour application should create. load. and then displav the image in the top-
leít corner oí the (an·as. in a staggeringlv dull and static íashion.
So. let's make it do something interesting!
3. Creatlne a $terybeard at run-tlme
Ií vou'·e e·er done anv llash de·elopment in the past. vou should hopeíullv be aware oí
Mo·ie(lips. Basicallv. a Storvboard is the Presentation lramework's equi·alent to a
Mo·ie(lip.
Ií vou`re one oí the íew people in this Uni·erse who ha·en`t e·er used llash. Storvboards are
best described as container timelines that contain object and propertv iníormation íor
animations: such as (olorAnimation. (olorAnimationUsingKevlrames. DoubleAnimation.
DoubleAnimationUsingKevlrames. PointAnimation. PointAnimationUsingKevlrames. or
136 bv John Bower
e·en another Storvboard. lowe·er. íor the purposes oí this article we won't be using these.
\e will simplv be creating a completelv emptv single-írame Storvboard.
1o create our Storvboard. we will use a slightlv diííerent technique to the one we used to
create our Image object. 1his is because Storvboards are a part oí the ResourceDictionary
rather than the VisualCollection.
lirstlv. we must create a new Storvboard in memorv:
Dim sbLoop As New Storyboard

1hen we need to write the íollowing íunction to add the Storvboard to the (an·as:

Private Sub attachStoryboard()

' Set up the new Storyboard
sbLoop.SetValue(Storyboard.NameProperty, "loop")

' Add the Storyboard to the Canvas
cParent.Resources.Add(sbLoop)

' Start playing the Storyboard loop
sbLoop.Begin()

End Sub
As vou can see. we are using the SetValue method to set the Storvboard's Name propertv
to "loop". 1his is because a Storvboard's Name propertv is actuallv read-onlv. Using SetValue
gets around this.
\ou'll also notice that. because Storvboards are resources. we need to use
Canvas.Resources.Add() as opposed to Canvas.Children.Add().
1he last line. sbLoop.Begin(), starts plaving the Storvboard as soon as it has been
attached to the can·as. At the moment. it will plav through to the end and stop. une·entíullv.
on the last írame.
linallv. we need to call our new Storvboard attachment íunction írom our
Page_Loaded() íunction. Simplv add the two lines oí code highlighted below:

Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()

' set rootCanvas to the canvas that called this function
cParent = FindName("parentCanvas")

' Create Image
attachImage()

' Create Storyboard
attachStoryboard()

End Sub
Ií vou run the application now. vou won't notice anv ·isible diííerence at all. \ou'll still see
the same static image patientlv waiting to be told to do something. but the Storvboard is
deíinitelv there.
Now we need to make the Storvboard call a new íunction at the end oí e·erv loop.
Sil·erlight-Speed Loop 13¯

4. Creatlne ¥eur Rleh-$peed Leep
\e will create a high-speed loop that will mo·e our Image object around the (an·as. bv
incrementing and´or decrementing its LeftProperty and 1opProperty ·alues. Both oí these
properties are read-onlv. so we will ha·e to use our trustv SetValue() íunction to alter them.
\e start bv creating two Booleans that will allow us to keep track oí whether our Image is
going leít. right. up. or down.
Dim movingLeft, movingUp As Boolean
Now we ha·e those. let's create two Integers to keep track oí the Image object's current
position.
Dim intX, intY As Integer
I íigured it would be best to ha·e these two Integers a·ailable throughout our class. íor speed
purposes. \e could Dim new ones and then get the current Left and 1op properties íor the
Image object on e·erv írame. but that seems unnecessarilv wasteíul considering we're aiming
íor top-speed here. plus we should be ·erv careíul about how we use memorv in such a íast
loop.
Next. we create our main íunction íor the loop. 1his is where the meat oí our application
makes the two pre·iouslv written ·egetables. the Image and the Storvboard,. work together.

Private Sub loop_Completed(ByVal sender As Object, ByVal e As EventArgs)

' Y-Axis logic
If movingUp Then

intY -= 1 ' update Y

If intY < 0 Then ' bounce
movingUp = Not movingUp
End If

Else

intY += 1 ' update Y

If intY > (cParent.Height - imgSprite.Height) Then ' bounce
movingUp = Not movingUp
End If

End If

' X-Axis logic
If movingLeft Then

intX -= 2 ' update X

If intX < 0 Then ' bounce
movingLeft = Not movingLeft
End If

Else

intX += 2 ' update X

If intX > (cParent.Width - imgSprite.Width) Then ' bounce
movingLeft = Not movingLeft
End If

End If
138 bv John Bower

' Update the Image object's position
imgSprite.SetValue(Canvas.LeftProperty, intX)
imgSprite.SetValue(Canvas.TopProperty, intY)

' Restart the Storyboard
sbLoop.Begin()

End Sub
lopeíullv. the íirst and second sections oí code should be íairlv selí-explanatorv. 1hev simplv
increment or decrement int\ and intX depending on the ·alues oí movingUp and
movingLeft.
1he third section updates both the Left and Top properties oí our Image object using the
imgSprite ·ariable we created earlier. Again. thev are read-onlv properties so we use
SetValue().
1he íinal part oí the íunction tells our Storvboard. which will currentlv be sitting on the last
írame. to start again. \ithout it - we ha·e no loop.
Lxcellent! \ith that done. all we need to do now is create an e·ent handler to point to our
loop íunction e·erv time the Storvboard gets to the last írame. 1his is actuallv ·erv simple as
we just need to add the íollowing highlighted line oí code to our Storvboard attachment
íunction.
Private Sub attachStoryBoard()

' set up the new Storyboard
sbLoop.SetValue(Storyboard.NameProperty, "loop")

' Add the Storyboard to the Canvas
cParent.Resources.Add(sbLoop)

AddHandler sbLoop.Completed, AddressOf loop_Completed

' Start playing the Storyboard loop
sbLoop.Begin()

End Sub
Now. hit l5. sit back. and watch vour image bounce around the screen like an earlv 80's ·ideo
game! Gi·e vourselí a ·igorous pat on the back!
leel íree to mess about with the code. because vou will get some satisíving ·isual íeedback.
£xtendlne yeur appllcatlen: Frames per secend (wltheut
Java$crlpt)
Now we ha·e a reallv íast application. wouldn't it be nice to know exactlv how íast it's
running·
1here`s good news. and bad news.
1he good news is. we can íind out ·ia a handv Ja·aScript propertv in our embedded object.
called enableFrameRateCounter. Ií this is set to "true" then the current írame rate is
displaved in the status bar.
1he bad news is that it onlv works in Internet Lxplorer. which automaticallv makes me dislike
it. Not that I`m against IL. but simplv because I preíer wider browser compatibilitv. 1he
second bit oí bad news is it means vet another íile to edit in order to make our application
work properlv.
Sil·erlight-Speed Loop 139

It`s up to vou ií vou use the Ja·aScript method. but I'm a íanatic oí doing things in the
simplest wav possible. and so I'm going to share mv own personal technique.
It mav not be the íastest or most accurate method. but at least we get a good idea oí the
current írame rate: plus this wav we can stav completelv within Sil·erlight. which makes our
application de·elopment easier to manage.
lirstlv. we need a 1extBlock to displav the írame rate. So let's start bv creating a new
TextBlock object called txtFPS:
lirstlv. we need a 1extBlock to displav the írame rate. So let's start bv creating a new
TextBlock object called txtFPS:
Dim txtFPS As New TextBlock

1hen we will need an integer called IrameRate to increment on each írame. and another
called Last1ime to keep track oí the number oí milliseconds:
Dim FrameRate, LastTime As Integer

\ith that done. let's create a new íunction that attaches the TextBlock to our (an·as:

Private Sub attachFPSText()

' Add the TextBlock to the Canvas
cParent.Children.Add(txtFPS)

End Sub

Award vourselí a pat on the back ií vou saw the Canvas.Children.Add method and
immediatelv. and correctlv. iníerred that TextBlocks are part oí the
VisualCollection.
\e now need to update our Page_Loaded() íunction with a call to our new
TextBlock attachment íunction:
Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()

' set rootCanvas to the canvas that called this function
cParent = FindName("parentCanvas")

' Create Image
attachImage()

' Create FPS TextBlock
attachFPSText()

' Create Storyboard
attachStoryboard()

End Sub
Now we need to create the íunction that updates our FrameRate Integer. lor this we will
use Date.Now.Millisecond.
140 bv John Bower
Private Sub updateFPS()

If LastTime > Date.Now.Millisecond Then
txtFPS.Text = "FPS: " & FrameRate
FrameRate = 0
LastTime = Date.Now.Millisecond
Else
LastTime = Date.Now.Millisecond
FrameRate += 1
End If

End Sub
L·erv time this íunction is called. one írame has elapsed: so we need to increment our
FrameRate Integer. Also. on each írame we update the LastTime integer with the ·alue
oí Date.Now.Millisecond. Ií Date.Now.Millisecond is less than the ·alue
oí LastTime. then roughlv one second has elapsed and the ·alue oí FrameRate is the
number oí írames that ha·e been displaved during that period.
Simple. eh· At least it sa·es us írom messing about with anv Ja·aScript íiles.
linallv. we need to call this íunction in our main loop: so add the íollowing highlighted lines
to our loop_(ompleted, íunction:
Private Sub loop_Completed(ByVal sender As Object, ByVal e As EventArgs)

' Update frame rate
updateFPS()

' Y-Axis logic
If movingUp Then

intY -= 1 ' update Y

If intY < 0 Then ' bounce
movingUp = Not movingUp
End If

Else

intY += 1 ' update Y

If intY > (cParent.Height - imgSprite.Height) Then ' bounce
movingUp = Not movingUp
End If

End If

' X-Axis logic
If movingLeft Then

intX -= 2 ' update X

If intX < 0 Then ' bounce
movingLeft = Not movingLeft
End If

Else

intX += 2 ' update X

If intX > (cParent.Width - imgSprite.Width) Then ' bounce
movingLeft = Not movingLeft
End If

End If

' Update the Image object's position
imgSprite.SetValue(Canvas.LeftProperty, intX)
imgSprite.SetValue(Canvas.TopProperty, intY)

Sil·erlight-Speed Loop 141

' Restart the Storyboard
sbLoop.Begin()

End Sub
\hen vou run the application now. vou should ha·e a shinv new 1extBlock in the top-leít
corner oí the (an·as telling vou how íast vour application is running.

$ummary
Not onlv are vou now íullv equipped to create objects at run-time. but vou can also use a
Storvboard to continuouslv call a íunction that updates vour objects` properties at great
speed. 1his puts vou in good stead íor being able to control and keep track oí the download
progress íor all assets within vour Sil·erlight applications.
lor anvone who is inclined towards making games in Sil·erlight. the onlv thing vou need to
íocus on now is translating user input to iníluence the propertv changes vour application
makes.
I`ll be interested to see what vou can do using these techniques.
In the next article. as part oí the mammoth request bv 1K´JLL,. we will examine the
potentials oí a "skinnable" User Interíace bv using Lxpression Blend 2 to create UI elements.
and then altering their appearance at run-time írom iníormation stored in a (ookie on the
user's computer.
142 bv John Bower
Links
1. 1he Source to the Sample code in VB - http:´´www.simple-talk.com´iwriteíor´ articleíiles´
466-Sli·erlightSpeed(odeVB.html
2. 1he Source to the Sample code in (4 - http:´´www.simple-talk.com´iwriteíor´articleíiles´
466-Sli·erlightSpeed(ode(.html

Sil·erlight Skinnable User Interíaces 143

$lLv£RLlGR1 $KlNNABL£ U$£R lN1£RFAC£$
05 February 2008
by John Bower
John Bower demonstrates more of the features of Silverlight, and Expression Blend, and
shows how one might write an application that avoids UI pitfalls by placing your design
responsibility squarely on your users’ shoulders. If it looks bad, it’s their fault!
Back in 199¯. I had mv íirst encounter with User Interíace design while I was working íor a
web de·elopment companv in London.
I used to watch with awe as one oí our senior (-- de·elopers spent hours e·erv dav
careíullv shiíting his buttons and textboxes around with neurotic precision. one pixel at a
time. le spent more time íiddling about with the UI oí the application than actuallv
programming the thing. le also had a tendencv to swear loudlv e·erv so oíten and stare at
co-workers with a psvchotic twitch in his leít eve. which I`·e since íound to be a mannerism
that's pre·alent among manv senior (-- de·elopers.
I couldn`t help but think to mvselí. it might look good to him as a programmer. but I
wonder how quicklv an oííice blood-bath would ensue ií e·en one client emailed him saving
the UI looks awíul. and he wants it all diííerent!` Aíter all. vou can`t please e·ervone.
especiallv ií thev're customers.
Mv desk was the one that was closest to his. I didn't like that twitch and I was highlv-strung. -
I worried that I`d probablv be the íirst to sustain collateral damage írom a írenzied attack. and
posthumouslv pro·ide a Pollock-stvle splatter eííect to the décor oí the oííice. It was this
bone-chilling íear oí impending doom that íirst led me to contemplate the selí-preser·ing
beneíits oí a customizable User Interíace.
Although Sil·erlight is quite capable oí meeting most oí vour programming needs on its
own. one oí its common uses is simplv as a íront-end íor a more ad·anced ser·er-based
application. 1his is where all oí Sil·erlight`s multimedia power can be harnessed to create
quite elaborate user interíaces. \ou could use this to make vour web application íeel more
unique to users bv allowing them to íullv customize how it looks. lor example: Ií. íor
whate·er reason. a user íound the colour Green repugnant - whv would thev want to use
vour bright Green application· \hv not gi·e them the opportunitv to change it to Red· Or
Purple· Or anv other colour·
I wouldn't necessarilv ad·ise such a thing but it makes a useíul demonstration oí Sil·erlight's
·ersatilitv. and gi·es us an excuse to use Lxpression Blend.
In this article we will create a Sil·erlight application that uses colour data retrie·ed írom a
(ookie in order íor us to create a customizable User Interíace. unique to each user.
\e will co·er:
• (reating and loading User (ontrols
• Lditing User (ontrols in Lxpression Blend
• Updating colour properties
• Reading and writing (ookies on the user`s computer
• Passing data írom the host document to Sil·erlight
1. Gettlne $tarted
lor anvone who has been íollowing this series oí articles. we`re íinallv going to use
Lxpression Blend! Ií vou ha·en`t got Lxpression Blend vet. go to
144 bv John Bower
http:´´www.microsoít.com´expression´ and download the latest pre·iew ·ersion. As oí the
time oí writing. it will be the December Pre·iew.
Beíore we get to the drawing though. create a new Sil·erlight project in Visual Studio 2008.
Gi·e it a con·enient name and then open the Solution Lxplorer.
Now we ha·e our new blank project we can add a new User (ontrol bv right-clicking in the
Solution Lxplorer and then selecting Add · New Item.`
In the Add New Item window. select Sil·erlight User (ontrol and enter a name íor it. I`·e
cunninglv called mine button.xaml`,. then click Add.

\ou`ll be presented with the XAML code íor our blank User (ontrol. but we won`t be editing
it here. \ou could create vour button using pure XAML code but it`s much íaster. more
precise. and íar easier to edit it in Lxpression Blend.
1o do this. right-click on the new (ontrol and select Open in Lxpression Blend.`
Sil·erlight Skinnable User Interíaces 145


\ou`ll probablv be asked ií vou want to sa·e the project at this point. Just click the \es`
button and \indows will start Lxpression Blend with the project readv to be edited.
2. Creatlne a butten ln £xpresslen Blend
Lxpression Blend makes editing XAML merciíullv simple. 1he onlv problem being that
Blend can appear daunting at íirst-glance. Ií vou`·e pre·iouslv done anv graphical work in
llash. vou should íind it íairlv easv to get vour head around once vou know where e·ervthing
is.
Ií vou ha·en`t e·er used llash. don`t worrv. Although vou`ll probablv íind it a bit tougher to
grasp. it actuallv íollows a ·aguelv similar lavout to Visual Studio with the addition oí a íew
graphics. and animation íeatures.
Lither wav. we`ll start írom the ·erv beginning íor this tutorial. \ou should now be presented
with a window similar to this one:
146 bv John Bower

lere`s a quick tour oí the íour main sections shown abo·e:
1. 1he Objects and Timeline pane shows the lavers oí objects Paths. Images. etc., in vour
(ontrol and changes to show the timeline oí vour Sil·erlight (ontrol. ií vou add a
Storvboard. enabling vou to create kev-írames íor animation. At the moment we ha·e a single
(an·as acting as the root object oí our (ontrol.
Preview pane shows what vour (ontrol looks like. \ou can draw new objects into this pane
in a similar wav to most graphics programs.
1he Project tab lists all the íiles contained within vour project. Next to it is the Properties
tab which isn`t selected in the abo·e image,.
1he Code pane displavs the XAML code behind vour (ontrol. 1his displavs exactlv what is
within vour XAML íile and it`s prettv much just a text editor.
Ob·iouslv I`m just scratching the suríace here. there`s much more to Lxpression Blend`s
interíace than these íour panes. but these are the main sections we`ll be working with íor this
tutorial.
lor the purposes oí our project. let`s make a íunkv glass button using a íew Path lavers and
some transparent gradients.
1he íirst thing we need to do is speciív the size oí the button. (lick on the \hite area in the
Preview pane. 1his is our (ontrol`s (an·as. and vou should notice that clicking it selects the
rele·ant section oí XAML code and also the |(an·as|` laver in the Objects and 1imeline
pane.
(lick on the Properties tab and íind the Lavout categorv. 1hen change the Width and
Height properties to 250. and 100 respecti·elv. As our button will ha·e rounded edges it`s a
good idea to make the (an·as transparent allowing the background oí the application show
through. so drag the A alpha, slider down to 0° in the colour editor.
Sil·erlight Skinnable User Interíaces 14¯


Setting the Opacitv íor our (an·as will aííect all its child objects. so make sure it`s leít on
100°. \ou could use this parameter to íade the (ontrol in and out ií vou wanted to. but let`s
not go too crazv just vet.
Ok. let`s start drawing. (lick the Rectangle tool írom the tool box on the leít-hand side oí the
screen and draw it into the pre·iew window. making sure vou lea·e a bit oí blank space round
the edges. In the top-leít corner oí the rectangle there are two anchor points which vou can
drag in order to round-oíí the corners.
1he rounding eííect is speciíied bv the RadiusX and RadiusY properties in the Appearance
categorv so as vou drag the anchor points thev will both change automaticallv. 1he alternati·e.
oí course. is to tvpe the ·alues in manuallv.
148 bv John Bower

\hen vou`re happv with the roundness oí vour corners select a nice garish red colour íor the
background. and keep the Stroke. outline,. colour as Black.
1his is the Rectangle we will change the colour oí in our application. so we also need to gi·e
it a name to reíer to later. At the top oí the propertv pane change the Name propertv írom
·No Name·` to btnBG`.
Lxcellent! Now it`s time to get all artv and create the glass eííect using gradients. Aestheticallv.
this section oí the tutorial is completelv up to vou as long as vou íollow a íew basic rules: but
we`ll get to them in a sec.
(opv and paste the rectangle vou`·e just created. change the Stroke alpha to 0°. and select
Gradient as the íill tvpe.

\ou can add colours to the gradient bv clicking on the gradient bar underneath the colour
square. and then dragging them along to edit the transitions between them. Just make sure
vou onlv use \hite and Black so we don`t end up with a button that looks strange when we
change its background colour later. Lach colour vou add can ha·e independent alpha ·alues
íor transparencv.
I made a gradient going írom semi-transparent Black. to íullv-transparent \hite. and then
reduced the Rectangle`s o·erall Opacitv to ¯¯°.

Now let`s create a laver to gi·e the button a highlight. (opv and paste the last rectangle vou
made and edit the gradient again. 1hen íade it out a lot more using the Opacitv slider and
alpha ·alues.
Sil·erlight Skinnable User Interíaces 149


Don`t worrv ií it doesn`t look that great. 1he secret to being a good graphic designer is to
assume that e·ervthing vou do is brilliant no matter how bad it actuallv looks.
\hen vou`re happv with vour button hit (1RL-S to sa·e the project and then go back to
Visual Studio. \ou should be coníronted with a message box telling vou that some oí the
project íiles ha·e changed and asking ií vou would like to reload them. Just click \es`. and
we`re readv íor some programming.
3. Creatlne and Readlne Ceekles uslne A$P
I won`t go into too much detail about (ookies here because we`d be digressing írom the main
topic too much. All we need to do is create three (ookies that store colour iníormation. 1his
will be the colour Sil·erlight will use íor our button (ontrol. \e`ll also create three text boxes
in a moment so we can speciív which one we want íor our application. lor the purposes oí
this demonstration. we'll set the cookies to be ephemeral. \e'll set the expirv to be tomorrow.
Ií vou were doing it íor real. vou'd want something a lot more permanent!
Rename 1estPage.html` to 1estPage.asp` and add the íollowing lines oí code abo·e the
<html> tag:
<%
Dim r, g, b
Dim r2, g2, b2

response.cookies("BtnColorR").Expires=date+1
response.cookies("BtnColorG").Expires=date+1
response.cookies("BtnColorB").Expires=date+1

r2 = Request.QueryString("rIn")
g2 = Request.QueryString("gIn")
b2 = Request.QueryString("bIn")

if r2 <> "" and g2 <> "" and b2 <> "" then
response.cookies("BtnColorR")=r2
response.cookies("BtnColorG")=g2
response.cookies("BtnColorB")=b2
end if

r=request.cookies("BtnColorR")
g=request.cookies("BtnColorG")
b=request.cookies("BtnColorB")

if r="" or g="" or b="" then
response.cookies("BtnColorR")=0
response.cookies("BtnColorG")=0
response.cookies("BtnColorB")=0
r=0
g=0=0=0=0
b=0
end if
%>
\hen that`s done. sa·e the íile.
150 bv John Bower
4. Creatlne and pepulatlne Ferm elements uslne A$P
Again. this is quite simple. \e just need to create a standard l1ML íorm with three text
boxes. one submit button. and three hidden íorm elements.
Open 1estPage.asp` again. and add these lines oí code within the <body> tags:
<form action="TestPage.asp">

<input type="text" name="rIn" value="<%=r%>">
<input type="hidden" id="r" value="<%=r%>">

<input type="text" name="gIn" value="<%=g%>">
<input type="hidden" id="g" value="<%=g%>">

<input type="text" name="bIn" value="<%=b%>">
<input type="hidden" id="b" value="<%=b%>">

<input type="submit" value="Save Colour">

</form>
Sa·e the íile. and we`re done with (ookies.
1he hidden íorm elements will be the ones our application reads when it loads to get the
colour íor the button. 1he text boxes will allow us to speciív which colour ·alues we want.
and thev`ll be sa·ed to our (ookies when the user hits the submit button.
5. Creatlne eur User Centrel butten ebject at run-tlme
1his is actuallv a ·erv similar process to the one we used íor our ligh-Speed Loop in mv
pre·ious article. so ií vou get lost here vou should probablv read through that one beíore
continuing.
In order to create our button object. we íirst declare a ·ariable using the New kevword. Open
Page.xaml.·b` and add the íollowing line:
Dim btnControl As New button
1he button tvpe is a direct reíerence to our button.xaml` íile. Ií vou called vour User
(ontrol something other than button.xaml`. vou`ll ha·e to change this accordinglv.
\e then need to set up a ·ariable to point to our main (an·as object.
Dim pCanvas As Canvas
Now we ha·e our ·ariables. we need to alter the Page_Loaded íunction to add our button to
the (an·as when the application loads. lind the Page_Loaded íunction and add the
íollowing highlighted lines oí code:
Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()


' set pCanvas to our root canvas object
pCanvas = FindName("parentCanvas")

' add our control to the canvas
pCanvas.Children.Add(btnControl)

Sil·erlight Skinnable User Interíaces 151

End Sub
Once that`s done. sa·e the íile.
6. Chanelne parameters fer eur Ul ebject
\e`·e alreadv co·ered updating Sil·erlight object parameters in the pre·ious two articles. so
I`ll trv to a·oid repeating mvselí again. All we need to do is set the colour parameter íor the
button UI object we created in Lxpression Blend. 1o do this. we`ll create sub-routines within
the button object so we don`t clutter up the main section oí code íor the application.
Lxpand button.xaml` in the Solution Lxplorer. ií it isn`t alreadv. and then open
button.xaml.·b`. 1hen add a ·ariable we can use to point to our background rectangle.
Dim bg As Rectangle
\e`ll also need three Bvte ·ariables to store the colour the button should be:
Public r, g, b As Byte
Next. let`s write a sub routine that does our colour changing. 1he (olour change method we`ll
create will take 4 bvte ·alues and use them to change the Rectangle.IillProperty ·alue oí
bg.
Private Sub ChangeBG(ByVal a As Byte, ByVal r As Byte, ByVal g As Byte, ByVal
b As Byte)

bg.SetValue(Rectangle.FillProperty, Color.FromArgb(a, r, g, b))

End Sub
\e`·e made this sub routine public so we can call it írom Page.xaml` later.
1o create a MouseO·er e·ent. to applv our colour change. let`s create two new íunctions. 1he
íirst one is called mOver. and the second is called mOut.
Private Sub mOver(ByVal o As Object, ByVal e As EventArgs)

' change the fill colour of the BG rectangle
ChangeBG(255, r, g, b)

End Sub

Private Sub mOut(ByVal o As Object, ByVal e As EventArgs)

' change the fill colour of the BG rectangle
ChangeBG(255, 0, 0, 0)

End Sub
mOverchanges the colour oí our button to the ·alues oí our r. g. and b ·ariables. mOut
returns it to Black when the mouse lea·es the button.
Something to watch out íor when de·eloping Sil·erlight applications is the order in which
íunctions are called. At certain points oí the loading process vou will íind that some methods
return an error. simplv because the object vou`re trving to reíerence doesn`t exist vet. 1he
152 bv John Bower
easiest wav to get around this is to use the Loaded` propertv on the object vou want to work
with. and use it to call a íunction that will onlv work when that object has been created.
Let`s create a sub routine that will point our bg ·ariable to the rectangle we need to change
the colour oí. and then call our (olour change íunction to make our background rectangle
Black when the application starts:
Private Sub bgLoad(ByVal sender As Object, ByVal e As EventArgs)

' set bg to the rectangle that called this sub
bg = sender

' change the fill colour of the BG rectangle
ChangeBG(255, 0, 0, 0)

End Sub
1he sender parameter will point to the object that called this sub routine. In this case it will
be the rectangle we want to change the colour oí. lor this to work. we now add a Loaded
parameter to our rectangle in button.xaml`.
Open button.xaml` and íind the íirst rectangle tag inside the (an·as tags. 1hen add the
íollowing highlighted segment oí code:
<Rectangle Loaded="bgLoad" Width="234" Height="84" Fill="#FFFF0000"
Stroke="#FF000000" Canvas.Left="8" Canvas.Top="8" RadiusY="11.5" RadiusX="11.5"
Opacity="1" x:Name="btnBG" StrokeThickness="1"/>
Now find the last rectangle tag in the Canvas and add the following highlighted
bit of code:
<Rectangle MouseEnter="mOver" MouseLeave="mOut" Opacity="0.51" Width="234"
Height="84" Stroke="#00000000" RadiusX="11.5" RadiusY="11.5" Canvas.Left="8"
Canvas.Top="8">
<t;Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF000000" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="0.174"/>
<GradientStop Color="#00FFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
lit l5 and see what happens.
\our button should be displaved in the top-leít hand corner oí the application. and it should
be Black! Mo·e vour Mouse cursor o·er the button and it will remain Black. íor now.
Sil·erlight Skinnable User Interíaces 153


1. Passlne data frem the becument te $llverlleht
Now the button is being displaved. we just need to pass the data írom our ASP page to
Sil·erlight and we`re good to go.
\ell. to be more precise. we`ll suck the data írom the page using the HtmlDocument and
HtmlLlement objects írom within Sil·erlight.
1o do this we need to íirst Import the System.Windows.Browser namespace. So. open up
Page.xaml.·b` and add the íollowing line to the ·erv top oí the íile:
Imports System.Windows.Browser
1hen we need to create a ·ariable that will point to our application`s host document:
Dim document As HtmlDocument
\e nearlv ha·e íull access to the document and all the elements within it. \e onlv need three
elements though: 1he Red. Green. and Blue hidden íorm elements we created earlier. Once
we are able to access those we need to populate the public r. g. and b ·ariables within our
button.
154 bv John Bower
Add the íollowing highlighted lines oí code to the Page_Loaded íunction:
Public Sub Page_Loaded(ByVal o As Object, ByVal e As EventArgs)
' Required to initialize variables
InitializeComponent()


' set pCanvas to our root canvas object
pCanvas = FindName("parentCanvas")

' add our control to the canvas
pCanvas.Children.Add(btnControl)

document = HtmlPage.Document

Dim rElement As HtmlElement = document.GetElementByID("r")
Dim gElement As HtmlElement = document.GetElementByID("g")
Dim bElement As HtmlElement = document.GetElementByID("b")

btnControl.r = rElement.GetAttribute("value")
btnControl.g = gElement.GetAttribute("value")
btnControl.b = bElement.GetAttribute("value")

End Sub
Setting the document ·ariable to HtmlPage.Document opens up the host page íor us to
read through and get data írom. lor each oí the three elements we`re interested in. r. g. and
b,. we need to get the ·alue attribute and populate our button`s r. g. and b ·ariables.
Now. when vou run the application. vou will be able to speciív and store the colour vou want
the button to change to when vou mo·e vour mouse o·er it!
Sil·erlight Skinnable User Interíaces 155


$ummary
(hanging the colour parameters íor one button object is just the tip oí an extremelv large
iceberg. Ob·iouslv. \ou could store all sorts oí iníormation. in ·arious wavs. and
subsequentlv change anv number oí properties allowing vour users to create their most
comíortable working en·ironment. \our users could ha·e a íield-dav customising the entire
look oí the application: be it position. colour. or stvle.
\our application could well become vour companv`s equi·alent to MvSpace¹: a ·eritable
treasure-tro·e oí elaboratelv awíul design lavouts.It`s completelv up to them and. more
importantlv. vou can`t be blamed íor it!
More importantlv. I hope I'·e shown how easv it is to use Lxpression Blend in the design oí
vour application. lopeíullv. there will be íewer hours spent obsessi·elv mo·ing buttons
around the screen!
Ií vou would like to see this project in action. go to:
http:´´www.deepspaceobjects.com´examples´skinnableUI´
...and all the source code. in both VB and (4. is in the speechbubble at the top oí the article.
156 bv John Bower
lNb£X
AJAX. 8. 83. 84. 85. 86. 8¯. 88. 90. 91. 93. 94.
103. 104. 105. 10¯. 109
AJAX(ontrol1oolKit. 109
ASP.NL1. 1. 8. 9. 10. 11. 12. 13. 14. 16. 1¯. 18.
19. 20. 21. 23. 24. 2¯. 39. 41. 42. 43. 44. 46. 48.
49. 53. 55. 60. 61. 63. 66. 6¯. 68. 69. ¯3. ¯4. ¯5.
82. 83. 84. 85. 90. 91. 93. 9¯. 98. 99. 102. 103.
104. 10¯. 110. 111. 114. 116. 149. 150. 153
asvnchronous processes. 36
(ookies. 143. 149. 150
data objects. 36
dictionarv ser·ice. 104
Lxpression Blend. 128. 142. 143. 144. 145. 146.
151. 155
GridView. ¯5. ¯6. ¯¯. ¯8. 82. 84. 85. 86. 8¯. 88.
105
RowDataBound. ¯5. ¯6. ¯¯. ¯8. ¯9. 80
Ja·aScript. 83. 84. 90. 91. 94. 95. 9¯. 98. 99. 100.
110. 128. 130. 138. 139. 140
Master Pages. 11. 14. 18
Master Page Base (lasses. 12
Master1vpe Directi·e. 11
NL1 3.5. 12¯. 128
Nunit
Page.xaml.·b. 129. 131. 134. 150. 153
Regular Lxpressions. 48. 49. 61
Sil·erlight. 12¯. 128. 129. 130. 131. 132. 133. 134.
135. 139. 141. 142. 143. 144. 146. 149. 151.
153
triggers. 88. 89. 93
URL Rewriting. 60
RewriteModule. 62. 63. 64. 65. 66. 6¯. 68. 69.
¯4
Virtual Larth. 92. 103. 110
Visual Studio. 13. 15. 25. 26. 46. 59. 83. 95. 104.
111. 113. 116. 119. 120. 128. 129. 130. 134.
144. 145. 149
\aiting Pages. 2¯
web ser·ices. 29. 94. 95. 96. 9¯. 98. 99. 100. 101.
102. 103. 104. 10¯. 109
\eb Ser·ices. 103
web.coníig. 14. 16. 23. 24. 25. 39. 60. 61. 62. 63.
64. 65. 66. 68. 69. ¯4. 83. 103. 104. 106. 10¯.
109
\indows Li·e. 103. 104. 10¯. 110

! " # ! * # $ , + . $) 0 ' /

$ # ! *$

$ % & ' $( % # ' $' # #

$) # - $#

! '

" ()

# $ # * $

% +%

&

$

$

, +- . 0 . 1 32 3 / 2

4 $ ' () # * $ +% %

!

" 4

# $ 4

% % 5 0.

&

$ 55 5

7

5

# 65

5

4 5 5

5 5

5 5 5 % 8% 5

5 % 5 5

# 4 5# 5 6 4 6

4 %

:

95 5

5 4 % 5

%

4 % ;

% 5 6 5 5

%

5 5

%

%

5 4 # " 4 4 & 5 . . 5 6 6? 5. % 5 6-< " & ) 5 6-< . # # 4 = * % 65 # 4 5 45 # 5 4 5 # 0 0 2 > > 6-< 66666666666666666666666666666666666666666666666666666666666666666666666666666666 / 66666666666666666666666666666666666666666666666666666666666666666666666 / / 4 ? 5 ? + 5 @ . 6-< 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 0 5 5 666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 0 6-< 66666666666666666666666666666666666666666666666666666666666666666666666 2 2 2 2 3 2 > 2 > 66666666666666666666666666666666666666666666666666666666666666666666666666666 2 2 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 / " " " " 5 . + . ! ? 5 5 6-< 5 + " + 6? 5 " 5 5 ? 5 5 9( 4 5 95 4 5 5 3 3> 3. 5 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 5 666666666666666666666666666666666666666666666666666666666666666666666666666666666666 00 5 # 00 0 03 02 0/ 6 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 0. 5 5 4 % 5 % . 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 1 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 .+1 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666663 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 5 .

6-< 6 666666666666666666666666666666666666666666666666666666666 1 1 1 6 66666666666666666666666666666666666666666666666666666666666666666666666666666 1 66666666666666666666666666666666666666666666666666666666666666666666666666666666666 1 3 &?'66666666666666666666666666666666666666666666666666666666666666666666666666666 1 2 % 65 4 666666666666666666666666666666666666666666666666666666666666666666666666 1 1 (666666666666666 1 .# 5 45 ? 5 5 ? <( + ( " 5 ? +5 ! 5 C + 4 % ( 6 ( 6? 5? ? ? & 5 5 &?'? % &?' % & " . 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 > . 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 1 0 # # % 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 . 5 ? %'# " # ?% + ! 4 ?% ?% " = 4D E %5 4 ! E % # 4 6 ?% ! " % ( 6 5 4 4 " % 6-< 5 (# <( ' ? 5 4 ? 5 <( 4 5 ? 5 ( 5 5 ( 7B8 5 5 5 5 5 5 ? 5 2 1 6666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 1 6-< 6666666666666666666666666666666666666666666666666 2 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 1 A 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 2 . 5 4 6666666666666666666666666666666666666666666666666666666666 > 0 > 0 > 0 > > 6666666666666666666666666666666666666 > 3 66666666666666666666666666666666 > > > 1 > 1 > / > > 66666666666666666666666666666666666666666666666666666666666666666666666666666666 > . . A 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 1 &?' 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 1 > 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 / 2 E %66666666666666666666666666666666666666666666666666666666666666666666666666666 / > 6-< % 66 / > / > / 1 .# &?' &?' % ) & ?% ? & .

2 6-< $ F <( 6-< 666666666666666666666666666666666666666666666666666666 3 3 2 > $F ( 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 . # % 6( 5 5 ( . 3 $ F66666666666666666666666666666666666666666666666666666666666666666666666666 . 2 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 . 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 0 0 0 0 0 0 6-< 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 3 0 2 0 2 66666666666666666666666666666666666666666666666666666666666666666666666 0 > 0 > 0 1 0 / 0 0 . 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 00 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 00 E # # % ? 6-< 66666666666666666666666666666666666666666666666666666 000 000 000 95 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 .# 5 < 5 . 5 G # 5&?' 5 5 5 F"') ( $ H-? . 1 . / . 5 & ! #5 % # % 65 <( . / . . . / 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 2 . ( ( <( ( ?. 5 5 " H 95 $F E ! ? 5 < 5 I 4 5 4 % '# 5 5 ?. 5F"') ?. 2 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 . $ F $ F $ F666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 . % 6-< $F4 5 & 5 #5 F"') 5 . 5 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 .

0> 0> 0> 0 0> 3 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0> > . 95 # ? <( + & D ) 5 . 95 % 6-< 3 5 % % #5 #5 4 4 003 002 00> 00> 001 001 00/ F "'6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 00 F "'666666666666666666666666666666666666666666666666666666666666666666 00 00 00 00 00. . = ' 5 7% $# 5 . 0 2 0 > F "' 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 / 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 / 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 / # 95 7) ' 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0 J 86666666666666666666666666666666666666666666666666666666666666666666 0 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 033 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 033 66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 033 032 032 03> 03/ 866666666666666666666666 03 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 02 0 45 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 02 3 02 3 02 > 02 .# ! 4 . & ! ! F "'< 5 95 5 E % Creating the Reflection ' ) H # ) D # 4 # # D % # ' ) 95 06 ! 6 36 2 6 <( # 06 ! 6 36 2 6 > 6 1 6 / 6 4 & 4 5 95 &.

( 6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 0> 1 .# .

-<H ? 6 ! % 4% # @ 5 E+ % 5 E + 5 "5 4 ? ! . 5 ? < ' " 5 ? " 5 # %%%69 % 65 6 ! "E 4 E + 5 # " 5 %6 ) . 45 5 5 7 =@ %%%6 @ 45 65 86 4 F"' 4 6-< # 7%%%6F"'4 6-< 8 % 5 4 5 F"' H6-< #5 "5 4: 6-< 4 6) : . / 1 +% ( 5 H 95 45 % 6) ' 5 < 4 5 5 % # L6-< 9 5 55 % 6 4 ! ? 4 K ? % 5 9 # 4 " &I6 ) 3 % 4 # 5 = $# % 6) 6 5 # 4 4 5 % 6 ) 5 6-< 6 % 6 * "5 4 4 6-< 45 % 5 4 5 # 4 $ "5 6 5 5 4 4 5 ( 5 5 D 4 % '4 % % 5 4 H . 5 7%%%6 465 86 $ 4 " E 4 7"E 8 E L "5 4 5 # 7" 8% # > 4 ( 5 5 6-< 5 # / 4 4 ( 5 "5 4 # 5 5 E + 5 C' # 6 $ % # 5 . -< M+ # 5 4 5 6 5 @ # 4 4 6-< 5 6-< 6 "E ) 5 F"' 4 6-< # 7 86 : % 5 5 9 % 5 5 4 % %4 6 =@ % @ 6 6 @% =@ @ 6 45 65 @ % 6 6-< $ # 4 6-< 4 # "5 4 5 5 ASPSOFT Microsoft MVP [C#] " 6-< 5 55 " 5 5 -" * 5 5 6 $ 5 4 # 0 4 M( 6) . / $ 5 .5 ! 6.# 1 . ! # 4 6 9 4 6-< 9 5 % 5 45 5 6 4 5 6D 5 4 4 5 # 7" 5 4" E 4 4 6-< F"' #5 8 # 5 .

# "5 4 5% 4 4 I 4 5 4 6D 5 5 5 5 5 $ E '# 9 # 4 6 6 5 D 6 4 6-< "E L 4 5 6) 5 ' 4' 4 HM ? " M 6-< @ $ F 5 . 86 # # =@ %%%6 @ 4 5% 65 5 5 + 0 $ & @ " # * 4% 5 6 % 4 6-< # % @ 6-< 5 % 4 5 % # 5 % # 4 " > 06(@ 5 4 4 N 5 5 6 % 4 4 (M 6-< 36 ) # .F 5 " E % 0.5 % # # * 6 4 # ( E+6-< @ L & ' 6 =@ %%%65 @ 65 65 6 N % 4 % 5 6 . / .( " $ % $ 5 4 ' 6-< 5 ! () # % # % 5 5 70. 6 .

# C' ? ! ! * 6 % 5 6 4 .+ 31 < % 4 . 4 % # 5 6 % 5 95 6 * # % % 6-< 6 H % * 4" 5 5 4 55 # 6 4 4 4 % ? 4 "5 % 4 5 % # # 5 45 ! ? 5 4 4 % 4 % 5 4 4 : 6 6 5 5 O 4 4 6-< 6 5 6 % 5 % 5 5 %: 5 4 ? 4: . % # # % 5 5 5 # ! : O : "5 ( 4% . 5 O .( +2 1 ".!& ( ! 4 5 ? ! 6 5 # + : 5 5 ' 9 % 4 % ! % ' 5 ' 4 5 4 : M 4 < # M 6 3 1 !.

} & % '5 ( FindControl() 5 6 % # 55 Master 5 5 5 5 5 5 5 . % 4 ( $ 4 5 5 4 % ! $4 ) 5 5 % 06 Label Menu 5 M Master % FindControl() Label lbl = this. 5 7 # % 6 Master 5 # 4 ( 5 4 % get get 5 5 Label 5 5 public Label HeaderLabel { get { return lblHeader. 6 5 4 .FindControl("lblHeader") as Label.Page.6-< " 5 00 ( 14 June 2007 & 3 ! 3+2 5 4 5 " 4 % 4 5 % 4 4 5 6-< M # 6 % 5 # 6 # % # # % # 6+ 5 4 5 MasterPageFile 4 % % 5 45 # % 4 6 ( 5 #M 4 % # 45 56 . 45 M M ( 4 4% 5 % MasterType 4 5 . if (lbl != null) { lbl.Text = "Welcome from the content page!". } } & 5 ) ( = 5 5 # Label 5 5 4 5 5 #= 5 6 4 5 MasterType .% M 5 # # 5 5 # 5 6 % . 5 5 5 % 4 4 M 5 5 lblHeader6 95 4 M # # 5 5 4 % 5 6 55 % M 4 86 55 .Master.

5 55 4 5 5 6-< 5 56 5 5 4 5 4 5 4 5 % 4 5 36 MasterPage 5 protected void Page_Load(object sender. EventArgs e) { this. 5 % + 5 5 5 # % 6 6) %# 6 5 8 2 6 4 5 ( 4 5 5 5 5 5 App_Code 4 MasterType VirtualPath # M % M PreInit # VirtualPath 5 7 5 # 4 6 Label 5 MasterPage % % public abstract class BaseMasterPage : MasterPage { public abstract Label HeaderLabel { get. } } & + # 4 +5 ( 5 BaseMasterPage 5 4 5 % 5 # 5 6 4 5 M 5 5 5 5 5 4 5 6 > 4 5 6+ 4 # 4 BaseMasterPage % ( 4 # 5 HeaderLabel HeaderLabel 6 BaseMasterPage public partial class Templates_InheritedMasterPage : BaseMasterPage { public override Label HeaderLabel { get { return lblHeader.Text = "Label updated using MasterType " + "directive with VirtualPath attribute. FindControl()6 +$ %( # 5 % . } & * 55 = 5 #6 MasterType M 5 4 5 MasterType & 5 # 5 4 5 % 5 .". EventArgs e) { .0 <%@ MasterType VirtualPath="~/Templates/WebsiteMasterPage.Master. 5 M % $ % % % 5 .HeaderLabel.master" %> M" 6 6&. } } protected void Page_Load(object sender.

% 45 5 5 4 5 M P # BaseMasterPage % ( 5 PreInit 6 5 5 .Web.lblHeader.Text)) { this. ( 5 6 / ' % " % 5 H 5 86 ' ( 5 5 86 $ %! 4 5 E 6 %3 5 7 6-< # 4 > ( 5 45 4E 6-< 75 % 4 # ( M 4 5 5 6H 5 MasterPageFile 4% 4 4 4 5 4 # 4 7.Now.UI. public string RuntimeMasterPageFile { get { 6&. 6 M PreInit . = # 4 55 + " M% 5 4 5 6 5 5 45 TypeName HeaderLabel MasterType's VirtualPath % (= <%@ MasterType TypeName="BaseMasterPage" %> 5 Master % % % 4 # 5 5 55 % 5 5 4 55 36 5 TypeName HeaderLabel 5 5 % 4 # 5 % 4 % M 55 FindControl()6 ) % # HeaderLabel 5 .IsNullOrEmpty(lblHeader.ToLongDateString().6-< " 5 03 //Provide default text in case content page doesn't set any if (String.Text = DateTime. } } } & .4 # 1 % M# % 5 # %6 ) % # % # 6 5 #6 86 5 6 # 5 4 E 6-< 6 % 5 4 5 5 4 5 "5 4M 5 ! 4 M M 4 5 #6 % 5 MasterPageFile 5 5 % M % % # # 6-< M 5 5 4 4 5 5 # 5 % 5 BasePage # 4 RuntimeMasterPageFile 6 BasePage # # 5 MasterPageFile RunTimeMasterPageFile 6 4 public class BasePage : System.Page { private string _RuntimeMasterPageFile.

cs" CodeFileBaseClass="BasePage" Inherits="WorkingWithNestedMasterAndBasePage" Language="C#" MasterPageFile="" RuntimeMasterPageFile="~/Templates/NestedMasterPage. } } & -5 ( 6 # 5 # 4 % (= 4 5 # 4 54 4 4 RuntimeMasterPageFile6 BasePage 5 MasterPageFile 7 # 8 4 4 RuntimeMasterPageFile <%@ Page AutoEventWireup="true" CodeFile="WorkingWithNestedMasterAndBasePage. M # # 6. .OnPreInit(e).IsNullOrEmpty(RuntimeMasterPageFile)) { this.5 .master" Title="Nested Master Page Demo" %> 4 RuntimeMasterPageFile BasePage # % 5 MasterPageFile 6 # # 4 BasePage 4 5 % # 5 # # %6 % M # 5 PreInit 5 # # 5 4 MasterPageFile $ %( 5 $ % 4 $ # 33 4 6-< 6 # 4 5 .MasterPageFile = RuntimeMasterPageFile. 5 5 # 5 4 % # E 6-< 4 UserControl MasterPage 5 5 M # 5 5 5 # 5 6 % 5 4 # 4 % =@ % @ 4 6 ( 5 % % % 6+ 5 6 5 % > ) "' 5 ! 4% @ 5 4 % # 9 4% 4 4 5 . @% 6 > 6 ( 86 5 % % 4 6 6 06 06 Q 6 6 E 6-< % 65 4 7 4 % 6 5 % / 6 4 . . } } protected override void OnPreInit(EventArgs e) { if (!String.aspx. } base.02 return _RuntimeMasterPageFile. 5 # M L 5 5 5 E+6-< 5 5 7! 86 . } set { _RuntimeMasterPageFile = value.

snk 8= sn.org/TR/xhtml1/DTD/xhtml1-transitional.w3. 5 5 4 ContentPlaceHolder 5 6 E 6-< 5 % 5 4 7 6 5 5 4 5 6 ( 5 E 4 5 5 ! ( 6 6-< 2 2H 6 6 % 4 % 4 % >5 keyfile.dtd"> <html xmlns="http://www.6-< " 5 0> <%@ Master Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.exe -k keyfile.0 Transitional//EN" "http://www.snk6 & > > 6 6 7 / = 4 MasterDemo 8 Bin 4 5 5 ( 6 # 86 D 4 % % % 5 6 E 6-< 7% 4 6 > .org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> Header <br /> <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server"> </asp:ContentPlaceHolder> <br /> Footer </div> </form> </body> </html> & 36 36 . ( 5 + 5 66-< 6 (5 R 4 4 4 % % .w3.

M # 5 % 6 % 5 % M # S = 4 4 % <%@ Master Language="C#" Inherits="ASP.% 7 5 4 ! 5 5 % =@ %%%6 @ 65 * 5 CreateResourceBasedLiteralControl() ) "'4 % 5 5 5 5 6 CreateResourceBasedLiteralControl() ( .master 4 <asp:Content> 4 4 6 5 6 5 6 5 5 5 % 5 @ 5 % 65 4 4 5 % . Version=0. 4 5 5 5 # 5 M 5 ?45 5 5 6. 4 5 % 0 6 # 4 5 .master.masterpagebase_master" %> . 4 5 5 4 % = 4 6 4 % 5 4 5 M 5 MasterPage. . # 4 5 7N 6 6 N 06 8T 5 95 4 4 6. 5 # 5 # M 4 5 6. PublicKeyToken=cceb8435cfc68486" /> </assemblies> </compilation> D M 95 7 ! E M 6 M . 7 Inherits # 5 E 6-< 95 % 8 4 4 4 M 5 6 5 86 4 6& 2 6 4 5 MasterPage.0. Culture=neutral.0.? 6 # 5 5 5 6- 4 ! 4 8 5 # 5 5 4E 6-< ( 5 4 M5 5 4 5 ! 86 6. 6 6 c:\Windows\Assembly ! % % <( % 5 % 6 6 % 65 4 4 5 6 ( 6H 5 F"' 4 M # 5 4 % % % 4 % / / 6 6 <system.web> <compilation debug="true"> <assemblies> <add assembly="App_Web_masterpagebase.01 1 1 .cdcab7d2. . % % ( @ M ) "' 8 ! % 4 4 .0. 5 > 5 6 4 6 # 7 5 PublicKeyToken M% 6.master 7 5 ContentPlaceHolderID ContentPlaceHolder1 # .

4 % 4 5 .xslt" LanguageCookieName="EgovCookie/Language" /></td> . 5 # 5 # 5 % " 6 D M # 6 % 5 5 56.% # ?45 5 6 M 5 6 # CreateResourceBasedLiteralControl() 5 + 4 " 5 % % % 5 5 6 . .xslt" LanguageCookieName="EgovCookie/Language" /> </td> 4 % 7 8= <td align="left" valign="top" height="45"><www:HtmlOutput ID="egovHeader" runat="Server" XmlSource="/XML/SiteLinks.xml" XsltSource="/XSLT/Header. 5 ) "' . 4 % 5 M . 5 55 5 # 5 # 5 6-< M # 6 % # 5 5 5 .xml" XsltSource="/XSLT/Header.6-< " 5 0/ <td align="left" valign="top" height="45"> <www:HtmlOutput ID="egovHeader" runat="Server" XmlSource="/XML/SiteLinks.

4 @ 6 5 @ 5 = 4 4 4 06 4 5 % 4 5 % 6 * 4 6 @ 8 4 5 " 4 " 4 # 6 5 # A " % ( 4 5 5 4 4 % N ) " N % 5 A M 4 49 N ) " 7 =@ %%%6 @ % . 5 5 4 4 5 N 65 A @ 6 <# 4 * # 0G 0 # 5 +6 ( 5 6 ( 5 5 * # 6 % +6 * ( 6 ( (% 6H % % 95 # # .0 " 05 May 2006 3 56 Sharing Web Parts across multiple pages " * 4 5 6+ % 4 * 45 4 5 5 # # J .

4 " % G MultiPageSqlPersonalizationProvider # % # % 6 5 4 4 % * 45 6 5 5 7 4 $$ +4 % # % 5 % # 4 4 N 5 5N $ 4 $) $ ' 4 % 6 6 6-< # 4 . % 6 6-< # % # 6 4 45 8 # 4 M% 5 % 0 4 4 . * # % 4% 5 5 6-< %# 5 5 4 # 7 5 # # 6 # = 5 # 56 .6-< % 5 % % 6 6 4 6. 6H 6 % 55 4 4 *4 4 5 % 6 5 5 5 % 5 5 * 5 0.

4 95 6 # 5 45 4 5 5 45 * # * # 4 4 ? ? 6 ? ? & .4 5 8 4 % 5 4 5 4 6 5 4 # # 6 6 6 # 6 4 5 # # 7 * # 5 4 5 4 # 4 F"' M 5 5 6 # 4 E4 4 55 ( 4 % 5 4 6-< % 6-< # # % 5 5 = C' # H 5 <(5 4 7 M M 9 % 486 D 4 4 % 5 # 3 5 # 4 # # " C' % 5 3G # 4 # . 5 # 5 .4 5 3 * * $ 0 5 6-< 4 # # $) $ ' 5 5 4 # 5 4 2 PersonalizationProvider 4 5 4 5 5 C 5 ? 5 * 6 # # 4 6 % 5 * 5 5 . " 6-< 4 ( $ $) $ ' ' 6 % 6 1 * . 95 6-< ! ' + H4 3 * # % 4 4 . ( * ?5 * 5 5 * C " " S T S T * S T S T 5 + + 5 4 ? 5 ' 4 # 5 5 5 .4 5 % * 6 # 4 . 4 .

% # # 6 # # # 9 5 % # % M # % % . 4 4 4 4 6 ( N 6 (N7 4 N " 5 @ 6 (N . # 4 4 ( 6. # 5 5 5 . H 5 . 4 6 % 5 % 4 5 J. 0 # # 4 # 4 % % ' $ 5 .M 9 . % 6 5 . M " C' SqlPersonalization% 4 % 4 4 5 * # 4 # 5 5 5 # (3 4 4 4 5 55 6 6-< 6 SqlPersonalizationProvider 5 5 5 # $6 5 56 5 4 5 4 5 5 45 . 5 4 % 4 6 +$ % ( 0 % 7 $ # 6D 5 % % 5 % 0 4 $) $ ' 4 4 # % 4 5 6 % 5 MultiPageSqlPerson- alizationProivder 5 5 5 4 bold 5 % 5 4 5 = . * . 4 5 5 45 6 . . 6.6-< 6 0 # + * # % S T " " + # * 0G 5 " * 0 5 # % " 5 4 5 5 6 4 # 5 45 5 # 6 % Provider 5 * C' # # 6. 6 5 6 ( # # 4 % 6 5 86 SqlPersonalizationProvider 5 A+ 5 # 5 % # 4 # % 6 . 4 5 # % % 5 5 * 5 5 # 4 C' C' # # 5 6 C' # C' $% % % # # 5 % 4 6.

using System. } public override int ResetState(PersonalizationScope scope. } protected override void ResetPersonalizationBlob(WebPartManager webPartManager. userInactiveSinceDate). ref byte[] userDataBlob) { base. int pageIndex.Empty) { query. } public override int GetCountOfState(PersonalizationScope scope. string[] paths.PersonalizationStateQuery query.PathToMatch = GroupName.GetCountOfState(scope.ResetPersonalizationBlob(webPartManager. byte[] dataBlob) { . int pageSize. ref userDataBlob). } } public override PersonalizationStateInfoCollection FindState (PersonalizationScope scope. DateTime userInactiveSinceDate) { return base.string path. using System. } return base. } return base. } protected override void LoadPersonalizationBlobs(WebPartManager webPartManager. ref byte[] sharedDataBlob.WebControls. } public override int ResetUserState(string path. userName). pageSize. } protected override void SavePersonalizationBlob(WebPartManager webPartManager. } set { _groupName = value. new string[] { GroupName }. usernames).WebParts. GroupName. query.string path.Empty) { query. PersonalizationStateQuery query) { if (query.PathToMatch != String.PathToMatch = GroupName.string path. userName. query). ref sharedDataBlob.PathToMatch != String. string userName. GroupName. string userName.Web.UI. pageIndex. string userName) { base.ResetState(scope.string[] usernames) { return base. namespace Providers { public class MultiPageSqlPersonalizationProvider : SqlPersonalizationProvider { private string _groupName = “PageGroup”.ResetUserState(GroupName. out int totalRecords) { if (query. public string GroupName { get { return _groupName.LoadPersonalizationBlobs(webPartManager.FindState(scope. out totalRecords).

SavePersonalizationBlob(webPartManager. configSettings). userName. } } //class } //namespace ' M ? % # GroupName . 95 . base. configSettings.IsNullOrEmpty(GroupName)) GroupName = “PageGroup”. 4 PathToMatch # % # % # % # GroupName6 5 % 5 GroupName # 6 # Initialize 5 % * # 6 configSettings Initialize NameValueCollection 95 5 4 # 4 % 65 4 7% M 65 4 86 GroupName # # 5 configSettings["groupName"]6 . % % 4 % % 5 4 5 6 % 5 % 5 GroupName 5 4 % 5 45 # 4 # % # % % . 4 GroupName _groupName 4 GroupName # % % 65 4 6 % % 4 # # * 4 6D # 6 . dataBlob).NameValueCollection configSettings) { GroupName = configSettings[”groupName”].Collections. 4 # # 4 6. GroupName. 4 N N 5 4 5 5 5 Initialize 5 % (5 5 5 ( 5 N N 4 % 65 4 6 H 5 # %# 6 # * # % % 6 # 6-< 4 5 5 % 4 5 ResetState 5 4 % 6 GroupName # 4 % 4 5 6.Initialize(name.6-< 6 3 base.Remove(”groupName”). .Specialized. 6D % GroupName 4 4 % 6 5 % LoadPersonalizationBlobs ResetPersonalizationBlob ResetUserState SavePersonalizationBlob G 5 4 6 FindState GetCountOfState % -H % % 5 PersonalizationStateQuery 95 6 H 4 4 PathToMatch 6. if (string. } public override void Initialize(string name. # N N # 4 configSettings 6 A 5 Initialize 5 (5 5 NameValueCollection6 . 4 N N -H % 65 4 5 configSettings["groupName"] 6H ( 4 # 4 GroupName # GroupName 4 # 4N ! N . System.

. <system. 4 -< +6 44 ( SqlPersonlizationProvider 5 5 * 7 % SqlPersonalizationProvider 5 % 5 5 4 4 86 N N N ! " I N # 5 4 % # 4 # % % # 6 N # 6" . 4 ( # . # < ( # 5 4 6D % % 6-< # 5 4 6 connectionStringName N 2 3 N5 5 % 5 5 5 6-< 6 784 5 04 Q 4 4 % 5 6.% 6 U $ * $) $ ' # 55 * V VU # % 5 4 = % * 5 4 % V V # U5 4 V 5 # 5 V V U 6% V VU% 4 % 65 4 6 4 % 5 MultiPageSqlPersonlizationProvider <configuration> .. * # N 5 MultiPageSqlPersonalizationProvider 5 # M 56 . <webParts> <personalization defaultProvider="AspNetSqlPersonalizationProvider"> <providers> <add connectionStringName="LocalSqlServer" groupName="MyGroupKey" name="MyMultiPageProvider" type="Providers.web> </configuration> ' M45 7 5 5 4 U 5 4% 5 V 4 2 % #6 6 4 U V - # & . 5 " & . 4 % 76 6 N S T N 86 % 65 5 46 defaultProvider 6-< % 5 # U 5 4 * V 4 4 4 # .. % # # " 4 * 5 4 % 5 # 5 4 * # 6D 4 65 5 4 % % # 5 5 4 4 G .2 + %$ % . 4 5 5 5 % 5 * 4 5 U5 C' 5 # % V 5 6 6 # - ! ..web> .MultiPageSqlPersonalizationProvider" /> </providers> </personalization> </webParts> </system.

<webParts> <personalization defaultProvider="MyMultiPageProvider"> <providers> <add connectionStringName="LocalSqlServer" groupName="MyGroupKey" name="MyMultiPageProvider" type="Providers.% 5 5 # 4 5 6 " 5 MultiPageSql- PersonalizationProvider " .web> </configuration> 4 ... M % " (6 + %$ % <# " " 5 $ %$ # # " 5 5 7 4 4 4 $) $ ' 4 8 6 4 4 %$ 5 5 = <asp:WebPartManager runat="server" ID="wpManager" /> 5 4 .6-< 5 4 6 # # 5 > ( 5 54 " 6. 4 5 4 % 5 # # 5 6D 4 %= <asp:WebPartManager runat="server" ID="wpManager"> <Personalization ProviderName="MyMultiPageProvider" /> </asp:WebPartManager> . 4 # 5 4 % 5 5 5 4 4 5 54 54 5 ( 5 % 65 4 % 5 46 # 5 # .MultiPageSqlPersonalizationProvider" /> </providers> </personalization> </webParts> </system. 4 5 5 # 54 MyMultiPageProvider 4 # 5 4 # ( 5 54 # 5 4 " 6+ % # 4 AspNetSqlPersonalizationProvider .web> . <system. ( AspNetSqlPersonalizationProvider6 4 5 4 4 6-< -< +6 44 Q 4 % 5 6 . 4 % MyMultiPageProvider 4 5 5 # = <configuration> ... % H E ' 4 5 7# 5 6D % 5 6H 5 5 % % 5 5 % 5 ' % 4 6' ( 8 4 .

1 6 4 5 5 6 5 " +6 6 4 ) & " 65 4 6 4 ( # # B B B B B B B 5 6 6 6 +6 6 6 +6 6 ( ( ( ( ( ( 5 4 ' " 4 " " " 6 # 4 " % ( 5 6 4 5 # 6 % 6 6 4 6 N 5 6 4% ( # % +6 6& N ( 6 5 N # 6 5 +6 (6 5 > 4 # 5 % . * 5 * 5 # 4 6 (6 4 N 4 % N 6 5 % 4 +6 (6 % . 5 7 6 % 4 E * 5 # 5 " " " 3G 5 5 + 4 . " ( N " * 6 (6 4 # 4 % 4 # % 4 5 5 6 5 6 % 5 5 4% 4 % 4 2 4 % 6 ( 6 = .. 5 6! 5 . 4 # % 4 . % .% # * % 4 # 5 4 5 % 4 H 5 " C' 5 J C' 6 % 5 # % .

% 5 % 4 6 5 4 . $ 4 4 $ +4 % * % % % % % # 6 06 5 5 5 4 6 & 56 ' 5 5 % . 6 . . 4 5 # G 5 4 % % 2 using System.( 3 &" 3 3 & & 3 16 July 2007 H 4 % 5 5 # 6 . % % 5 % 95 5 % 4 4 .%% ' : 4 % 5 5 5 5 ( 6 % 6 5 4 4 4 6 % 4 . 6-< / 3( . 4 % % 4% % 5 # 5 % 5 % 6+ 4 6 6 % % 4 W % : 5 5 % 6 A % X # % # 5 ( 4 4% 5 % # G 4 4 4 5 % 9 % 5 5 % % # 5 % % 5 # 5 56. .. 5 5 6 5 % % 5 % 95 5 . 6 # 5 5 5 5 4 4 5 5 4 % 5 5 5 5 .

Web. public static class SimpleProcessCollection { private static Hashtable _results = new Hashtable().! using System.Web. public static string GetResult(Guid id) { if (_results. using System.Remove(id). using System.HtmlControls. using System.org/1999/xhtml" > <head runat="server"> <title>Simple Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Button ID="btnStart" runat="server" Text="Start Long-Running Process" OnClick="btnStart_Click" /> </div> </form> </body> </html> using System. using System.Collections.UI.Web.Threading.Security.ToString(_results[id]).WebParts.Web. } } public static void Add(Guid id.Web.6 4% 4 % 5 5 5 % . public partial class Simple_Start : System. } else { return String.UI.w3.UI.Web. } public static void Remove(Guid id) { _results.aspx.Collections.Contains(id)) { return Convert.Empty. using System. } } " # % # .dtd"> <html xmlns="http://www.w3.0 Transitional//EN" "http://www.WebControls.Data. 5 4 5 . using System.Configuration. using System.org/TR/xhtml1/DTD/xhtml1transitional. . using System.WebControls.UI. using System.Page . string value) { _results[id] = value.cs" Inherits="Simple_Start" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. 6 5 % 5 .Web. % 5 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.UI. using System.

System.Collections.gif" runat="server" /></p> <h1> <asp:Label ID="lblStatus" runat="server" Text="Working. public partial class Simple_Status : System. "Some result.UI.Web.dtd"> <html xmlns="http://www.UI.").WebControls."></asp:Label> </h1> </div> </form> </body> </html> using using using using using using using using using using System.org/1999/xhtml"> <head runat="server"> <title>Simple Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <p align="center"> <asp:Image ID="ImageStatus" ImageUrl="~/Images/Wait. protected void btnStart_Click(object sender. System.Web.org/TR/xhtml1/DTD/xhtml1transitional. Please wait.HtmlControls.. there could be a call for a remote web service Thread.Page { protected void Page_Load(object sender.UI.Web.UI. System.Start()..aspx?ID=" + id. Thread th = new Thread(ts). System.UI.ToString()).Web. 6-< . System.Web.Add(id.WebControls. System. System.. EventArgs e) { // chech if the page was called properly . System. EventArgs e) { // assign an unique ID id = Guid.Sleep(9000).0 Transitional//EN" "http://www. } // this is a stub for a asynchronous process protected void LongRunningProcess() { // do nothing actually.w3.cs" Inherits="Simple_Status" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. th.aspx. // redirect to waiting page Response. } } 4 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.Redirect("Status..Security. // start a new thread ThreadStart ts = new ThreadStart(LongRunningProcess).. { protected Guid id.Web.Web.WebParts.w3. but there should be real code // for instance. // add result to the controller SimpleProcessCollection.Data. System.Configuration.NewGuid().

9 % * 45 6 # 4 % % 46 6 % 4 & 56 ) % 6 " % $ $ .Empty) { // no result . 4 % 5 4 2 5 # 4 5 $4 5 4 5 6 % 4 5 5 % 5 % 5 % ( 6 % 4 public static class MultiProcessCollection { private static Dictionary<Guid.3 ! " # if (Request. public static int GetProgress(Guid id) { if (_results. } } else { Response.Redirect("Start.". } } } D 4 5 5 5 6 4 % # % 5 . we have the result lblStatus. "3").apsx").AddHeader("Refresh".Params["ID"] != null) { Guid id = new Guid(Request. ImageStatus. int> _results = new Dictionary<Guid.Params["ID"]).ContainsKey(id)) .Text = "Job is done.let's refresh again Response. } else { // everything's fine.Visible = false. int>().GetResult(id) == String. // check if there is a result in the controller if (SimpleProcessCollection.

Data.1. } _results[id] = _results[id] + 1.Collections. System.UI.Add(id.ContainsKey(id)) { _results.dtd"> <html xmlns="http://www.Web.Web. 0).Web.ContainsKey(id) && _results[id] > 0) { _results[id] = _results[id] .0 Transitional//EN" "http://www. } public static void Add(Guid id) { if (!_results. System. . } } } 5 5 4 4 % 5 5 4 % 5 5 5 5 4 (6 5 6 5 4 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start. System.WebControls. } else { return 0. <asp:Button ID="btnStart" runat="server" Text="Start Three Long-Running Processes" OnClick="btnStart_Click" /> </div> </form> </body> </html> using using using using using using using using System.Configuration.w3. System. System.aspx.Web. 6-< 30 { return _results[id].UI. } } public static bool IsCompleted(Guid id) { return (GetProgress(id) == 0). } public static void Remove(Guid id) { if (_results. System. System.org/TR/xhtml1/DTD/xhtml1transitional.cs" Inherits="Simple_Start" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.org/1999/xhtml" > <head runat="server"> <title>Progress Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:CheckBox ID="cbProgress" runat="server" Text="Show Progress" /> &nbsp.w3..Security.

Remove(id). MultiProcessCollection. protected void btnStart_Click(object sender. ThreadStart ts = new ThreadStart(LongRunningProcess1). MultiProcessCollection.org/TR/xhtml1/DTD/xhtml1transitional.WebParts. th. ts = new ThreadStart(LongRunningProcess2).WebControls.Web.Web.Add(id).w3. MultiProcessCollection.Add(id).ToString()).NewGuid().Redirect("Status. ! " # if (cbProgress. MultiProcessCollection. ts = new ThreadStart(LongRunningProcess3).Start(). public partial class Simple_Start : System.ToString()).Sleep(3000). th.org/1999/xhtml"> <head runat="server"> <title>Progress Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <h1> . } protected void LongRunningProcess3() { Thread.HtmlControls. th = new Thread(ts). using System.UI.w3.Start().aspx.0 Transitional//EN" "http://www.Remove(id).Checked) { Response. MultiProcessCollection.Redirect("Progress.Sleep(5000). EventArgs e) { id = Guid.UI.Page { protected Guid id. } else { Response. th. MultiProcessCollection.Start().UI. } protected void LongRunningProcess2() { Thread.Add(id).Web.Sleep(7000). } } protected void LongRunningProcess1() { Thread.aspx?ID=" + id. Thread th = new Thread(ts). } } 4 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.cs" Inherits="Simple_Status" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.dtd"> <html xmlns="http://www. th = new Thread(ts). using System.Threading.3 using System.Remove(id).aspx?ID=" + id.

UI. System.Web.Web. System.Web. System.Data.WebParts. System. System.. } . System.UI.Web..Web. "1").Drawing.WebControls. System.Security..AddHeader("Refresh".Web. System.".Page { protected void Page_Load(object sender.Data. System.UI.Redirect("Start.Web.Text = "Job is done.UI. public partial class Simple_Status : System.Web.Configuration. EventArgs e) { if (Request.aspx").Web. } else { lblStatus.WebControls.UI.Collections.. System.Web.Web.Web. if (!MultiProcessCollection.Configuration.Web.Params["ID"]).WebControls.IsCompleted(id)) { Response.Security. Please wait. System. namespace MyControls { public class SimpleProgressControl : WebControl { private int _Max.HtmlControls..WebControls.WebParts. System. System. public int Max { get { return _Max. System. } } else { Response. System. System. System.UI.UI.UI.UI. } } } % 5 5 4 % 4 using using using using using using using using using using ( 6 6 2 % 5 % 5 System.HtmlControls. System. 6-< 33 <asp:Label ID="lblStatus" runat="server" Text="Working.Params["ID"] != null) { // check for result Guid id = new Guid(Request."></asp:Label> </h1> </div> </form> </body> </html> using using using using using using using using using using System.

32
set { _Max = value; } } private int _Value; public int Value { get { return _Value; } set { _Value = value; } }

!

"

#

protected override void Render(HtmlTextWriter writer) { writer.AddAttribute(HtmlTextWriterAttribute.Width, this.Width.Value.ToString()); writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0"); writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0"); writer.RenderBeginTag(HtmlTextWriterTag.Table); writer.AddAttribute(HtmlTextWriterAttribute.Height, this.Height.Value.ToString()); writer.RenderBeginTag(HtmlTextWriterTag.Tr); for (int i = 0; i < _Max; i++) { // background color if (i < _Value) writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor, ColorTranslator.ToHtml(this.ForeColor)); else writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor, ColorTranslator.ToHtml(this.BackColor)); writer.RenderBeginTag(HtmlTextWriterTag.Td); writer.RenderEndTag(); // td } writer.RenderEndTag(); // tr writer.RenderEndTag(); // table } } }

4

2
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Progress.aspx.cs" Inherits="Multi_Progress" %> <%@ Register TagPrefix="my" Namespace="MyControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Progress Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <my:SimpleProgressControl ID="ctlProgress" runat="server" BackColor="blue" ForeCOlor="red" Max="3" Value="0" Width="300" Height="30" /> <h1> <asp:Label ID="lblComplete" runat="server" Text="Process complete." Visible="false"></asp:Label></h1> </div> </form> </body> </html>

,

6-<

3>

using using using using using using using using using using

System; System.Data; System.Configuration; System.Collections; System.Web; System.Web.Security; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.WebControls.WebParts; System.Web.UI.HtmlControls;

public partial class Multi_Progress : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (Request.Params["ID"] != null) { Guid id = new Guid(Request.Params["ID"]); int p = MultiProcessCollection.GetProgress(id); ctlProgress.Value = 3 - p; if (p != 0) { Response.AddHeader("Refresh", "1"); } else { lblComplete.Visible = true; } } else { Response.Redirect("Start.aspx"); } } }

%

4 6 36

& 5 " 3.

# 5

%

6

$ %4
( 95 5 5 6 %

'
% % 5 95

84 $
# 5 # 95 %

4$
% 5 %

$4
4 5 4 5

31 5 95 4 2
using using using using using using using using using

! 4 5 6 92
System; System.Data; System.Configuration; System.Web; System.Web.Security; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.WebControls.WebParts; System.Web.UI.HtmlControls;

" 4 4 % 45

#

5

5 4

5

4% 4 4 G # 5

public class FeedbackObject { private string _result1 = String.Empty; public string Result1 { get { return _result1; } set { _result1 = value; } } private string _result2 = String.Empty; public string Result2 { get { return _result2; } set { _result2 = value; } } private int _progress = 0; public int Progress { get { return _progress; } set { _progress = value; } } public bool Complete { get { return (_progress == 100); } } }

4

2
using System; using System.Collections; public static class FeedbackProcessCollection { private static Hashtable _results = new Hashtable(); public static FeedbackObject GetResult(Guid id) { if (_results.Contains(id)) { return (FeedbackObject)_results[id]; } else { return null; }

UI.Web.HtmlControls. } } 4 4 % # 4 % 6 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start. for (int i = 0. ThreadStart ts = new ThreadStart(LongRunningProcess).w3.Threading.Add(id.ToString()). EventArgs e) { id = Guid.org/1999/xhtml" > <head runat="server"> <title>Feedback Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Button ID="btnStart" runat="server" Text="Start Long-Running Process" OnClick="btnStart_Click" /> </div> </form> </body> </html> using using using using using using using using using using using System.UI.Web.Redirect("Status. Thread th = new Thread(ts).0 Transitional//EN" "http://www. FeedbackObject stat) { _results[id] = stat.UI. FeedbackProcessCollection. protected void btnStart_Click(object sender.Web. } protected void LongRunningProcess() { FeedbackObject fo = new FeedbackObject().aspx?ID=" + id. fo).Data. th.NewGuid().Web.WebControls. i <= 100.WebParts. i++) .Start().org/TR/xhtml1/DTD/xhtml1transitional. System. System. public partial class Simple_Start : System.Remove(id). } public static void Remove(Guid id) { _results. System. System. System. 6-< 3/ } public static void Add(Guid id.w3.Collections.. Response.UI.WebControls. System.Configuration.Web.dtd"> <html xmlns="http://www.Page { protected Guid id. System.Web.Web. System.UI.Security. System.aspx.cs" Inherits="Simple_Start" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. System.

System.GetResult(id).Sleep(50).dtd"> <html xmlns="http://www.w3. ctlProgress. System. lblComplete.GetResult(id). System. } } } ! " # 4 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.Visible = true.Value = FeedbackProcessCollection.Result2.Result1 = "First result.".Data. if (i == 100) { fo.3 { Thread.0 Transitional//EN" "http://www.Result2 = "Second result. public partial class Simple_Status : System. System. System.HtmlControls.Text = fo. EventArgs e) { if (Request.". System. System.Params["ID"]).Progress = i.GetResult(id).Web. "1").aspx.Collections. } else { FeedbackObject fo = FeedbackProcessCollection.cs" Inherits="Simple_Status" %> <%@ Register TagPrefix="my" Namespace="MyControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.Progress.AddHeader("Refresh".Web.UI.Web." Visible="false"></asp:Label></h1> </div> </form> </body> </html> using using using using using using using using using using System.WebControls.UI. if (!FeedbackProcessCollection.org/TR/xhtml1/DTD/xhtml1transitional.WebControls. System. lblComplete.Configuration.Security.WebParts.Result1 + " " + fo.w3.Page { protected void Page_Load(object sender.Params["ID"] != null) { // check for result Guid id = new Guid(Request.Complete) { Response.Web.Web.Web.UI.org/1999/xhtml"> <head runat="server"> <title>Feedback Waiting Page</title> </head> <body> <form id="form1" runat="server"> <div> <my:simpleprogresscontrol id="ctlProgress" runat="server" backcolor="blue" forecolor="red" max="100" value="0" width="300" height="30" /> <h1> <asp:Label ID="lblComplete" runat="server" Text="Process complete. System.UI.Web. fo. } } . } fo.UI.

} } } .org/TR/xhtml1/DTD/xhtml1transitional.w3.dtd"> <html xmlns="http://www. System. System.Web. else { Response.cs" Inherits="Simple_Status" %> <%@ Register TagPrefix="my" Namespace="MyControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.Data.aspx. System.org/1999/xhtml"> <head runat="server"> <title>Feedback Waiting Page</title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"> </asp:ScriptManager> <div> <asp:UpdatePanel ID="UpdatePanel1" runat="server" ChildrenAsTriggers="true"> <ContentTemplate> <asp:Timer ID="Timer1" runat="server" Interval="1000" OnTick="Timer1_Tick"> </asp:Timer> <my:SimpleProgressControl ID="ctlProgress" runat="server" BackColor="blue" ForeColor="red" Max="100" Value="0" Width="300" Height="30" /> <h1> <asp:Label ID="lblComplete" runat="server" Text="Process complete. 6-< 3.% % 5 4 4 5 5 6 5 5 % ' % 8. .Configuration. ' <# % % 4 5 % 4 5 6 6 5 6D 4 $ "5 % 67 ( 5 # % 5 9( 4 % 4 9( <( 7 =@ 9(6 6 @ % @ 8 5 " 5 & 5 4 "5 4 9( <( 4 6-< 6 6<( 5 4 % 65 4 4 4 5 5 4 4 4 68 5 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.Collections.aspx")..0 Transitional//EN" "http://www.w3." Visible="false"></asp:Label></h1> </ContentTemplate> </asp:UpdatePanel> </div> </form> </body> </html> using using using using using System.Redirect("Start. System.

UI. System.GetResult(id).UI.Web.UI. % .WebParts.Params["ID"] == null) { Response.Web.Redirect("Start.2 using using using using using System. } } protected void Timer1_Tick(object sender.Complete) lblComplete.WebControls. ctlProgress. System. System. if (FeedbackProcessCollection.Security.Visible = true. } } % 4 % # 5 9( 4 5 % 4 6 6.Progress. 5 5 5 5 9( 4 4 5 5 6 % 4 ( 5 J 86 5 6.aspx").Page { protected void Page_Load(object sender.Web. System. ! " # public partial class Simple_Status : System. EventArgs e) { if (Request.Web.% + 4 .Params["ID"]).Web. EventArgs e) { // check for result Guid id = new Guid(Request.Web.GetResult(id).WebControls.HtmlControls.Value = FeedbackProcessCollection. 4 .UI.UI. 4 % 5 5 # % 4 4 % 4 4 % % % 4 % 7 5 5 ( 4 6 # 5 4% 5 5 .

Your new password is [$PASSWORD$]. Please keep this password in a safe location and quit losing it.\r\nYou recently requested a password reset. 5 # 5 % 5 5 ( 6 4 5 5 6 H 6-< 6 % M 4 % $ 5 5 5 4 4 5 5 4 4 5 6 % ( % 4 # 4 # = % 4 5 % 56 D # Dear [$NAME$]. Thank You H 5 5 . Please keep this password in a safe location and quit losing it.+ ( 3 % 5 5 5 5 5 . 5 4 9 4 A 6 "Dear " + Name + ".? 5 6-< 2 0 1 2 01 November 2006 . Thank You . You recently requested a password reset. Please keep this password in a safe location and quit losing it. You recently requested a password reset. % 5 * 5 8 # 6 N " % N % # % ( % 5 7 5 [$NAME$] 5 = = 86 ' M N YC / +#N > 1 7 % [$PASSWORD$] Dear Matt. Your new password is " + password + ".\r\n\r\nThank You" 4 5 5 5 5 % 6 5 5 % 5 5 6 5 AD ( ) "' 5 6 % 5 # L 4 5 4 5 . 4 9 5 % 6 # 5 % # # % 4 5 9 N 5 * 5 ) "' 5 N# 5 5 5 4 4 5 45 # 4 ## % N N# 6-< 6 6 % 5 5 4 6 6-< 4 5 5 ) "' 5 6. 5 5 = 55 4 5 % 5 9 % % 5 # 5 . Your new password is 5ZQS76Bv.

Age:{2}. You recently requested a password reset. 4 4 5 5 5 5 5 % # 76 6 5 6) M 5 5 ( % 5 9 4 4 % 4 8 ( Dear <%=Name%>. Gender: Male.Format String. "Male". M 5 String. 6. Thank You H 5 N " % 6 ( 5 ) 5 M < D 5 % % % 6ND 5 5 9 5 6-< 4 % % = 6-< <asp:Button runat="server" id="btnPwd" text="<%=Name%>.Format String. "160"). //OUTPUT-->Name: Matt. 5 % 4 5 = 6-< 6 . Weight: 160 4 {1} 5 {0} 5 N " # % 5 % N # 4 5 5 5 6. Please keep this password in a safe location and quit losing it.Replace StringBuilder. (5 6 4 6 D # 5 # 5 5 4 # # 4 N " 5 5 N # 5 4 5 5 4 # {N} % .2 5 # 5 % 5 6 5 # # % 9 $% # . Age: 25.Format("Name: {0}. Weight:{4}". "5'10\"". Your new password is <%=Password%>. Height: 5'10". Click Here to Email Your Password" /> - 5 4 5 4 5 5 6 6 M 4 4 $ %: $ 5 # # 5 ( 4 5 ' 55 4 % 6 5 5 6 ( 4 5 4 % 4 String.5 • • • <5 . "Matt". Height:{3}. Gender: {1}.Replace 5 4 %6 3 4 $ 6-< 5 5 5 5 4 4 5 5 5 5 = # 6. "25".

"!")."X". Age:{2}."H". . D 5 = 5 Replace StringBuilder 5 ( 5 Replace % StringBuilder 5 4 = 5 5 # 6 ) %# StringBuilder Replace 4 5 5 5 M# StringBuilder 5 StringBuilder myBuilder = new StringBuilder("Hello."Z". Weight:{4}". //OUTPUT-->HELLO . N YN D 5 % 5 ( NN J {2} %5 # 6 6 5 5 6 " [$NAME$] N N FN DN 6-< 5 5 # % ) "' 4 5 5 ) "' 5 # 4 5 5 #6 {7} 5 # 6 $% ' + % 4 5 M # 4 4 ' $% Replace '$ 4 5 5 # # 5 5 4( String ' StringBuilder 5 6 5 5 4 # % Replace String 56 D 4 6 String # 5 56 String 5 5 5 Replace 4 Replace 4 5 5 5 5 % String 56 5 4 6. 4 % Replace 4 5 6D 5 # %# # 4 % ( = 5 4 string myString = "Hello. Height:{3}."L".Format("{1}{3}{2}{2}{0}".Replace("[$NAME]". //You have to assign the value of the Replace function back //to the string to change the value myString = myString. //This does NOT change the value of myString myString.". "Matt"). 5 5 # 5 # = 5 String.? 5 6-< 2 3 // Throws an exception because there are 4 tokens // but only one replacement value String.Format("Name: {0}. Gender: {1}."E". "Matt"). my name is [$NAME$]."Matt")."Y". "Matt")."). my name is [$NAME$].Replace("[$NAME$]". "O".Replace("[$NAME$]". myBuilder.

UI. DateTime. pageSource.Replace("[$CURRENTDATE$]".Render(htmlWriter). 4 5 6. } protected virtual void RunPageReplacements(StringBuilder pageSource) { } } ' 0G ? 5 5 .smith@somecompany. base. 4 A 5 4 6-< 5 6-< # # ) "' 5 5 4 6 % Z # % 4 # 5 5 % % %5 .Web. 5 % M5 4 System.Replace("[$SITEEMAIL$]". System.2 2 [$NAME$] 4 5 # 5 5 5 5 6 5 5 % 5 6 5 ) "' 4 6-< M 4 5 ) "' %M 6-< 4 $% 7 H % # % 95 # 6. "john. pageSource.UI.ToString()). } protected void RunGlobalReplacements(StringBuilder pageSource) { pageSource.Web. RunGlobalReplacements(pageSource).IO.ToString("MM/dd/yyyy")).Text. System. System.UI.Now.Replace("[$SITECONTACT$]".com").Web.Page6 % 5 4 = public abstract class TokenReplacementPage : Page { protected override void Render(HtmlTextWriter writer) { //Create our own mechanism to store the page output StringBuilder pageSource = new StringBuilder().Write(pageSource. //Output replacements writer. //Run replacements RunPageReplacements(pageSource).Page6 5 4 5 9 # 4 4System. "John Smith"). 4 %+ 6-< 5 5 6 5 % 5 6 5 M # TokenReplacementPage 5 4 5 % 4 4 5 ) "' 5 # 5 % 6 4 5 6-< 5 5 6 4 # 5 4 5 5 6. StringWriter sw = new StringWriter(pageSource). TokenReplacementPage TokenReplacementPage 5 using using using using System. HtmlTextWriter htmlWriter = new HtmlTextWriter(sw).

( M # pageSource 4 % 6 5 45 6 5 5 & D 4 5 4 6 pageSource pageSource. "REPLACEMENT VALUE").Page6 5 4 • • • 4 6 # 5 4 TokenReplacementPage 5 TokenReplacementPage 5 Render G % RunGlobalReplacements ) "' G %4 5 4 5 45 5 5 5 RunPageReplacements G "$ % % $ 4 $% % % 6% '$ HtmlTextWriter # Render HtmlTextWriter % % # % 5 6 5 5 % 6 (5 % 5 ) "' 4 5 5 # ) "'4 5 6 ) "' 5 Render 6.Render 4 5 pageSource6 # 4 45 4 5.? 5 6-< 2 > 5 5 # 4 Page 4 5 % 5 4 5 4 Page 5 6 = StringBuilder TokenReplacementPage 5 System.( % N N % 5 % % = StringBuilder 5 % StringWriter sw pageSource 5 5 6 * sw % pageSource 6 % % pageSource6 . 4 Render Render % 4 5 % % 5 5 4 % % % 5 % 5 55 6 % 5 5 pageSource6 . 5 4 5 % 6 Render # 5 % 5 Render RunGlobalReplacements6 pageSource % = RunPageReplacements 6 H 5 # .UI.Render % % 6 4 Base.Replace("TOKEN".( % 5 % HtmlTextWriter htmlWriter sw TextWriter6 5 5 6 * htmlWriter % sw % htmlWriter % sw % 5 % pageSource6 % htmlWriter Base. 5 5 5 5 % # = # RunGlobalReplacements Replace TokenReplacementPage 5 # Replace 5 % 5 M# 5 # 6 6? ( pageSource [$NAME$] 5 5 % % 5 5 5 4 9 NAME 5 % 5 .Web.

"This replacement only runs on this page!"). 4 4 7 5 5 2 # 4 5 4 5 @ 6 4 E ( 4 5 % 5 7 5 4 8 4 % 1 2 5 5 =@ %%%6 @ 5 5 = 5 % 4 # 202 1 5 0 2 2 5 5 5 4 5 4 5 45 % 6 5 5 4 5 5 6-< M5 5 5 5 4 % # 5 5 5 5 # 5 6 % 5 PageSpecificReplacementsPage. } } ' G H# ? ? 5 <( + 4 % % 65 # & ' @ 5 #6 6 7 : # 0 5 .4 1 2 4 5 (A [ / 8 4 .Text.aspx RunPageReplacement # 4 ' 6 5 5 # # % % % 4 4 # 5 % 6" % [$PAGESPECIFICTOKEN$] # % PageSpecificReplacementsPage.Replace("[$PAGESPECIFICTOKEN$]".StringBuilder pageSource) { pageSource.aspx + 4 5 6 .2 1 5 5 4 6 6D M% 55 % % 44 4 % % % % 4 5 45 5 = 5 45 5 45 5 5 6 5 6 5 # 5 TokenReplacementPage 5 # RunPageReplacements RunPageReplacements 5 5 % 5 ( 5 6 public partial class PageSpecificReplacementsPage : TokenReplacementPage { protected override void RunPageReplacements( System. # 6-< 5 ) "' 5 5 5 5 ) "' % 4% 5 .aspx6 4 % 5 4 4 % % 6 Default. # % 5 .

? 5 6 6-< 5 5 2 / 5 6 6 .

= 4 4 4 5 5 5 % ( M %M 5 6 % 5 5 4 6D 4 % 4% % # M 4 5 = 4 4 % 5 (5 5 1 4? ( 5 5 4 <( 5 5 5 5 <( 6) \[\$(?<functionName>[^\$]*?)\((?:(?<params>. 7 4 5 =@ %%%6 @ % 4 5 % .% 5 5 4 5 " # A9 4 5 ( " $ $% $ .2 &. 5 # 65 @ 5 5 95 % 5 5 @ 6 6 5 5 6-< 5 % . @ % 5 % 4 5 # 5 4 1 ? 5 5 6-< @ % 5 % 8 4 5 56 4 5 4 6 % 2 7<48= !" ## $ #! " #! # & & % # # " $ &!! # #& # # ! 5 2 5 5 4 5 5 ( ( M %# 4 # 5 5 5 6 6 .10 January 2007 .|(?=\))))*?\)\$\] . 5 M % 5 4 5 ( ? % <( 5 ( 5 5 . 31 ! 1 2 . M # 4 % 5 6 . # # % $ # ! $ & 5 1 % 8 ! ! " # ! = 4( 4 ( 6 J.+ ( 3 .*?)(?:. $ ? <( 6D % 5 5 4 1 \ • • • 4 5 5 5 6 % % 5 % ( 5 4 6 ? 5 ( 6 45 ( ( % 8 5 ? < % 4 <( 4 6-< 5 6.

% 5 2 . )$ * ) M # 5 6-< 4 5 = 1 1 % 5 ? <( H ? 5 .param2. 5 <( + 5 M% 5 ? 5 M 5 6 .30)$] [$GetContent(ContentGroupName.5. 6 6 4 5 5 5 4 = 5 5 N 5 6 4 # N ( # . 5 4 6 4 4 5 5 5 45 4 5 % 5 5 5 ( ? % # 4 <( 5 5 4 % 5 4 5 5 45 % 5 N N ( 4 % 4 4 % 4 5 5 5 5 # 4 6 ( 4 N 5 ? 4 4 ' 4 % 5 N 4 $ M ( ( 5 ( 4 6 2 ( 4 4 5 % 6" 5 ( % 5 5 4 D @ . # 5 45 ? <( ) M 5 = 5 NOTE= 5 2 5 ? 5 5 1 6 <( % 56 5 4 4 4 % % 5 6 4 6 4 5 6 % = # 44 5 4 1 2 % 9 5 6 5 % 5 6 4 5 % # 4 4 4 <( .15.M 9 %6 6-< 6 .20. 5 4 5 5 5 ( 4( 6 5 % % 4 & 2 4 5 N ! ) 5 5 4( N J % 9 5 5 4 [$FunctionName(param1.ContentKey)$] 5 5 (5 5 5 5 % 4 5 5 ? <( 6 ( 5 % 5 % 6H 5 5 5 6.25.? . 6 5 # 5 45 4 .paramX)$] [$Add(1.10.

4 6.functionName GetContent o Capture Group – params FormLetter Salutation Match – [$UserName()$] o Capture Group – functionName UserName o Capture Group – params Match – [$Password()$] o Capture Group – functionName Password o Capture Group – params Match – [$GetContent(ContactInfo. CustomerSupportEmail)$] o Capture Group – functionName GetContent o Capture Group – params ContactInfo CustomerSupportEmail 5 5 5 6 45 4 5 4 4 5 5 4 5 5 4 5 5 5 % # 4 * 4 # 6 . 5 55 % # [$GetContent(FormLetter. Thank You. 5 = 4 ( ( 6 % 5 %M 5 4 5 6 ( ( 5. Salutation)$] Thank you for registering on our website. ( 4 # 4 ( 54 ( 5 . Salutation)$] o Capture Group . 6 5 5 5 5. please feel free to email us at: [$GetContent(ContactInfo. Customer Service % 5 • ? 5 <( 4 % = 4 % % 4 • • • Match . Please use the following information to login to your account: Username: [$UserName()$] Password: [$Password()$] If you have any problems.> 4 5 D 5 % 5 5 45 4 5 5 5 4 5 5 4 5 6 2 4 % 5 4 5 4 ( (6. CustomerSupportEmail)$].[$GetContent(FormLetter.

M # @ 5 6 ( % M ( 6 5 ( 6.8 4 4 % 7>8 ( 5 6 5 % 5 5 5 5 N 4 5 5 5 5 6 + M5 5 7.$ D # 5 ( 5 M 5 4 4 6. N . 8 55 5 N 5 5 5 5 # 6 ( M N 5 5 % 4 (6 4 5 = . 4 5 5 = >? ' $ 7. 5 7 % # 6 45 4 ( # 5 8% 5 ( % 5 6 . ] 5 5 .>6 ] 5 5 4 NN % % N % &$ D %4 5 5 $4 4 4 4 4 % 4 4 = # G 5 6 (4 @ 7 G 5 C G6 + 55 ( 5 4 4 G 5 ( 5 5 5 4 = @ G% 5 6 M 5 ( = @ 5 4 5 4 A 4 = ( 4 (5 6D 5 5 6 5 5 4 4 45 5 4 5 5 # 6. 5 ( % % 4 4 5 5 ' 4 ( %M 6 M ( 4 %M % 5 5 4 =5 # % (= 4 # 4 5 ( 5 5 N 5 6.? <( + ? 5 6-< > 0 % $ . 5 6 4 % (= 6D 5 = ( 4 % 4 ( ( % 4 6 • $ ? 7 ?A B # & 55 • • • N 5 5 5 5 N5 5 5 5 ( ( 6+ • 5 5 4 5 5 ( ?A D 7 =# 5 ?A ( .

@6 + % .*\) ( = ( 5 O 5 5 = 5 4 .> + $4 $ 5 5 5 5 5 5 % 5 (5 E G 6 5 5 5 ( 5 5 G 4 5 5 5 (5 6D F 5 5 5 4 5 ( .? 4 * 5 5 (who is a boy) and Jill (who is a girl) . N N 4 P N . 5 5 5 5 5 % 5 # 5 5 % 5 5 5 % 5 5 5 5 45 5 4 3 2 6D 54 ( 4 # 5 5 < # % E 86 <$F 6 5 5 5 E =" 6 F E2 0 9 = = "5 F % (5 6 4 5 5 4 5 6. 5 # 4% 4% 4 7 4 5 5 5 5 ( 8 % % N# 5 ( N ( 4 4 6" 6 ( = M ( % Jack (who is a boy) and Jill (who is a girl) ran up the hill D \(. E =" G F 5 4 5 5 5 5 (5 *D 6 # % E G F+ 6 ?@H EJ L>MGNO6 IFK @ C 5 \d+ $ 4 ( 4 6 ( ( = ( 4 % % 4 5 5 4 ? 5 N 4 4 O P OA PA HI H %I H% I H 4 N 4% N 6 . 0 4 N 25 0 > # N 0 # 5 0 > 0 2 5 = Y H Y H <( 5 + % N. % ( N 4 55 # % 4 6H 5 5 5 5 4 4 4 5 5 5 5 .

$ 5 4 5 $ \[\$(?<functionName>[^\$]*?)\((?:(?<params>. 7% 6 0 6 7% *. M 6. 5 4 5 5 = 5 % 6) 5 5 \(. 5 %6 4 S ] H 5 % 4 4 4 % # % 4 5 4 5 6! BS B] 4 5 5 5 4 5 S HI<-]T6 ] 7A U4 5 S ^ B] V (5 5 % 5 ( 4 5 5 5 6 S 4 ] 6.|(?=\))))*?\)\$\] 4 5 5 5 45 4 % .*?\) D # % 5 # 5 5 4% = 5 5 5 5 % 6 5 5 4 (who is a boy) (who is a girl) 4 % M% 55 5 %M 5 5 4 5 ( 4 % % 4 5 S ]T 5 ] 6 5 $ %' * + % % 4 6-< = 4 ( $% $ . # 55 4 5 4 5 (5 5 <(5 5 74 5 5 - 8 .? <( A+ 5 + 4 = 8 8 8 $ 7% ? 5 5 @ 5 6-< 4 > 3 5 6 ?% . 5 4 4 # 6 5 5 % 5 OA.*?)(?:.

> 2
T _ A <(5 C 4 G 5 5 5 * 4%

8 ' B7 5 068 68 7A = 5 4

4 H 5 H? & 5 4 = 4 5

7A U 6 _ A

V

5 C 5 4 G 5

7

8

*

4%

8 - ( 4 5 5 5

N 4 4 %# ( % % 4 5 5 % % 4 5 & 5 # * M 5 4 6 6 , 4 5 6 5 4 5 5 * % 6 N 4 6 5 5

7A =

R 7A [ B8 8 8 < ; 5 4 5 4

H? # H < < 6& 5 * ( # 4 4% #

8

<

?

<(

+

?

5

6-<

> >

_ A

C

4 G

5

*

4%

B8 4 B] BT 5 ]T ' ' 4 4 5 5 ] T

- %

# % M %

4% %

( 5 6

' %$% $ . $ '
4% 4 4 ? 1 1 5 % 4 2 2 6-< 5 5 5 5 6 5 4 4 ( 4 4

4
# 4 % 1 2 6, M 5 5

%4
5 % 5 %M 5 # 5 % % 6

+ % ( =
using using using using using using

System; System.IO; System.Text; System.Text.RegularExpressions; System.Web.UI; System.Reflection;

public abstract class TokenReplacementPage : Page { private static Regex functionRegex = new Regex( @"\[\$(?<functionName>[^\$]*?)\(" + @"(?:(?<params>.*?)(?:,|(?=\))))*?\)\$\]", RegexOptions.IgnoreCase | RegexOptions.Singleline); protected override void Render(HtmlTextWriter writer) { StringBuilder pageSource = new StringBuilder(); StringWriter sw = new StringWriter(pageSource); HtmlTextWriter htmlWriter = new HtmlTextWriter(sw); base.Render(htmlWriter); RunRegularExpressionReplacements(pageSource); RunPageReplacements(pageSource); RunGlobalReplacements(pageSource); writer.Write(pageSource.ToString()); } protected void RunGlobalReplacements(StringBuilder pageSource) { pageSource.Replace("[$SITECONTACT$]", "John Smith"); pageSource.Replace("[$SITEEMAIL$]", "john.smith@somecompany.com"); pageSource.Replace("[$CURRENTDATE$]", DateTime.Now.ToString("MM/dd/yyyy")); } protected virtual void RunRegularExpressionReplacements( StringBuilder pageSource) {

> 1
//Regular Expression Replacements MatchCollection matches = functionRegex.Matches(pageSource.ToString()); //Iterate through all the matches for (int i = matches.Count-1; i>=0; i--) { //Pull function name from the group string functionName = matches[i].Groups["functionName"].Value; string[] paramList = CreateParamList(matches[i]); string replacementValue = string.Empty; //Run different code based on the function name switch (functionName.ToUpper()) { case "ADD": int sum = 0; for (int i2 = 0; i2 < paramList.Length; i2++) { sum += int.Parse(paramList[i2]); } replacementValue = sum.ToString(); break; case "CONTENT": replacementValue = ContentManager.GetContent(paramList[0], paramList[1]); break; default: replacementValue = String.Format( "<!-- Could not find case statement for {0} -->", functionName); break; } //Make replacements pageSource.Remove(matches[i].Index, matches[i].Length); pageSource.Insert(matches[i].Index, replacementValue); } } //Create string array containing each parameter value protected string[] CreateParamList(Match m) { string[] paramList = new string[m.Groups[2].Captures.Count]; for (int i = 0; i < paramList.Length; i++) { paramList[i] = m.Groups["params"].Captures[i].Value; } return paramList; } protected virtual void RunPageReplacements(StringBuilder pageSource) { } }

.
D 5 4 56

% $.$
4 M # ( 5 5

4
5 4 4 5 5 1 # 8 4 6

4 %. $ )
D 5 5 5 % (5 5 6-< 4 4 % 1 M 5 ( = 4% % =

Regex.Matches(inputString, regularExpressionPattern)

Value.IgnoreCase | RegexOptions.Groups["functionName"]. RegexOptions. 5 4 %M 5 4 ( 5 %1 95 5 .|(?=\))))*?\)\$\]". M 6 5 = string functionName = matches[i]. ? 4 5 5 5 % 6 55 % 5 # 4 # 6 # ( 5 5 4 5 5 Q 5 6 % 4 # % 5 5 2 M 4 0 2 ( 5 4 # " 5 Q % 6 7 N 5 4 5 4 5 N 6 4 4 5 95 5 5 6 # % # Q (6 # # # % 4 5 5 % # % # % 5 .Singleline). $ 1 5 1 8 4 ' 95 5 2 2 5 95 0 0 2 4 4 5 95 6.? <( + 5 ? 5 5 6-< 1 ( 95 % 1 95 6 # # 1 6 95 % > / 54 6 4 % % 4 5 (5 4 4 M 5 5 95 ( 4 % 5 4 5 # # 5 = private static Regex functionRegex = new Regex( @"\[\$(?<functionName>.*?)" + @"(?:.M 5 %M 6 6 % % $. (5 4 2# 2 2# 2 # 2# 4 4 5 % 2 2 2 ( 5 5 95 % 5 5 # * 6 . ( 1 95 % % 66 # % 5 5 5 6 5 5 5 ( % ( ( M 5 5 6 6D 5 54 = 6 # 5 # 6 .( 4 4% 4 4 4 % 1 2 55 % ) "' 4 6. 4 5 ? (# 6 4 4 5 2 95 5 4 4 5 6 6 5 # 5 % % % 5 5 % 6.( % 5 4 5 4 % % 4 4 5 % 4 % 4 5 4 5 4 5 # 5 % 5 2 .*?)\((?:(?<params>.

Captures[i].Groups["params"]. . 5 6 ( 4 4 4 7 MatchObject. <5 5 4 4 # % 4 % 5 6 % 2 95 6 .M # 4 4 5 5 6 5 6 5 5 4 ( 4 5 ( ( 2 4 5 ( 4 5 4 # # 5 5 5 % % % 5 4 4 4 % 6 % M 4 6 55 M% 6 6 ( # 4 4 2 4 Q 2 95 5 5 # 6 .Value +5 5 % = 4 5 55 # 6. +$ $ # ' ( 5 6D 5 % 6. M # % % 4 5 5 5 # #<74874 4 5 # 4 55 5 % # 4 5 = % 5 6 # 6.Value D 5 5 = MatchObject. 4 4 5 4 # 5 # # MatchObject.Value + % ( 5 6 . % # 55 4 0 2 7 # * 5 6 5 5 5 # 45 2 95 95 5 4 .Groups["functionName"].Groups["params"].5 0 2 # 6 .> D 5 4 5 # # 5 4 5 5 . ' # % 4 4( '$ 5 6D # 1 ' 1 8 4 (5 % 5 % 1 N 5 2 N4 6 4 5 1 5 5 " 6 . 5 6 = 5.

? <( + ? 5 6-< > . + 4 % % #6 & Q Q 4 6 65 4 ! 5 E 4 4 # 7 B B ( ? " 5 65 7 5 4 ( 5 % 5 65 # 5 5 ( 5 5 4 6 6 ( 5 5 4 5 % 6 4 5 4 5 5 ( 5 % # 5 4 4 5 5 % % 4 N 4 8 4 5 % 4 # = ? 5 5 5 5 # 6 5 4 5 N7 6 6 4 8 % 5 5 5 # # 4 5 6 5 % 5 % + 4 ? # 5 # ) .% # 5 J 4% % 4 % 4 % 4 5 5 5 4 5 5 4 ( ( % 54 5 5 # 4 5 6 % % 5 5 ( 6 .

56 6-< 6 6 45 4 01 March 2007 5 55 54 # # 4 &?' % % &?' 6 # % " . 5 6 4% &?' MM 0 = 5 =@ %%%6 @ 5 &?' 5 # 65 4 % 5 4 6 5 5 @ + @ 10 @ @ 6 ( . 5 5 4 5 ( " 3 3 & 1 31 :1 -. % 6' ! '( & (&& 5 5 4 < # 4 # # 4 5 4 % # ! ( ( ! ! '( & (&& ! ( ( # # Copyright.1 ! " # +1 ( - . 45 6 % # &?' 4 56 ( &?' 9 5 4 % 5 &?' % 4 % % % urlMappings 5 % % 65 % . 6-< 6 5 6.aspx %4 5 Help6 & % # &?' % 5 6 5 # 5 Response. 5 5 # % # # # 5 # &?' 6 % # 5 5 5 9 4 5 5 &?' % 5 4 % 4 ). &?' 6 5 % 55 &?' 4 &?' 6 % % 5 = [ 1 K" [0 K [0 M 4# % % 65 65 @ + @ + =@ %%%6 @ =@ %%%6 @ @ @ 10 @ @ @ 0 4 % # &?' 5 % # # 5 ( 5 .$* $ % < % 5 5 &?' 6 % 5 6-< . % 5 % # % 4 5 # 6 4 708 7 8 % % M 4# 5 45 4 4% 5 5 55 # 4% 5 5 % 5 % 5 6 (A D 4 . .aspx Contacts.Redirect7 & 86 ) % # % 4 # 5 5 A % 95 % 5 # 4 5 % 5 6 % 5 % 4 6. .

simple-talk.aspx" /> </urlMappings> 4 5 ! '( & (& & ! '( & (&& ! # 6 4 4 % 5 # 6 4 % ( 5 5 ( # ! ( ( ) % ( ( % # # # 5 5 5 % 5 % .aspx 5 8 ! '( & (&& ) "' 6 % 5 &?' 5 6-< 6 4 ! ! '( & (&& 4 action = <form name="formTest" method="post" action="http://www.Day=$3"/> <rule source="(. . 8= 7 1 0 ~/ 5 5 web.aspx" /> <add url="~/Support/Contacts.aspx" mappedUrl="~/Help/Contacts.config 4 4 % 5 <urlMappings enabled="true"> <add url="~/Info/Copyright. # 7% 5 4 ! 4 ( ( ) form 5 &?' * # &?' 4 Contacts.com/Help/Contacts.aspx?Folder=$1"/> </rewriteRules> .aspx" mappedUrl="~/Help/Copyright.*)/Default.config web.web 4 4 6-< 6 .aspx?Year=$1&amp." * # 5 5 =@ @ 5 5 # 86 % 45 4 4 web.Month=$2&amp.1 / 16 4 (8 % 6 7 6-< # 6 .% =@ 6% @ " 6-< 6 4 4 6 - 5 .config 4 6 74 ( 5 5 5 4 5 4 # 4 % 5 5 5 5 = % 5 % <rewriteModule> <rewriteOn>true</rewriteOn> <rewriteRules> <rule source="(\d+)/(\d+)/(\d+)/" destination="Posts.aspx" destination="Default. .$* $ % % 5 4 ) " @ 4 6 (8 % ' " &?' % # 5 .config 5 ) 6 5 6 4 " 465 % % 74 @ @ * 54 4 4 # % 46 4 5 web.&?'? % system.aspx" id="formTest"> </form> &?' ? @ @ % ? =@ @ # 54 6-< 4 74 4 @ 6 <( Q( 8 6 5 465 @ ( &?' 6 6 &?' # # % @ .

5 ! ( (.aspx?Year=$1&amp.1 </rewriteModule> ! " # .*)/Default. . % % # # # &?' . 6 5 5 6 F"' 4 web.config= <configSections> 4 <sectionGroup name="modulesSection"> <section name="rewriteModule" type="RewriteModule. / 01 2 +. 4 01 % . RewriteModuleSectionHandler. 5 web.Day=$3"/> <rule source="(.Month=$2&amp. RewriteModule"/> </sectionGroup> </configSections> 4 % 5 % configSections 5 = <modulesSection> <rewriteModule> <rewriteOn>true</rewriteOn> <rewriteRules> <rule source="(\d+)/(\d+)/(\d+)/" destination="Post.aspx" destination="Default..( 6 &amp\ # 4 rewriteModule % 4 6 . * +. ( .config & Posts.aspx % = ! '( ( .(.aspx?Folder=$1"/> </rewriteRules> </rewriteModule> </modulesSection> % 4 # % 5 % # M # ! 5 6 5 = ! '( & (&& # M &?' % .( # .-.config 4 configSections 5 5 5 web.(+. ( .(+.

Configuration. namespace RewriteModule { public class RewriteModuleSectionHandler : IConfigurationSectionHandler { private XmlNode _XmlSection. private string _RewriteBase.Collections. System. System. System.&?'? % 4 6-< 6 1 3 : % % % % ' 6 % 4 5 5 45 4 5 web. private bool _RewriteOn.Generic.config % # 5 IConfigurationSectionHandler 45 7 " . System.config % 6 4 5 6-< 5 6. } } public bool RewriteOn { . System.Xml.4 System.Text.Web.Configuration / ' % 5 86 using using using using using using 4 5 %$ 5 4 %= 4 54 web. % % 4 System.Web % % System. } } public string RewriteBase { get { return _RewriteBase. public XmlNode XmlSection { get { return _XmlSection.

System. System. '( & (&& 8 4 54 %= ! ( ( #( / 5 % 0 # 7 <rule source="(. object configContext.aspx" destination="Default.config try { _XmlSection = section.Web.InnerText). System. # % 5 .Xml.Specialized.Generic.config SelectSingleNode 4 XmlNode 5 5 # % Create XmlNode6 4 6 . System. .SelectSingleNode("rewriteOn"). } } } RewriteModuleSectionHandler % * rewriteModule 5 4 web. ex)).1 2 ! " # get { return _RewriteOn. } } public object Create(object parent.Text.Collections.ToBoolean( section.XmlNode section) { // set base path for rewriting module to // application root _RewriteBase = HttpContext. % $ # 5 $ $ $* $ # &?' % % 6& &?' . } catch (Exception ex) { throw (new Exception("Error while processing RewriteModule configuration section.aspx?Folder=$1"/>.Current.Collections.".ApplicationPath + "/".Request. _RewriteOn = Convert. namespace RewriteModule { public class RewriteContext { // returns actual RewriteContext instance for // current request . } return this. 5 4 =@ %%%6 @ % =@ %%%6 @ # % &?'= 65 4 65 @ + 5 @ @ @ A [+ &?'= @ 4% 55 5 4 M # = M 6 % 5 5 5 using using using using using # % 5 System. // process configuration section // from web.*)/Default. System.

Current 5 M# 54 * $ %.config 4 6 5 class RewriteModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) {} } RewriteModule_BeginRequest % &?' . If there is no RewriteContextInfo // item then this means that rewrite module is turned off if(HttpContext. } } } } D 5 &?' # 4 5 # 5 not 55 . _Params = new NameValueCollection(param).Current.Contains("RewriteContextInfo")) return (RewriteContext) HttpContext. string url) { _InitialUrl = url. } } private string _InitialUrl.Empty.Current. public NameValueCollection Params { get { return _Params. else return new RewriteContext().% 5 % 5 M % % 5 5 HttpModule= 6 % 5 % &?' (5 6 4 4 web. .Items. } set { _Params = value. } set { _InitialUrl = value. } public RewriteContext(NameValueCollection param. public string InitialUrl { get { return _InitialUrl. } private NameValueCollection _Params.&?'? % 4 6-< 6 1 > public static RewriteContext Current { get { // Look for RewriteContext instance in // current HttpContext. } } public RewriteContext() { _Params = new NameValueCollection(). _InitialUrl = String. M # % 6 RewriteContext.Items["RewriteContextInfo"]. 5 # &?' % 5 5 4 # .

SelectNodes("rule")) { try { Regex re = new Regex( cfg.RegularExpressions.QueryString.Length == 0) return.Length != 0) { // check for QueryString parameters if(HttpContext. string path = HttpContext. System. xml.Request.Current. path = path + sign + HttpContext. RegexOptions.Request. // module is turned off in web. System.Current.Configuration. } void RewriteModule_BeginRequest(object sender.Current. System. namespace RewriteModule { class RewriteModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { // it is necessary to context.Text. if (path.Web. System.QueryString. System.Count != 0) { // if there are Query String papameters // then append them to current path string sign = (path.GetSection ("modulesSection/rewriteModule").Generic.IndexOf('?') == -1) ? "?" : "&".XmlSection.BeginRequest += new EventHandler( RewriteModule_BeginRequest). Match match = re.Attributes["destination"].Attributes["source"].Collections. System. EventArgs e) { RewriteModuleSectionHandler cfg = (RewriteModuleSectionHandler) ConfigurationManager.Text.RewriteBase + xml.Replace( path. System.Xml.SelectSingleNode("rewriteRules"). foreach (XmlNode xml in rules. // load rewriting rules from web.Specialized.Web. System.Match(path). System. } . if (match.UI.ToString().Request.RewriteOn) return.IO.Collections.Success) { path = re.1 1 5 HttpContext.config if (!cfg.InnerText.config // and loop through rules collection until first match XmlNode rules = cfg.IgnoreCase).InnerText). // there us nothing to process if (path.RewritePath ! # 5 # " # 6-< using using using using using using using using using using 6 System.Path.Current.

Current.1 +.CurrentHandler != null) { Page pg = (Page)app.CurrentHandler is Page) && app.PreRequestHandlerExecute += new EventHandler( RewriteModule_PreRequestHandlerExecute).Context. } void RewriteModule_PreRequestHandlerExecute(object sender.(+.". 6 M 6-< 5 ~/Posts.RewritePath(rew).Current.CurrentHandler.Context.Request. 5 # 4 # % 4 &?' % % % 6 HttpModule= public void Init(HttpApplication context) { // it is necessary to context.% 4 5 5 5 9 4 4 4 M 5 % 4 6 5 4 % 5 % 5 5 4M # 6' M M # 5 5 5 6 % M. } } catch (Exception ex) { throw (new Exception("Incorrect rule. } } .&?'? % 4 6-< 6 1 / // new path to rewrite to string rew = cfg. // save original path to HttpContext for further use HttpContext.Items. HttpContext.BeginRequest += new EventHandler(RewriteModule_BeginRequest).PreInit += new EventHandler(Page_PreInit). ! '( & (& & ! [N @ 6 (N 6 5 5 = ! '( & (&& J 4% 45 % ) "' 5 4 # # &?' 5 4% ( (.-. } + % # 4 . pg. } return.aspx # 4 % 4 5 6-< 6 ( .RewriteBase + path. // rewrite HttpContext.BeginRequest += new EventHandler( RewriteModule_BeginRequest). context. if ((app. } } } = public void Init(HttpApplication context) { context.Context.( % ! ( 6 .RawUrl).Add( "OriginalUrl". ex)).(.Current. } } return. EventArgs e) { HttpApplication app = (HttpApplication)sender.

void Page_PreInit(object sender.web 5 = * % 5 4 % 4 % 5 4 % 5 5 % web.RewriteModule.QueryString. web. The second rewriting is necessary to make ASP.Current.Items["RewriteContextInfo"] = if (path.Contains("OriginalUrl")) { string path = (string)HttpContext. RewriteModule"/> </httpModules> .Items.config 4 6 HttpModule web.Request.NET page and adds a handler for the PreInit event of the page lifecycle. HttpContext.RewritePath(path). } } con. % 5 RewriteModule = % $% * ( ' $ RewriteModule HttpModule system.config 4 <httpModules> <add name="RewriteModule" type="RewriteModule.Current.IndexOf("?") == -1) path += "?".Current. EventArgs e) { // restore internal path to original // this is required to handle postbacks if (HttpContext. This is where RewriteContext will be populated with actual parameters and a second URL rewriting will be performed. HttpContext.Current.NET believe it wants to use a virtual path in the action attribute of an HTML form. // save query string parameters to context RewriteContext con = new RewriteContext( HttpContext. path).config % 5 5 6D &amp\ RewriteModule= 5 ( # ) "' `@ 6 % 4 4&6 4 F"' 5 ) "' 5 % 5 =B F 2 DRBMD1 5 5 $ ?RST6 4 % 5 ResolveUrl T4 6 5 R@MCRTC6 9 . % * ( ' $ 4% • • .Items["OriginalUrl"].Current.1 ! " # This method checks if the user requested a normal ASP.

&?'? % •

4

6-<

6

1 .

+
web.config

4 4

( 4 5 =

%

<rule source="Directory/(.*)/(.*)/(.*)/(.*).aspx" destination="Directory/Item.aspx? Source=$1&amp;Year=$2&amp;ValidTill=$3&amp;Sales=$4"/> <rule source="Directory/(.*)/(.*)/(.*).aspx" destination="Directory/Items.aspx? Source=$1&amp;Year=$2&amp;ValidTill=$3"/> <rule source="Directory/(.*)/(.*).aspx" destination="Directory/SourceYear.aspx? Source=$1&amp;Year=$2&amp;"/> <rule source="Directory/(.*).aspx" destination="Directory/Source.aspx?Source=$1"/>

, 4 5

% ; 6

RewriteModule %

6 6-<

( 5

5

4

%

(

, , (

33 +

%$ .
7 H % Q % 5 4 6-< , 6 5 , , 4 , , , (

% * ( ' $
% ( 4 86 5 6 5 (

*
6

.
( 74 # ( 6

$
6( 8 6-<

#

5

%

4 % 5

U ?6 , 6 @ 2 R1 R

/

!

"

#

)V V* 0

?6 6 @

&?'? %

4

6-<

6

/ 0

5 5 5 4

Configuration…

5

4

4

E %

5 86

7

)

U ?6 , 6 @

/ ! " # )V V* ?6 6 @ .

&?'? % 4 6-< 6 / 3 .( 5 5 6-< . 4 % 6_ ( # 54 ( 6-< 6-< . . % 3 Insert… 4 Add… 6 . ( 4 . > . 1 . ( 4 4 % =5 5 6-< . % F # 6+ 4 . . exists6 Add ( 6 M4 . <( 6D 5 5 54 Check that file .

/ 2 ! " # + 4 . 4 web.config 4 % 6-< 6 &?' 6 5 % 4 4 # M &?' web.config % .%% ( # % # 5 % 4 4 % ( ( 5 5 5 6.config 4 6 . # M4 # &?' % 6-< 6 6 # 4% ( 5 # &?' # % 5 6 &?' 4 4 4 5 45 5 5 5 6 5 5 4 M # % % 4 4 C RewriteModule web.

: .% 5 # 4 % 5 % 5 4 # % 4 % 4 5 4 % % 45 # 6& 4 ! ! E % E % # # % % 5 H ! E % 5 4 5 4 % + 4 ! % 5 # 6 # # 4 4 % % %5 4 4 6 # % .+1 1 1 A1 . & 3! 3 " 27 September 2007 The GridView in ASP.? %'# 4D ! E % / > 2 1 " - .% % 4 &$ *4 $ ' E % 6-< 6 % % % ( % 6 # # % 4 5 ! E % # ( 5 ( 5 4 5 ' # 4 4 % 4 % 4 ! E % $ ' 45 # 6 5 6. % 1 5 4 ' 5 # 6 6. but when you start using the DataBound events. % 5 5 %' ) ! E % % % 5 % 4 # 6 ' 4 &$ ' * ) % ( 5 6" 5 ' 5 5 6 ! 6 E % # 55 5 '$ $ % 54 % 4 5 ! . 1 6 \ % 1 4 5 6 56 4 4 5 4 # 5 4 # # % 6 4 5 # % 5 6 # % 55 # A 4 6 .NET seems to give the programmer few configurable options at first. ( $ * % ! 5 " . then it starts to become surprisingly versatile.

= # 6 5 '" ( 4 5 ( ! ! E %? % 5 % 95 ! ?% 5 ! ?% ( 4 ! E %? % 95 4 ! E %5 6 4 ! E %? % 95 6 4 % 5& 4 % 5 5 & 6 > ! 5 % E %% ./ 1 $ % &6 $18 + <# ( 4 ! E % * ! ' : % ' = 5 # 4 $$ ' 4 ?% 4 95 4 5 4 ! E ?%% 5 4 6 0 5 " . 5 % 4 5 6 & % H . ? %. ?% 4 .5 + 4 % 5 6 2 ! 5 ! 5 5 4 ? %8 5 5 5 8 67 4 % 5 95 6 4 5 4 95 67 % 5 8 4 74 # 6' # 6-< # ! E %? %<# 6 4 # 5 % 6 4 # ?% ! E %? % . H H 6 & 5 4 . 5 & 5 .

Row.Drawing. if (Convert. < ( D 5 5 5 ! E %6 % % 6 D % 55 4 ! E % % # 5 % 5 6 .Color.Row.Row.DataRow) { //We're only interested in Rows that contain data //get a reference to the data used to databound the row DataRowView drv = (DataRowView)e.Font.Color.Red.Blue.ToInt32(drv["UnitsOnOrder"]) > 0) { //The current out of stock item has already been ordered //Make it blue e. } } } } &6 $18 ( 4 4 5 4 5 %6 .Row. //Make the font bold e.Bold = true.DataItem.ToInt32(drv["UnitsInStock"]) == 0) { //The current product has 0 items in stock e.ForeColor = System. GridViewRowEventArgs e) { if (e.RowType == DataControlRowType.Row.ForeColor = System. //Set the text color red if (Convert. 5 # # # ?% # + 6 5 # % 6 5 % 1 6 # protected void GridView1_RowDataBound(object sender.Drawing.? %'# 4D ! E % / / &6 $18 .

4 4 4 45 = 5 5 % . 5 % 6 6? %6 ./ ! 6 E %4 4 % % 5 % 5 1 # $ % 4 . ( %! % E %% 5 5 % 5 6D 5 5 5 4 4 =) 6) 4 % 5 6 6 % # % &6 $18 / ( 4 5 # ( 7 5 5 6 6. 95 6 5 ! E % . # % 5 # 4 4 ? %6 4 5 1 Q % 5 % 4 % 4 ! E %% 6. 5 ' 56 4 6 % # % 6H 5 % 4 5 1 Q % 5 5 4 4 5 4 5 4 5 4 5 5 5 6 / % 6 % & D % % . 4 5 1 % % % # 5 5 % % # 6+ 4 .

5 / . if (previousCat == drv["CategoryName"].DataRow) { //We're only interested in Rows that contain data //get a reference to the data used to databound the row DataRowView drv = ((DataRowView)e.Row. firstRow = e.Cells[0]. string previousCat = "".ToString()) { //If it's the same category as the previous one //Increment the rowspan if (GridView1.DataItem).Cells[0].RowIndex. GridViewRowEventArgs e) { if (e. int firstRow = -1.RowSpan == 0) GridView1.Top.RowSpan = 2.Cells. //Maintain the category in memory previousCat = drv["CategoryName"].RowSpan += 1.Rows[firstRow].RemoveAt(0).Row.Row.RowType == DataControlRowType. protected void GridView1_RowDataBound(object sender.Row. } } } &6 $18 W ( 4 5 . else GridView1. //Remove the cell e.Row.Rows[firstRow]. } else //It's a new category { //Set the vertical alignment to top e.? %'# % 0 4D % ! E % 6 6 .Cells[0].ToString().VerticalAlign = VerticalAlign.Rows[firstRow].

//Set the text color red if (Convert.Row.Color. if (Convert.DataItem.Red.ToInt32(drv["UnitsInStock"]) == 0) { //The current product has 0 items in stock e.$ % &6 $18 ' ( V 5 # 5 %5 %5 5 5 D ( 0 % % 5 5 # # 4 # = 5 # ( 0 1 6 4 5 % 5 5 5\ % 5 6 6 % % 5 00 4 % 5 55 % 6 # 4 # # % # 5 % 4 % # 5 5 # 5 5 4% # % 4 %6 # 0 1 4 6) % # 5 % 6 6 # % protected void GridView1_RowDataBound(object sender. //Make the font bold e.Bold = true.DataRow) { //We're only interested in Rows that contain data //get a reference to the data used to databound the row DataRowView drv = (DataRowView)e.ForeColor = System.RowType == DataControlRowType.Row.Font.Row.Row.ToInt32(drv["UnitsOnOrder"]) > 0) . GridViewRowEventArgs e) { if (e.Drawing.

img.Controls.Width = Unit.Row.Blue. img. } } } &6 $18 ' ( ' 4 4 5 5 6 . e.Color.Add(img).Pixel(11).gif".AbsMiddle.ImageUrl = "arrow_down.Row.Row.Add(new LiteralControl("&nbsp.Cells[0]. img.Text)). //Add the text as a control e.ImageAlign = ImageAlign. img.Controls.Cells[0].ForeColor = System.Cells[0].AlternateText = "Discontinued Product".Drawing.Height = Unit." + e.Pixel(10).Row. } } if ((bool)drv["Discontinued"]) { //Discontinued product //Add the image Image img = new Image().? %'# 4D ! E % 0 { //The current out of stock item has already been ordered //Make it blue e. img.

$ % &6 $18 ' ( )< 5 4 % 5 % 5 5 5 5 % 4 5 4 5 5 ( ( 5 5 6 5 % 5 4 5 ! E % 4 6. 6 . % 5 % # 4 5 4 4 ! E % # % 4 5 4 4 5 4 # % 4 5 ( 5 4 ! E % <# 5 % ) J 5 5 % # 5 # 4 5 5 6 # ( # 5 5 4 5 4 % % # 5 65 4. 4 5 % 5 % 5 5 ( 5 6 # 5 5 6 5 ! % % % ( 5 E % # ( 6 + 4 4 4 5 ! E % 4 6-< 6.

# % 6 5 5 # 4 • • • ' M 5 4 $ # . 31 $F 7 5 5 6 * % 5 4 5 95 $F 6 "5 $# 5 # 45 6 5 4 F"'8 4 4 9( 55 4 5 4 U # 4 4 4 5 ! 5 6 5 M % % 5 # 5 4 $F 5 % 4M 6-< $ F <( # # $F4 5 6-< % $# 5 $F 5 6 5 % 5 % ( % 5 $ 6 D M % $ 4 % . 01 May 2007 " 3 "3 / .< 5 % 6-< $ F <( 3 / + A1 . 5 $# 5 % .. 6-< # % 5 $F $ 4 % % 5 =@ 9(6 6 / @ # N 6-< $F< E 6-< > % 6-< $ F 4 5 N 5 ) . 5 % $F 5 = $ F <( 3 % +4 5 6-< $ F <( 5 6H 5 % % 4 # <( 6 5 6 6-< $F 5 2 4 . 4 M 6 % 65 1 % M 4 2 2 6 5 $ F <( 5 E 5 . % 8 5 4 4 6 D M 45 5 # 6-< $F 4 5 M % 65 4 4 % 6-< $F< 4 % 65 4 6-< $ F <( 6 7 . 4 % 6 + 5 6-< % 65 4 4 % ? 56 ( % 2 5 $# 5 4 ) 4 5 ( 5 $F 4 % 5 45 5 % 5 # 6-< 6 $F . 5 % . 4 $F . 6-< 5 5 . 5 % 1 3 5 6 $ 5 6 % 6-< 2 5 5 5 5 .

4 4 5 . 4 4 3 # N 6 $ 5 5 # # % MU ! 6-< $ F 4 % 4 4 6 2 $ 6D 5 5 2 5 5 5 % (= $F +4 $ 5 6-< $ F 5 5 2 4 5 5 5 5 5 4 E 6-< ( .2 ' % ' +4 $F % F"' 7 . 5 5 5 5 5 5 4 5 5 5 . $F5 <asp:ScriptManager ID="ScriptManager1" runat="server" /> H 5 6 5 2 $ 5 6 ) %# # $F 5 5 5 4 6 % % Q %% 5 % 5 6 5 $ 5 $ 5 % 5 5 5 5 # 5 5 $ 5 % % 5 % # 5 5 # 4 6-< % 4 $F5 $F 5 # 6 ' 0 = <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:GridView ID="GridView1" runat="server" CellPadding="4" DataSourceID="SqlDataSource1" ForeColor="#333333" GridLines="None" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="CustomerID"> <Columns> <asp:BoundField DataField="CompanyName" HeaderText="CompanyName" SortExpression="CompanyName" /> <asp:BoundField DataField="ContactName" HeaderText="ContactName" SortExpression="ContactName" /> <asp:BoundField DataField="ContactTitle" HeaderText="ContactTitle" SortExpression="ContactTitle" /> </Columns> </asp:GridView> </ContentTemplate> </asp:UpdatePanel> ' 06 & & 5 6 . 4 4 % % # 45 N 5 : $ # % 4 % % % @ 5 5 95 6-< $ F <( 5 $ . 4 6-< " % 6 4 . 8 5 4 6 $F 4 % 5 $# 5 5 ( 6 $ 5 4 6 % 5 95 6 . $# 5 5 $F 5 % 4 # ? 4 4 5 $ F <( $F 5 % 5 #5 5 6 ?. % 5 5 6 % 4 5 4 5 $F 5 45 5 6 .

GridView contents omitted for brevity -</asp:GridView> </ContentTemplate> </asp:UpdatePanel> )& 6 D M 5 5 5 4 6 & $ $ $ & $ 5 5 % 45 % 5 7 6 4 $ 4 M 5 5 5 2 % $ 5 5 4 4 5 % 6 4 $ 4 % 5 5 ( (6 6 55 0 5 6 * 65 6 % . 5 5 6 % 55 4 5 # # 4 4 5 M % % 5 .Artist:&nbsp. # 5 $F # ( % 6 % 5 4 5 4% #5 $ 5 5 # 4 6 $ 5 . 4 M% 5 # # 568 5 4( 2 5 5 5 4 6 4 % 5 6.< 5 % 5 6-< 5 5 5 $ F <( Q $F 4 6 5 5 4 4 5 > $ 5 ! % $% $ $ 5 5 . <asp:TextBox ID="txtArtist" runat="server" /> <asp:Button ID="btnSubmit" runat="server" Text="Get Albums" /> <asp:UpdateProgress ID="upProgress" runat="server" DynamicLayout="false"> <ProgressTemplate> <img src="Images/loading_animation_liferay. # 5 % 5 4 % 6 % <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> &nbsp. 5 5 4 5 0 4 0 % 5 % # 4 5 ' . . 5 % 5 M 4> 45 4 % $ #5 . 6-< # ' % % 6' .gif" /> </ProgressTemplate> </asp:UpdateProgress> <asp:GridView ID="GridView1" runat="server"> -. 5 5 6 ) %# $ 6.

1 & '& 6 & * 65 2 5 5 #5 6 # # 4 5 5 7 " # $ $ 5 $F 5 $ $F4 5 4 # %6 5 # # 5 % % % ( 4 $ % ( % Q 4 4 6 . # 5 5 % 5 6 % # Q 6 .

DataItemIndex %>' /> <asp:UpdatePanel ID="upChild" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:GridView ID="gvOrders" AllowPaging="true" PageSize="5" Visible="false" runat="server" AllowSorting="True" AutoGenerateColumns="False"> <Columns> -. ) 6 4 M6 % 5 % 4 5 % # 6 % 5 # % 45 # % $ ' 36 .Bound fields omitted -</Columns> </asp:GridView> </ContentTemplate> </asp:UpdatePanel> <asp:SqlDataSource ID="sdsOrders" runat="server" .< 5 % 6-< $ F <( / & 55 Q $ $ . % Q 5 4 5 6 4 5 5 Q 6 N %H E % % 5 N $F % % <asp:UpdatePanel ID="up" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:GridView ID="GridView1" runat="server" CellPadding="4" DataSourceID="SqlDataSource1" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="CustomerID" OnRowCommand="GridView1_RowCommand"> <Columns> -.Bound fields omitted for brevity -<asp:TemplateField ItemStyle-Width="300px"> <ItemTemplate> <asp:LinkButton ID="lbOrders" runat="server" Text="View Orders" CommandName="ViewOrders" CommandArgument='<%# Container.

% $ %$ % 5 5 5 $ 4 5 5 M 4 ' 4 4 # 4 5 55 2 4 5 6 % 6 % % $ 6 .ConnectionString="<%$ ConnectionStrings:ConnStr %>" > <SelectParameters> <asp:Parameter Name="CustomerID" DefaultValue="NULL" /> </SelectParameters> </asp:SqlDataSource> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> </ContentTemplate> </asp:UpdatePanel> * 6 $ N % N 6 5 $ $ 5 % 4 # 5 4 $ 5 5 * 4 5 5 5 5 5 $ 5 5 5 ( # % 4 $ 5 4 5 4 $ . $ 4 % # N % 5 5 ( $ $ 5 5 6 4 5 6 # % M 5 5 6H 6 M 4 % % 4N ' 4 # 6 6 .Column definitions omitted for brevity -</Columns> </asp:GridView> </ContentTemplate> . 4. # 7 $ 5 5 2 % M 6 24 5 45 5 2 6 # 55 2 5 6 4 5 6 # 5 2 $ 6 2 4 4 $ # # M5 4 6 5 M # # $ 4 6 6 24 5 M5 + 24 $F M5 5 5 5 5 4 2= # 4 5 $ 4 4 2 5 4 4% 5 $ % 6 % 24 5 5 5 4 5 4 5 N ( N 4 2= <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="CustomerID" DataSourceID="sdsCustomers"> <Columns> -. 5 6 # 5 5 4 % 5 # 3 5 Q 5 .5 8 5 4 M5 .

[TerritoryDescription] FROM [Territories] ORDER BY [TerritoryDescription]"> </asp:SqlDataSource> </div> <div style="float: right." /><br /> </ItemTemplate> </asp:DataList> </ContentTemplate> </asp:UpdatePanel> <asp:SqlDataSource ID="sdsTerritories" runat="server" ConnectionString="<%$ ConnectionStrings:ConnStr %>" SelectCommand="SELECT TOP 20 [TerritoryID]."> Territories:<br /> <asp:UpdatePanel ID="upTerritories" runat="server" ChildrenAsTriggers="false" UpdateMode="conditional"> <ContentTemplate> <asp:DataList ID="dlTerritories" runat="server" CellPadding="4" DataKeyField="TerritoryID" DataSourceID="sdsTerritories" ForeColor="#333333" OnItemCommand="DataList1_ItemCommand"> <ItemTemplate> <asp:LinkButton runat="server" ID="lbTerritories" Text='<%# Eval("TerritoryDescription") %>' CommandName="TerritoryClick" CommandArgument='<%#Eval("TerritoryID") %>' Style="text-decoration: none. width: 200px"> Employees:<br /> <asp:UpdatePanel ID="upEmployees" runat="Server"> <ContentTemplate> <asp:DataList ID="dlEmployees" runat="server" CellPadding="4" DataSourceID="sdsEmployees" ForeColor="#333333"> <ItemTemplate> <asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>'></asp:Label><br /><br /> </ItemTemplate> </asp:DataList> </ContentTemplate> </asp:UpdatePanel> <asp:SqlDataSource ID="sdsEmployees" runat="server" ConnectionString="<%$ ConnectionStrings:ConnStr %>" SelectCommand="SELECT Employees. <Triggers> <asp:AsyncPostBackTrigger ControlID="DropDownList1" EventName="SelectedIndexChanged" /> </Triggers> </asp:UpdatePanel> + 6 $ .< 5 % 6-< $ F <( . 5 # $ N % ' ' $ 5 % 4 6 4 4 5 4 M 4 4 5 4 6 # 5 5 5 5 # 5 M5 7 4 4 $ 4 5 0 # % $ 4 % ' $ 5 4 5 2% 5 4 5 4 4 # 4 M M # N 6 # % # 55 > + % ( 4 5 5 % 6 ' + $ 4 5 % 4 386 <div style="width: 400px"> <div style="float: left.EmployeeID = EmployeeTerritories. width: 200px.EmployeeID WHERE .FirstName + ' ' + Employees.LastName AS Name FROM Employees INNER JOIN EmployeeTerritories ON Employees.

.TerritoryID = @TerritoryID)"> <SelectParameters> <asp:Parameter Name="TerritoryID" /> </SelectParameters> </asp:SqlDataSource> </div> </div> . (EmployeeTerritories. 5 # . 4 %% # 5 # $ 6 M # 5 . ( . 6H % 4 $F 5 6 5 $# 5 5 % 6 2 5 % $ % 4 5 13 $ % % 5 $# 5 6 5 .& 6 4 4 % % 6 5 5 36 6 5 5 M 4 $ 5 5 & * 6 5 # 4 4 $ 6 $ $ % 4 6 5 4 ! $ % $ 5 % % % % 5 4 6-< 55 5 .

PageRequestManager. 0 $F % # 5 $ 4 4 5 . 4 & 5 5 55 5 ( 5 6 5 6 4 % 6 . 6 1 3 # 13 ( # 95 5 55 # 6' 5 1 4 4 % # % 5 4 5 # 5 Sys. function EndRequest(sender. 5 6 4 4 5 # % # .WebForms. 1 3 # % 5 13 4 6 5 <# ( 13 "1 3 # 8 "1 3 ? % # 5 . eventArgs) .getInstance().5 6-< . 5 6 5 5 4 5 6 % 5 % # * 6 13 ? 5 2 ?@ 5 var prm = Sys. # 5 5 .getInstance().WebForms. . H 5 4 4 .M % # 5 ( 6-< $ F <( 5 . 5 2 5 5 5 5 $ % 6 5 # 4 M $# 5 4 4 5 . % 5 ? .PageRequestManager. 45 5 6 + 6 13 5 5 4 5 . 5 5 6 5 # 5 5 5 6 5 # 5 .< 5 1 3 % # % 6 . # 5 4 45 4 ? 1 3 ? 5 55 % (= 1 3 5 % 5 4 55 6 5 6 $ 6 .add_endRequest(EndRequest).

function Init(sender) { prm = Sys. } else { GetMap($get("hidField"). alert(errorMessage). 5 5 5 % % 6 H 5 .add_initializeRequest(InitRequest). 5 % 5 6.abortPostBack()..get_error(). 5 4 % 6 . .Application.message. .id == 'btnRefresh') { //Could abort current request by using: prm.get_isInAsyncPostBack()) { prm.get_error() != undefined && eventArgs.WebForms.add_init(Init). { if (eventArgs.args) { if (prm. 5 . eventArgs. 4 E 5 1 3 5 < # 6 55 55 1 3 ?@ # 4 # % # 5 5 5 4 % 5 5 55 4 5 55 4 # 6 5 5 % 4 5 ( .httpStatusCode == '500') { var errorMessage = eventArgs.PageRequestManager. } } } function InitRequest(sender.value). var prm = null. } } 6 1 3 4 4 55 ) 5 4 % 5 1 3 .get_error().get_postBackElement().getInstance().set_errorHandled(true). 45 5 & % % 5 ?@ 5 6 ' % 5 5 $ ?4 / 5 Sys. //Ensure EnablePartialRendering isn't false which will prevent //accessing an instance of the PageRequestManager if (prm) { if (!prm. > 4 6. 55 4 5 # 1 3 5 5 4 # 4 5 4 6 8 6-< 4 .get_isInAsyncPostBack() & args.

# 4 4 + 4 6-< 4 5 5 . Please " + "wait before refreshing again. 6 5 5 5 5 5 6 M "1 3 5 4 # 5 . 6 # 5 4 + 28 5 # # . 5 5 4 .set_cancel(true). % % ( 45 4 # 6 # 6 5 " $ 5 5 4 % # % 4 4 6D M # %$ 5 5 # % 4 % $ 5 5 % % $ M5 % 13 5 5 5 . 4 % % .< 5 % 6-< $ F <( . 6 4 5 # % 4 4 4 % 5 $F 5 ( 5 . 4 : 2 6 2 5 % 5 / % 5 55 # 13 ."). % 6 5 6 $ 5 5 1 3 4 % . 3 //Cancel most recent request so that previous request will complete //and display args. 4 5 "1 3 1 3 28 8 86 5 55 5 5 10 5 # 5 5 5 6 H 5 M 6 6 5 2 5 5 55 7 13 5 M 6 1 3 6 1 3 2 2 # 2 . 5 # # 6 4 4 4 M # 5 5 $F % 4 5 M # 5 . # 5 ' M 6 . } } . M 6 5 4 ?. . alert("A request is currently being processed.

% 6 5 . 6 5 5 3+ 3 .. 4 % 4 # % 5 4 #5 6 6 5 5 5 #5 5 4 5 5 #5 . 6 # . (-/ 9( 5 H 55 4 5 % # 95 U % 6 7 4 5 . 8 ) %# 4 5 % 5 5 5 ) %# H # 4 ) 5 . ( ( 2 5 6 5 5 5 5 % 5 & 5 5 4 % 4 # 5 ! 6 5 + 5 5 $# 5 5 5 # 45 #5 5 6 4 4 5 . $ . 4 06 95 5 1 3 95 % % 5 5 # % 5 5 6 . =@ 9( @ % 65 @ 5 5 . 7% % % 9( =@ %%%6 @ 5 % 5 * 4 9( #5 4 % % $# 5 6 U ! 1 3 95 # 6 # 4 5 # 5 # 5 ( 6 6 4 % % #5 M #5 5 . 2 & + --3 & + 1 # $ H 5 % # .% 5 H / 6 61 4 . # 4 5 % @ % 6 % 5 5 M # 5 6 9( .4 # . M 4 % 5 5 5 # 5 % % 6 5 4% !< @ H 5 4 5 5 # A % % M 5 . 4 5 6 H# 4! 5 44 5 % 5 6 D % # # 4% ' 64 5 29 December 2006 % @ 4 5 * 5 5 % 4 4 . D 6 5 M % 5 5 5 ) ! #5 5 M %5 5 *4 5 5 ! % % # 4 5 # # 5 #5 5 D ' 64 5 % ( 6 4 $ 4 4 )$ $ . % 4 5 4 D 5 45 !1 ( # 44 3 " 5 4 % . <> 55 5 5 " * 4 13 6 # 5 % 84 U 6 5 #F5 % % 06 95 H % 5 ! 1 3 4 6 95 6 U ! # #5 .

} function createRequestObject() { if (window.open("GET". url. > <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.js"></script> </head> <body> Currency To Convert From: <select id="FromBox"> <option value="USD" selected="true">USD . Dollar</option> <option value="GBP">GBP .British Pound</option> <option value="EUR">EUR .#5 5 H 5 % 5 5 #5 6 6 # #5 6H % $F U 5 4 6 4 4 ! 1 3 ) "' %= 95 5 . xmlhttprequest. < 4 5 4H 6 5 $# 5 5 # 4 % 4 5 E # 5 &?' = function initiateConversion() { xmlhttprequest = createRequestObject().onreadystatechange = getData.British Pound</option> <option value="EUR">EUR . true).readyState == 4) &&( xmlhttprequest. var url = "http://www.S.aspx.value + "&ToCurrency=" + document.XMLHTTP").getElementById("FromBox").net/CurrencyConvertor.value .U.S.Japanese Yen</option> </select> Currency To Convert To: <select id="ToBox"> <option value="USD">USD .ActiveXObject) { return xmlhttprequest = new ActiveXObject("Microsoft.w3.U. } } function getData() { if ((xmlhttprequest. Dollar</option> <option value="GBP" selected="true">GBP . 9(696 . } else if (window.send(null).asmx/ ConversionRate?FromCurrency=?FromCurrency=" + document.XMLHttpRequest) { return xmlhttprequest = new XMLHttpRequest().Euro</option> <option value="JPY">JPY .status == 200)) { .Japanese Yen</option> </select> <br /><br /> <input id="button1" type="button" value="Click to convert currency" onclick="initiateConversion()" /> <br /><br /> <table id="table1"> </table> </body> </html> H M U 5 ! 5 4 1 3 5 95 . xmlhttprequest.getElementById("ToBox").webservicex.org/1999/xhtml" > <head> <title>Ajax Currency Convertor</title> <script type="text/javascript" src="Ajax.Euro</option> <option value="JPY">JPY . xmlhttprequest.cs" Inherits="_Default" %> <html xmlns="http://www.

length). <( # N 55 % M 6D . table.ActiveXObject) { XMLText = myXml.length).childNodes[0]. } } & 5 % # 4 5 2 1 3 < 92 % # 4 % N N5 56D 5 &?'6 D % 4 5 # F"' ( 5 7# % 5 5 U ! 13 95 &?' % #5 ( 4 N N5 5 4 4 U ! 1 3 95 2 # 4 5 U ! 1 3 95 6 4 5 H"8 95 6 H45 5 % M% 45 4 4 6 5 5 M 3 $ .nodeValue. var XMLText = null.getElementById("FromBox").getElementById("table1").firstChild.value)). tablecell. var tablecell = row. 1 var myXml = xmlhttprequest. 2 5 6 6D M 6) %# % 5 .firstChild. < 6 4 4 4 5 # 4 4( 4( 5 M # # 4 % 5 % 4 6D M # 4 N 55 45 5 5 5 % 5 N % 5 6 .cells. if (window.value + " to " + document. var row = table.appendChild(document.insertRow(table.createTextNode(XMLText)). tablecell.rows.insertCell(row.insertCell(row. # < # 5 % 5 4 4 # 5 5 % 55 R6 < R N R 2 5 #5 6.length). var tablecell = row. } var table = document. "2"). $$ $ $ ' * 5 4 5 D 5 5 5 4 ( 8 ) %# 5 4 % . } else { XMLText = myXml.responseXML. var xmlobject = null.getElementById("ToBox").cells.createTextNode (document. . 4 5 .appendChild(document.setAttribute("border".nodeValue..childNodes[1].

4 7 5 5 % 4 U 5 4 # 8% 5 5 # 4U $# 5 5 % #5 4 # 5 ! 1 3 6 ( 6 : B +$ ! .0" ?> <cross-domain-policy> <allow-access-from domain="*" /> <allow-access-from domain="*. U # ) ! 6 $# 5 var xmlhttprequest = null. 95 5 5 F"' 5 % % # 4 . 4 # N # % 2 # 5 ( # 5 4 5 # % 54 .macromedia. % % 5 % M % % 5 5 # 4 6 7# 6 / $ $ ' $. ) 6-< 6-< ( # # # . 5 4 5 % 5 4 54 # #5 # 4 % 5 5 5 %= 6 5 55 5 5 # 5 % 4 #5 42 % 5 5 5 5 5 5 % # 5 5 6N % 5 55 #5 4 6 F"' 4 6D <?xml version="1.value # 5 5 4 5 # 4 .adobe. / 56 4 5 $. function initiateConversion() { xmlhttprequest = createRequestObject().getElementById("FromBox"). 5 5 5 .com" secure="false" /> <allow-access-from domain="*. 5 4 6-< $# 5 5 % # $# 5 5 . ( 45 6-< = # % 5 6 H45 F"'6 $# 5 5 5 6 #5 6-< # # 6-< # 5 6.com" secure="false" /> </cross-domain-policy> H4 5 % 8 % 5 4 5 4 .aspx?FromBox=" + document. var url = "callservice. 5 4 1 3 4 . .#5 5 5 5 5 4 4 5 # # $F 5 6 .

$postvars). % 5 6-< 5 5 4 6 U &?' 5 F"' 5 5 XmlDocument wsResponse = new XmlDocument(). } curl_setopt ($session. // Open the Curl session $session = curl_init($url).getElementById("FromBox"). curl_setopt($session. if ($_POST['ws_path']) { $postvars = ''.webservicex.. CURLOPT_POST. xmlhttprequest.net/CurrencyConvertor.getElementById("ToBox").Write(XMLDocument).ContentType = "text/xml". next($_POST).open("GET". url.$element. CURLOPT_RETURNTRANSFER. 4 5 # #= 55 5 4 ) G 5&?'75 %= # 5 M &?'8 ( 6-< G ) 6 $# 5 5 var path = 'http:// "http://www. .QueryString["ToBox"]. true).onreadystatechange = getData. true). .InnerXml. wsResponse. while ($element = current($_POST)) { $postvars . string url = "http://www. url.Load(url).'&'. .net/').getElementById("ToBox").value . $path = ($_POST['ws_path']) ? $_POST['ws_path'] : $_GET['ws_path'].QueryString["FromBox"]. xmlhttp. curl_setopt ($session.webservicex.net/CurrencyConvertor. . false).= key($_POST).open('GET'. } // Return the call not the headers curl_setopt($session. Response. true). xmlhttprequest. xmlhttprequest.$path.php?ws_path=' + encodeURIComponent(path).'='.value + "&ToCurrency=" + document. D ) 5 &?' .asmx/ ConversionRate?FromCurrency=' + document. Response. string XMLDocument = wsResponse. 'http://www.value. $# 5 5 5 4 5 5 4 # 5 6 4 . true).$.ToString() + "&ToCurrency=" + Request. CURLOPT_POSTFIELDS. var url = 'http://localhost/curl_proxy. = 5 5&?' 4 4 4 ) 5 $# 5 # 5 4 5 55 % #5 6 84 %M < 4 define ('HOSTNAME'.webservicex.asmx/ ConversionRate?FromCurrency=" + Request. $url = HOSTNAME. + "&ToBox=" + document.ToString(). } & .send(null). CURLOPT_HEADER.

org/1999/xhtml" > <head> <title>Dynamic Script Tag Example</title> <script type="text/javascript" src="ajax2. 4 # % 4 % 5 4 ( 5 #5 4 4 $ H.yahoo.7$# 5 H 95 4 8 6D 5 5 5 5 % #5 6 % % 4 % #5 5 (5 4 $# 5 6 $ H. header("Content-Type: text/xml").aspx.js"></script> </head> <body> Find Images: <input id="userinput" type="text" /> <input type="button" onclick="dynamicTag().#5 $F . echo $xml.com/ImageSearchService/V1/ imageSearch?appid=YahooDemo&query=Einstein&output=json = http://api.5 \ 5 6 .w3.search. 5 4 5 5 U ! 6-< 5 \ ( F"'6 F"' % 1 3 95 6 .com/ImageSearchService/V1/ imageSearch?appid=YahooDemo&query=Einstein&output= json&callback=ws_results . 4 2 5 $ H5 < N% % # M 4 4 5 ( 95 4 4 5 2 $ H. % % #5 ! 44 $ . .cs" Inherits="_Default" %> <html xmlns="http://www. curl_close($session).5 5 # 4 5 # 5 6 % #5 #5 5 N .% 5 4 5 $# 5 # & 4 6) %# 6 4 N N 6 ( 4< 4 2 7 4 % # D 26 8 4% &?' 7% % = #5 5 85 http://api." value="Search" /> <br /> <div id="PlaceImages"></div> </body> </html> .yahoo. // call the data $xml = curl_exec($session).M 5 4% 45 % # 6 H ) "'@ 5 4 % F % 9( 4 5 4 %= 0 5 4 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Dynamic.search.

for (i=0.item(0). div. 98 % de la population ne serait pas capable de résoudre l'énigme suivante..createElement("image").jpg" RefererUrl: "http://www. } % 54 4 5 5 5 4 5 4(5 4 %= #5 $ H- # 5 % 5 ( (6 4 5 5 5 6 function getImages(JSONData) { if (JSONData != null) { var div = document.fr/Colleges/93/jmoulinmontreuil/ mathematiques/enigmes/anciennes_enigmes2/enigme24. 100). image.getElementsByTagName("head").jpg" ClickUrl: "http://www.. var head = document.ac-creteil. image. head. i++) { var image = document.jpg" Summary: "Selon Einstein.} 5 6 4 5 5 4 &?' 0 5 4 %= 2 .ResultSet. i<10.getElementById("userinput").Result[i].setAttribute("type". script. JSONData.0 5 5 % B2 C % % 5 6 2 #5 $# 5 5 M &?' 5 5 5 6 4 5 5 % 2 4 & % 5 % #5 5 5 % 5 5 function dynamicTag() { var userinput = document.appendChild(image). Ferez-vous partie des 2 % les plus intelligents ? A vous de" Url: "http://www.appendChild(script). "text/javascript").ac-creteil. image.Url). } } } <7 & ? " & & 95 6 ! ( 1 95 4 4 4 % % 5 $ # 2$ 6 4 4 %= 95 4 5 10 $ % 4 Title: "einstein.setAttribute("width".com/ImageSearchService/V1/ imageSearch?appid=YahooDemo&query=" + userinput + "&output=json&callback=getImages".value.fr/Colleges/93/jmoulinmontreuil/ mathematiques/enigmes/anciennes_enigmes2/gifs/einstein.setAttribute("src". var request = "http://api.setAttribute("height". request).getElementById("PlaceImages").yahoo.createElement("script").search.htm" FileSize: "25828" FileFormat: "jpeg" Height: "291" Width: "281" Thumbnail: {. 100).fr/Colleges/93/jmoulinmontreuil/ mathematiques/enigmes/anciennes_enigmes2/gifs/einstein. var script = document.setAttribute("src".ac-creteil. script.

4 6 95 # 1 3 1 3 % ) 1 3 ! 1 3 # # 5 5 5 45 # #5 % 5 % 5 6 6 4 % ) 6N #5 "5 N F % 4 ( ) 2 ! U 6 4 4 % .M 4 5 5 1 3 95 4 # 7 5 5 # ) % 6 2 4 5 4 6 # 5 4 4 ( H 5 2 . : $ '$4 . 95 5 # 4 6 4 % & 7 ) 5 + . M 6 5 4 % U 2 U ! ! 5 = 65 7 @ > 3@ @ 5 ( 2 U 95 % 5 4 % 5 95 % 4 6 ? 5 ( 5 5( . 5 5 5 \ 45 . # ! # % 5 1 3 4 # (6 !< 9 # 8 U 8 ! 5 6 .#5 $F 0 0 4 . 6 % # # 44 % 5 : . < . (-/ 7 95 & % $ U 6. M% 4 6 5 4 # 5 6 4 4 # 4 M 5 5 4 % 4 5 4 = 5 4 . <> # (5 . % $ H5 . 5 <1 # 4% % 5 M 4 % 5 #5 5 5 . % ) =@ 5 @ # # # 5 # % 5 4 (-/ . M 6 6" 6 M # 5 4 6 $ H5 6 5 % . 4 5 # # # 5 .

@ $ % 45 % 5 5 # 95 % % # 6.0 & 1 ' ) 7 <71 3 % 5 =@ 9 6 @ H-? .4 5 $ H. # % 4 5 5 (5 # 5 + 4 45 % 6-< 5 45 5 # 4 ( 6 4% #5 # # 5 % 4 # 5 % 9( 6) %# % # 4 # 5 % #5 5 \ 9(6 5 . (5 % % $ H% 6 5 5 4 M 6 # 6 6 #5 % 4 # % % 4 % 5 . % # # % 5 % F"' 4 F"' 6 (5 5 4 6 % #5 4 % 4 % 95 <71 3 7 8 % 5 5 84 < 4 # 7#8 # 95 * $ H.5 # 6 (5 # 4 $ H4 5 45 * % 6. # # $ H% #5 4 5 4 6 .

# % 48 ( 4> = % 4 4 4'# 4 #5 % % % #5 5 5 5 % #5 % #5 5 4 '# 6' 5 5 5 #5 5 5 4 5 5" 5 4 = 5 5 5 4 6 % 78 4 4E 5 < % 4 E 4 ( 5 5 : # 5 4 @ : . • 6 % 65 D 5 # 5 4 4 6 7# # 4 . with Daniel's sample application to guide you. 5 4 5 #5 % # 5 5 4 #5 % 4 * 5 6-< 6 % 5 # @ 5 5 % '# 5 6 # 4 5 4 #5 # % % 5 5 ( # % 5 %9 6 % '# 4 # 5 ( 5 6 5 . . H 5 . 6 =5 . 5 % . 6 # % # E H 5 % % < 5 ! 40 . % 78 %.% % 4 4 4 6-< #5 $ F 06 9( "5 4: 5 '# 5 6 .& #5 % 6-< 0 3 . 6GH 5 . 3 &" 3+ "3 / 14 December 2007 Daniel Penrod Webservices without tears? Popup maps in your websites? Ajax-based Search-engines? No problems.% 4 95 6. M # # 5 5 5 % 4 % = 5 • • • • • • < 4 • • • • • • 4 ) & 4 & 5 < 4 % '# 6-< ! # % 5 4 $ F 06 5 9( 4 5 4 '# 5 4 5 5 ( 5 % % % 5 5 # 5 5 % .

nc.com OR site:www.fayetteville.6 .7 8 6 7% 8 5 #5 <add key="DictService" value="wn" /> 4 + ( 4 .us) www. 2 0 4 2 = M . .us OR site:www. '# #5 % % # ( 6 5 % I =@ @ 4 5 6 "5 4 65 @ # #5 8 % 65 % 65 E # 4 4 5 > 5 * 6 6 7 6 =@ %%%6% @ $ F <( % 06 7 5 65 =@ 6 @ .ci.cumberland.us/portal/council_meeting_minutes/ 7 % 5 ( 5 =@ %%%6 # 65 @ 5 # 5 @ .0 2 & % % $' ) $ 4 % F 5 6 6-< 6-< % 4 0 6 H 7 036 02 7H 6 8 4.ci. .nc. @ 8 @ 9(@ % 5 @ 5 #@ 8 6-< 6 .faypwc.co.nc.nc.ci.us OR site:police.fayetteville. # 5 = 2 5 .nc.org OR site:www.nc. 006 .fayetteville. 86 5 40 a 5 : 5 6 5 5 4 # 0 a4 % (5 % '# 5 4 .us OR site:www.fcpr.ci. 4 @ # % 5 # 55 6 % 5 .us OR site:flyfay.ccbusinesscouncil. 5 {frsh=100} {popl=100} (site:www.fayetteville.fayetteville.ci. % 4 ! * 4 0 4 % 2 666 =@ @ 5 6 65 @ # r 5 0 2 <add key="SearchLic" value=""/> 4 2 5 .us OR site:jobs. .

0"/> & . #5 6 5 '$ .Search\Logs\"/> 2 0 2 5 % 2 4 4 > 6 5 # 6 4 % 5 4 6 <add key="Latitude" value="47.5 $ F 06 6 4 6+ # 4 . $% 3 H . 4 4 .328567" /> <add key="Radius" value="25.) 4 5 6 5 6 5 # U 5 ! 5 % $ 4 4 45 % 5 5 # 5 5 U 5 6 4 5 5 4 5 4 5 4 % 6 . # <add key="IsSearchSession" value="true"/> . 4 5 "5 4: 8 2 ?@ 4 5 4 5 5 4 4 #5 .603828" /> <add key="Longitude" value="-122. 4 #5 4 4 4 5 ( 4 5 4 5 5 6 # &?' 4 % # 20 7 6 . 5 % 5 (5 5 " '* . # 5 % <add key="IsLogging" value="false"/> <add key="ErrorLogPath" value="C:\Inetpub\wwwroot\Windows. % 5 5 % 5 6. 46 4 6 " 5 46 4 6 # 0 5 % % 5 6 % % 5 # # % 6 + # ! E % 5 .Live. ( 6 ( 5 56 . * > 6 <add key="ResultsSize" value="25"/> " # 4 4 % 4 4 6 5 5 5 .& #5 % 6-< 0 > <add key="SearchSites" value=""/> 4 1 " > 6+ 4 .

Trim() == "")) return null. else LogMessage("A successful search was made with searchQuery: " + searchQuery). # % 4 . SearchProperties). SearchResponse searchResponse. % 4 4 5 # %5 % 5 6 4 % ( 4 6 4 5 % % 4 % 4 0 6"5 4 > 4 . IList<LiveSearchResults> webCollection = new List<LiveSearchResults>(). } catch (Exception e) { ErrorMsg = e. webCollection = CaptureWebResults(searchResponse). } finally { // If there was an error // Logging flag checked in function if (ErrorMsg. searchRequest = SetUpRequest(searchRequest. searchQuery).Length > 0) LogMessage("There was an error with searchQuery: " + searchQuery).Search(searchRequest). } } . # % # % 5 % 5 .ToString(). // Session flag checked in function GenerateSession(searchResponse.Length == 0) || (searchQuery. 5 5 5 4 5 5 . # 6 5 44 .Session["searchResponse"].0 1 45 4 6 # 4 # 0 5 5 4 5 % 4 % 65 4 4 4 > 4 9 ! 4 44 . try { // If the searchQuery is the same // Session flag checked in function if (IsSameSearch(searchQuery)) searchResponse = (SearchResponse)HttpContext. else // Use Live Search to get the Response searchResponse = s. # 6 95 5 % 5 6 $ ( 4 H45 ' 5 6 Figure 1 – The main entry point of the class public IList<LiveSearchResults> Search(string searchQuery) { // Basic checks if ((searchQuery == null) || (searchQuery. 6 % 5 # 4 45 # 4 5 # 4 . # 6 4 % 6 # 95 : : A : 4 . using (MSNSearchService s = new MSNSearchService()) { SearchRequest searchRequest = new SearchRequest().Current. searchQuery.

" D . ?. 4 % 5 % 5 5 6< 4 5 6 " 5 95 5 5 % % ! 5 5 # %5 6 95 6 7 6 5 ? % 5 ? 4 5 5 45 5 5 % 4 % 4 0 4 J 4 4 5 • • • SetUpRequest G ) # % '# %% % % 6. 4 '# 6' 5 6 CaptureWebResults G 4 5 6. 4 95 : 4 6 CheckForNull G 5 5 5 4 6 % # 6 " + % 5 5 5 4 $F 5 6 . 6 5 . 5 06 % 6 . 5 6 01 . . 5 6 0/ . . 6 . 5 4 6 6. 5 5 ( 4 5 = % ' " 56 . # % #5 5 6) % 5 # 6 4 6 HandleSpellingSuggestion G . 5 5 ?. } % #5 5 5 5 # 5 5 ?. 4 5 6 4 # % . 5 $ F 06 4 5 4 6 H 95 5 .& #5 % 6-< 0 / return webCollection. % 5 % % 6 . 6 . . . 4 5 5 6. #5 4 % 0> . 4 '# 5 6.5 0. 5 6 5 0 6 . 5 ? 4 5 5 4 5 6 " 95 5 % 6. 6 ' " 5 5 • 5 # 5 5 #5 5 5 4 8 4 95 95 5 5 ? 4 4 6 4 4 6 5 % 6-< 6 5 % 65 5 5 5 % 4 44 6 . 5 ? .

resultsCollection = (IList<LiveSearchResults>)e.SpellingSuggestion. % 5 4 6 Figure 2 – Getting the instance of the class WindowsLiveSearch thisObject = new WindowsLiveSearch(). SpellingSuggestion. 5 ? # 6 5 6 % 5 #5 6 . ObjectDataSourceEventArgs e) { thisObject = (WindowsLiveSearch)e. } catch (System. # 5 % % 5 5 % % 4 4 5 % 6 Figure 4 – Handling Spelling { if (thisObject.Text = "Did you mean " + "<a href='javascript:void(0).%.Text = "".NullReferenceException) { Instructionlbl.5 J . # % 5 6 5 # 4 5 % .0 1 84! $ % 5 5 4 5 $ 4 % % 4 4 5 6 .Length > 0) Spellinglbl.5 ( 6 # 4 = ObjectDataSource1_Selected try Figure 3 – Getting the return value { IList<LiveSearchResults> resultsCollection = new List<LiveSearchResults>(). 5 4 % ? %6 5 5 6 . } else Spellinglbl.ReturnValue. } . } . protected void ObjectDataSource1_ObjectCreated (object sender.ObjectInstance.Text = thisObject. . ( % : 5 5 .% 6 H 95 5 5 4 5 .". = 5 % 6.SpellingSuggestion + "?</a>". # 5 5 5 H 95 % # 5 5 .Text = "Please enter a search term. resultsTotal = resultsCollection.SpellingSuggestion + "'>" + thisObject.Count.' onclick='submitSpellingSuggestion()' title='" + thisObject.".4 H 95 5 5 5 5 <# 95 5 4 % 6 # '# .SpellingSuggestion.Text = "Your search provided no results. if (resultsTotal == 0) Instructionlbl.

5 ? ! # % ? 6. 4 66 . The instruction label . yada yada yada! </asp:UpdatePanel> J Figure 7 G "5 4J . J % # % ... . + % % $ #5 4 % <( 2 4 ' 5 6 $F + 4 5 "5 <( 5 # 6 4 5 4 6 4 % 5 5 % #5 % #5 # .% Figure 6 – The AJAX 1. PhoneResultsView. .5 5 66 4 0 . # % % .. The Gridview for the phonebook results ... 4 5 4 $F A .0 UpdatePanel tag<asp:UpdatePanel ID="UpdatePanel1" runat="server"> ..DataSource = thisObject. 4 % 6-< 5 0Q 5 .. A 4 4 6 5 5 % ( I A 6. thisObject.. 6 . " % . # 5 6 " %4 5 $F 4 "5 % 4 $F4 . // Dispose of this instance 5 5 . The Gridview for the web results . % .PhoneResults.Dispose(). Figure 5 – Handling the Phone Book Results// Get the Phone Results and bind them PhoneResultsView. 5 9 5 # ( 5 % #5 6 3 5 4 6 5 #5 5 % 3 4( # 5 5 5 4 4 5 4 % 65 4 6 . 5 95 5 . 5 $ F 06 & . The spelling label .. : 4 45 J 6.& #5 H 95 6. # 55 6.DataBind().. % 5 % % 5 4% .

4 * 4 5 4 4 % 6 6 . 5 5 4 4% . 4 % '# '# % H % #5 4 # #5 #5 6-< * # 4% 6 5 # 5 % 6. 5 4 .4 .00 $ < $ % 4 % 5 G 3 4. 4 4 % 4 '# 6' 5 < # # + % 6 % 4 # 6 # 5 % % . 5 % 5 % 4 6 4 4 C # 4 8 % 4 E < 6 5 5 3 # %7 4 E 5 < D $ +5 44 % #5 # 5 4 4 . % % 5 % 5 % A 4 =E # $# 5 < 6 # # 6 . $# 5 4 5 5 4 6H 5 .% . # E 4 # % % 5 5 4 5 # 6 $# 5 5 5 6-< % 4 5 E < 5 % 4 4 E < # + : # % 4 5 6 5 4 ! ! " " .

!31 ! 04 May 2007 . 7 5 ? "5 4 4H # # @ 6 ? 6-< 6 65 5 / # 8 7 5 ? 6-< 45 * 4 % 5 5 4 5 # 6-< 5 % 6 % 4 ? M? 6 4 # 4 6 8 5 % 5 =@ %%% @ 4 )$ * ) 5 % ( ? 4 5 % 5 # % 4 # % # #5 # 6 #& ## & ! 45 3 % ( ( ! / 06 . - . . 6 # 6 ? 6-< % % #5 % 4? 4 5 4 M C' 4 5 ? 4 4 5 C' # % #5 6H 5 % # 4 5 5 5 5 !# 0 4? 4 &?' # # % ( 6 6 4 ! * #& " " 45 " ! '( & (&& # # # % 4 5 ? 6-< 4 5 C' # +6 & 6 5 0G! 4 4 % D 5 • • 5 4 ' (5 5 G G 44 4 5 5 5 5 6 % 4 4 5 4 % 6 4 % 2 53 4 = * .! ? E ? 6-< 000 & / 3 & : ! . 3 & 3 .

00 $ G + ? C' & % 5 7 4 4 5 5 # 4 5 4 5 4 6 # 6 # . 4 5 % 6D 5 ? 4 4 5 6 . 5 % &?' 4 4 6 & 4 4 M# 5 % #5 % 6 5 % 5 # 5 5 % 5 6 % -&'' # 6 .4 4 9 5 4 1 5 4 4 . 6 4 4 % 5 5 # 4 +5 6. 7 7 =@ 5 @ =@ @ 6 6 % 4 % ? 4 % 5 65 @ @ 9 6 @ 6 (8 H6-< 65 @ @ 6( 86 +5 6. 4 & 4 5 C' 55 & # 1 8 & 5 6 5 M % 4 5 % % 1 $ 6. 4 4 6 5 4 5 # 4 % 6 1 6D % =B6 H 5 & 4 . % +5 6 .4 5 1 $ ?& 7 ? .

rdr["Url"]. } } } return feedList.ToString(). rdr["LastPostGuidName"].Enabled = false. LastPostGuidName. 5 4 * 8 % 6 # 6 ' 5 % #5 26 .ToString())).Interval = 30000. } .! H45 ? 5 1 $ E 5 5 ? 6-< % 4 6 4 003 % & $% ( 5 & ? 6-< . 6 .CloseCo nnection).ToString()). Url. feedGathererTimer = new System.Add(new Feed(Convert. feedGathererTimer. feedGathererTimer. 6 % 5 4 # # % #5 % 0 1 4 6 . % 5 public FeedGatheringService() { InitializeComponent(). DateCreated FROM Feed ORDER BY Sequen ce".ToInt32(rdr["Id"]). M % 95 4 5 #5 % E 5 4 ? 4 6-< 2 6 .BlogRoll ConnectionString)) { cn.ToDateTime(rdr["DateCreated"]. feedGathererTimer. using (SqlCommand cmd = new SqlCommand(sql. Convert.Timer(). Title. cn)) { cmd. while (rdr.Text.5 & 0 44 5 4 C' % 5 . rdr["Title"].ToString().CommandType = CommandType. # 44 95 5 #5 M5 4 4 5 . SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.Elapsed += new ElapsedEventHandler(feedGathererTime r_Elapsed). using (SqlConnection cn = new SqlConnection(Settings. //900000.default. # & C 4 # 6 M 5 B& 4 public static List<Feed> GetList() { List<Feed> feedList = new List<Feed>(). } 4 : 8 # & 5 % 5 5 5 4 4 4 6 <5 4 % B& C6 4 % % % 5 H6-< 6 4 5 % & M5 5 6<5 #5 5 6 . string sql = "SELECT ID.Timers.Read()) { feedList.Open().

if (rssFeed.Author = rssItem.002 $ % H 5 4 M? 5 ' B& 4 $ C 6 5 4 6 ( 4 4 1 & 5 % 6<5 5 5 4 4 & 6 . } 5 5 6 B % C6 5 5 4 # # 4 5 6 .Channels[0].Items 4 &?' 4 # 5 % % 5 5 7 86 4 -&''6 ) % # 7 # 4 4 ? 6-< 1 & 6 # % # 4 (5 4 4 4 M public static List<Post> GetNewRssPostsFromUrl(Feed feed) { string mostRecentPostGuidName = null.PubDate.Name.FeedID = feed. .Add(post). This means the rest of pos ts are old. foreach (RssItem rssItem in channel. Post post = new Post(). RssFeed rssFeed = RssFeed.LastPostGuidName == rssItem.Body = rssItem.Link = rssItem.Count == 0) return postList.Channels[0]. 4 ( 5 4 6 7 5 5 4 M# 5 5 5 1 M6 5 8 4 7 ( # 7 % 5 5 55 5 5 6 6 6 5 5 # % #5 5 4 ? 6-< 5 6 5 % 5 4 ? 6-< 4 4 (5 4 # ? 6-< 1 & 5 &?' 4 4 6 . } // Update the Feed's last post setting if (mostRecentPostGuidName != null) feed. post. // Get Channel 0 RssChannel channel = rssFeed.PostDate = rssItem.LastPostGuidName = mostRecentPostGuidName.Title.Guid. post. 5 % rssFeed.Link.Author. postList. post.Id.Name) break. post.ToString(). if (feed. List<Post> postList = new List<Post>().Description. return postList.Read(feed.Url).Items) { // If we already have this post.Channels. post. post. // Grab the first Post's Guid if (mostRecentPostGuidName == null) mostRecentPostGuidName = rssItem.Guid. exit.Title = rssItem.

5 5 4 2 95 6 #5 5 5 9 # 5 % 1 5 4 # #5 .5 % # 4 .% 4 4 # 4 . . // If it has posts.Add().LastPostGuidNameChanged) feed. M 5 5 # # 1 . 6) %# 5 4 4.5 5 5 6 5 5 . } 4 6 % 5 4 5 5 5 4 5 6 5 5 4 5 4 5 4 4 7 5 % ( 4 6 & (5 6 % +$ % 5 % 5 #5 5 . update the feed if (feed. 5 4 6. .% 4 5 6 & 2 2 5 . 4 5 4 2 5 5 % % 2 5 4 5 5 .Count > 0) foreach (Post post in postList) post.Complete().! 4 6 4 ? 4 4 6 E B ( % 5 ? 6-< C 5 # 65 7 55 % 00> 7 %% 6 3 $ % H 5 4 4 4 M 2 B 6 C 4 % 5 > #5 % 2 % " 2 5 5 % 6 6 . 0> 7 . 4 H 5 7 5 5 Add Installer $ % 4 # M 86 % 6 % #5 % 5 86 5 4 . 5 % 5 % & ( 92 6 5 25 4 M 2 % 36 . #5 6 7H45 4 % 4 4 % 5 5 5 4 5 5 68 using (TransactionScope ts = new TransactionScope()) { // if the Feed and its Guid changed.5 6. add them if (postList.Update(). ts.

6 <( 5 4 % #5 = net start FeedGatheringService 5 ( 4 #5 #5 #5 4 6 5 5 " 5 5 " & 4 2 ! % % % % 0 $4 ) % #5 # # % 4 : 8 5 45 4 6-< # 5 5 5 6 " 5 % 5 .% '5 6.001 $ 3G 92 6 5 5 2 54 ) %# 5 . % 5 6.exe $ 5 # 5 6 7. % # % 56 5 : % #5 5 2 27 N ! #5 #5 ? 6 N 4D 6 2 . # % '5 5 92 6 M 2 2 5 . 5 #5 ( # %6 . .5 5 9 5 # 6 26 4 & 4 5 #5 % = 3 H 5 " '* $4 ) #5 5 6-< 5 2 # = 5 5 95 6 7D % 68 <( 5 % 4 % 5 4 % . 6 5 (5 5 5 6 ( 4% . 6 . 4 % @ ( 5 4 5 E 4 6 % % 68 .exe FeedGatheringService.1 4% 95 5 6H E 5 4 & 5 % 4 5 #5 5 InstallUtil. 2 % # 5 5 5 2 4 6 6 5 45 .5 5 5 (5 #5 #5 % 26 5 % % #5 #5 6 $ 5 6I 5 5 .

% # C' 95 5 # # 6 4 7 5 8B C' 56 4 % #5 % 5 5 % >? 6 5 5 % 5 6 4 5 45 H6-< 4? 5 45 4 4 55 .( 5 ? 6-< 5 7 & % 2 86 2 4 00/ 4 2 .! 5 # ? 5 5 5 5 E 5 6. 4 % % 5 % 0 % 5 M #5 4 % # 5 6 : 8 # 5 (5 6 5 "$ %.

5 % 5 @* Mb @ '? 95 % 5 % 4 5 % 5 % 5 5 5 4 % 4 X A 5 5 F "' F "'6 F "' 4 5 86 <# # 95 5 4( % 6 M 4 % F "' '? 95 5 % M5 5 % % # 6 4 5 & % <# $' " % 4 . 45 F "' 4 5 % ( 5 # 5 = F"' 4 86 4 4 ) "' 5 75 4 5 8 % % # 3 % % 4 ) "' 4 4 4 % % ( 4 6-< 3 % 5 % E % F 7 % 6-< 06 + $ . 4 5 5 % 5 5 7 ( 4 . (% % M # # % 4 % 5 4 1 5 # 4 5 6<5 6 5 4 4 5 # % # 4 ( 5 95 % 6 5 6 5 # 4 # 4 F "' % F "' 5 5 6 # 4 4 6 # 4 5 % % % 5 # 5 * 6 4 $84 % 5 5 4 8% % % 06 4 4 % # 6 5 % 4 4 6H 4 % 5 # 5 45 6 .00 $ ' () # * & 3 & ! "3 /. 5 5. 4 # # 5 ) "'= <Button Width="20" Height="10">OK Button</Button> . (5 6. (! " 16 March 2007 +$ % C 4 * . (4 5 5 5 % 6 M L "5 4M 6-< 3 5 5 %M # ) "' 5 6 9 # 5 45 7 # .

.! % F "' 00.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.microsoft.. & ' % 5 ( 5 7 8 % +$ % 5 " 5 % 4 E 5 % 7 8 6 5 5 % Project6 PelotonShack & )5 = % % 5 4 % # = 5 # 4 % %% 5 % <Window x:Class="PelotonShack. </Grid> </Window> .Window1" xmlns="http://schemas.com/winfx/2006/xaml" Title="PelotonShack" Height="300" Width="300"> <Grid> .

Resources 7 $ 5 4 4 4% % 4 %0 5 1 7 5 8% ' % 4 4 () 5 %6 # * E 4 %%M F "' % % .com/winfx/2006/xaml" Title="PelotonShack" Height="600" Width="900"> <Grid> <Grid.Resources> <Style x:Key="TitleText" TargetType="{x:Type TextBlock}" > <Setter Property="FontFamily" Value="Segoe Black" /> <Setter Property="FontSize" Value="24px" /> <Setter Property="Foreground" Value="MidnightBlue" /> </Style> </Grid.0 % 5 % 55 86 Grid % M % Grid.Resources> <TextBlock Style="{DynamicResource TitleText}"> <Span>The Peloton Shack: </Span> <Span FontStyle="Italic"> keeping you in the pack!</Span> </TextBlock> </Grid> </Window> 5 Adding the List Box ( 4 06 6 36 .Window1" xmlns="http://schemas.microsoft. 7 # % 5 4 GradientBrushes 568 4 ! % M% 5 5 4 6 % 3= 3= 5 %M ( 5 6 ( 5 4 % = <TextBlock Style="{DynamicResource TitleText}"> <Span>The Peloton Shack: </Span> <Span FontStyle="Italic"> keeping you in the pack!</Span> </TextBlock> 5 %# TextBlock DynamicResource M TitleTextM? 6 5 5 F "' % ( 0= % % 4 5 5 Example 1 <Window x:Class="PelotonShack.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas. 5 ( 4 5 % ( 6 % 4 55 5 45 ( 5 % % 9 4% % 4% 6 6 %6 7 86 .

ColumnDefinitions> <ColumnDefinition Width="0.99.0.66*"/> </Grid.IsEnabled="True" ContextMenuService.5.5 % 5 % 4 5 # % 4 % # * % 4 # % 5 4 DataContext 4 5 (6 % 6 DataTable % M . <DataTemplate x:Key="ProductDataTemplate"> <StackPanel HorizontalAlignment="Center" Background="{DynamicResource ListBoxGradient}" Height="Auto" Width="150"> <Image Source="{Binding Path=ThumbNailPhotoFilePath}" ContextMenuService.ContextMenu="{Binding Path=Name}" HorizontalAlignment="Center" /> </StackPanel> </DataTemplate> .VerticalScrollBarVisibility="Visible" > </ListBox> </ContentControl> <ContentControl x:Name="DetailsPane" Grid.5"> <Grid.33*"/> <ColumnDefinition Width="0.! 2+ 6 %M % ( F "' .0.5"> <ListBox x:Name="MasterList" Width="Auto" Height="Auto" RenderTransformOrigin="0.5.127.RowDefinitions> <RowDefinition/> </Grid.5" ItemsSource="{Binding Mode=Default}" ItemTemplate="{DynamicResource ProductDataTemplate}" ItemContainerStyle="{DynamicResource ProductTemplateItemStyle}" HorizontalAlignment="Center" SelectedIndex="0" IsSynchronizedWithCurrentItem="True" ScrollViewer.Resources section.118" Width="Auto" Height="Auto" RenderTransformOrigin="0.ColumnDefinitions> <Grid. ( 4 (% %6 # ( = 4 6 # # % 5 ( % 4 %6 0 0 5 % 5 5 # <Grid x:Name="MainGrid" Margin="125.0.5.RowDefinitions> <ContentControl x:Name="MasterPane" HorizontalAlignment="Center" VerticalAlignment="Stretch" RenderTransformOrigin="0.Column="1" /> </Grid> 4 % Grid 5 4 % % 5 Row 4 4 % DetailsPane6 Row M % 5 % % 55 6 Column 4 7 % 5 5686 4 % = % 55 0@ 4 3 @ 4 3 M% 6 H % 5 5 4 ( % % M % 5 5 (6 4 5 5 DetailsPane = MasterPane 4 %6 4 ItemSource 5 5 4 4 (% ItemTemplate6 DataContext % 4 6 M theTableM 5 % M theTableM % % 5 (6 ItemsSource="{Binding Mode=Default}" ItemTemplate="{DynamicResource ProductDataTemplate}" The ProductDataTemplate must be defined up in the Grid.

ThumbNailPhotoFilePath. Product. ProductPhoto.Name. Product.Integrated Security=True". String query = @"SELECT Product. Product. String connString = @"Data Source=<Your Server>. } DataContext = theTable.Window { DataTable theTable = new DataTable().LargePhotoFilePath FROM Product INNER JOIN ProductPhoto ON Product. Product.ProductPhotoID AND Product. ProductPhoto.ListPrice.ProductPhotoID = ProductPhoto.ListPrice < 100)".Initial Catalog=AdventureWorks2000. Product.Color.Style. conn ). } 5 2 = 4 ( 5 4 .Windows.ProductPhotoID WHERE (Product.Fill( theTable ).ProductPhotoID = ProductPhoto. using ( SqlConnection conn = new SqlConnection( connString ) ) { SqlDataAdapter da = new SqlDataAdapter( query. da.ProductNumber.Size. public Window1() { InitializeComponent().ProductID. Product.0 4 55 % 4 5 DataTable $ 5 4 ' % %5 = () 5 # * ( 4 5 5 public partial class Window1 : System.

2" Storyboard.TargetProperty="Opacity" To="1.60" /> <Style.MouseLeave"> <EventTrigger. % 5 5 % 5 6 M 0 a% 6 5 61 71 a8\ 5 1a % %: 0 a% 5 # 5 5 # # # 4 6 55 5 MouseEnter 4 6 4 MouseLeave # ProductTemplateItemStyle Resources 5 = <Style x:Key="ProductTemplateItemStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Opacity" Value=".! % F "' 0 3 2 = 5 4 Adding Events M 1a 45 % 4 M % 6 5 ItemContainer # 5 5 4 5 5 .Triggers> <EventTrigger RoutedEvent="Mouse.0" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.2" .Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.

0.5" TextAlignment="Right" Text="{Binding Path=ListPrice.21"/> <GradientStop Color="#00FFFFFF" Offset="0.0 2 $ ' () # * Storyboard. Mode=Default}" Height="200" RenderTransformOrigin="0.0" Stroke="{x:Null}" Width="{Binding Path=ActualWidth.5.Actions> </EventTrigger> </Style.5.0.5" EndPoint="0.0.0.19" HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="64" Height="26" RenderTransformOrigin="0.0.0" Width="Auto" Height="Auto" RenderTransformOrigin="0.5.OpacityMask> <LinearGradientBrush StartPoint="0.0.0.TargetProperty="Opacity" /> </Storyboard> </BeginStoryboard> </EventTrigger.2.5.0.OpacityMask> </Rectangle> </StackPanel> <TextBlock x:Name="Name" Margin="0. ConverterParameter='$#.LayoutTransform> <TransformGroup> <ScaleTransform ScaleX="1" ScaleY="-1"/> <SkewTransform AngleX="0" AngleY="0"/> <RotateTransform Angle="0"/> <TranslateTransform X="0" Y="0"/> </TransformGroup> </Rectangle.RowDefinitions> <StackPanel> <Image x:Name="ProductImage" RenderTransformOrigin="0.5" TextWrapping="Wrap" Text="{Binding Path=Name}" TextAlignment="Left"/> <TextBlock x:Name="ProductNumber" Margin="0.00'}"/> </Grid> </ContentControl> .392"/> </LinearGradientBrush> </Rectangle.5"> <Grid x:Name="DetailsGrid" Width="Auto" Height="Auto" RenderTransformOrigin="0.ColumnDefinitions> <Grid. Converter={StaticResource myNumberFormatter}.0.5"> <Rectangle.5"> <Grid. ElementName=ProductImage.5.5.2.###.0.ColumnDefinitions> <ColumnDefinition/> </Grid.0.5" Width="Auto" Height="200" Source="{Binding Path=LargePhotoFilePath}"/> <Rectangle Fill="{DynamicResource ReflectionBrush}" Margin="0.0.5.0.5" TextAlignment="Center" Text="{Binding Path=ProductNumber}"/> <TextBlock x:Name="ListPrice" Margin="0.Column="1" Margin="15.Triggers> </Style> 45 4 % 4= # 5 6 +$ % 5 ! % 5 6 * 5 5 5 %M M4 5 4 M = 5 <ContentControl x:Name="DetailsPane" Grid.1.5.RowDefinitions> <RowDefinition/> </Grid.5.2.0.LayoutTransform> <Rectangle.19" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="64" Height="26" RenderTransformOrigin="0.19" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="64" Height="26" RenderTransformOrigin="0.5"> <GradientStop Color="#7FFFFFFF" Offset="0.-0.

OpacityMask> <LinearGradientBrush StartPoint="0.5" EndPoint="0. 5 = % 5 D [ 086 4 5 6>06> 5 5 6> 5 6 6> \ % % LayoutTransform OpacityMask % LinearGradientBrush <Rectangle.! % F "' 0 > +$ % 45 5 4 4 4 5 6 % 7 5 % .0.LayoutTransform> <Rectangle.21"/> <GradientStop Color="#00FFFFFF" Offset="0.Column="1" Margin="1.LayoutTransform> <TransformGroup> <ScaleTransform ScaleX="1" ScaleY="-1"/> <SkewTransform AngleX="0" AngleY="0"/> <RotateTransform Angle="0"/> <TranslateTransform X="0" Y="0"/> </TransformGroup> </Rectangle.1.OpacityMask> 45 5 4 5 4 > = > 45 = 4 % %M 5 GridSplitter % 5 5 5 % ( % % = 6 % # <GridSplitter x:Name="GridSplitter1" Grid.5"> <GradientStop Color="#7FFFFFFF" Offset="0.1.-0.0.5.5" Height="Auto" RenderTransformOrigin="0.5.5.5"/> 45 4 % 1 = .5.392"/> </LinearGradientBrush> </Rectangle.3" HorizontalAlignment="Left" Width="11.5.

0 1 $ ' () # * 1 = # 5 =@ %%%6 @ # 6 6 2 5 5 365 5 5 % @% 4 5 4 4 % 6-< 3 6 D 5 % # I .

: % 5 # ( 5 4 X 4" 5 8 5 # ( # . 4 + 5 # L 4 4 % . whilst guiding you to producing your first Silverlight application. 5 ( # 4 % % 5. It may just be 'Hello World' but soon.' # 0 / - / 3- -3&/ 05 January 2008 John Bower stays calm. /1 )$% " .. 5 # : 5 6' =@ %%%6 @ A . % 6? # 6 . % 5 % 9 % # 5 5 # = 5 # 5 5 5 % 4 5 4 C' 9 6 @ 6 5 4 4 4 6 5 : % 4 # 4 4 4 % : 6-< 36> 5 4 % J # 6-< 36> ( 5 5 % 6 5 4 6 # 'H 4 6 # .% % : 5 % 5 # <# 4 5 5 4 6. :# 5 # 4 5 5 00" # : O5 : 5 5 % F"' =" M % 4 5 5 6. 5 4 W 4 C' 6-< % # 4: <( 4 5 % 5 % # # "5 4: % 6-< 5 45 4 6 . : ( 56 )$% A +5 5 4 ( F"' 4 % 5 ( 5 W % % : 5 $ # 4 ( 4 ( ' # 4 4 45 4 ( 5 6 # 6. X 6D # % % # % # . 7 .. 5 5 5 5 E+ 6 ( 4 4 # #6 4 5 E # 5 @ 6-< 4 6 . # 5 : 4 4% 6 % % 5 # 4 4 95 5 . 5 : 6 # # 45 F "'4 . 5 55 <# 5 & 5 5 L4 5 # # % 4 # # % "5 4 4 4 . . and so will you.

[ > 2 /1 02 2 2 + + < < > 01 3K 02 > 02 < [ ( 2 =@ %%%6 5 @ % <( ( <( : 4 + 08 .% 95 % * E 6 : % % # 4 4 4 # # =@ %%%6 5 @ 465 @ % @ . 4 5 5 E O :# 6 ' @ $ . E # 4 6 % # # 5 4 % 95 $ ( +4 % $# 5 ( % 5 % 55 .% 4 45 4 4 E "5 # 6 E+ %: L % % 4: 5 5 5 5 5 % # # % % (A 6 #[ 45 4 92 # =@ %%%6 5 @ 4 4 465 # @ # 060 @ 0 Q % 6 6 (A 4 "5 4% % 95 4 . % + @ # # # % 5 5 5 @ 6 # 4 4% E+ 5 E+6 : % 5 784 % 2 # 6-< 36> 5 45 % % # 6 Q )V V/ # E 5 6 5 465 % # % "5 L5 4 @ 4 5 4 % # 5 # # 4 @# 5 0 7 W 5 X H : 8 @ 4 6 ( # 6& 4 # 55 5 6 . @ <( + 6 F "'4 4 465 5 4 ) 5 @ % .% 95 . 4 =@ @ % % <( 4 + ( 5 * @ 4 ?4 . % ) @ .0 % 4 % # 5 $ +% 6 / ' % *$ ' )$% . @ < D 5 5 # % % : %J 7% 5 % 5 J 8 5 @ % 6 (A [ % 5 # 5 # 5 6 # 4 4 6 % J % - J A $$ )$% E $84 = / 5 5 " $D '? V.

. '! & ! # 8 95 6 95 4 5 5 W % X + 5 5 W HIX E % 5 # Y 4 7 5 4 4 4E BE + 5B # B0 33B86 37 ! & !# 9 .% 95 ( 0 . 4 % 4 4 6 5 4 5 4 5 5 5 5 5 4 5 5 6 4 4 5 6 69 4 % # 4 56E 5 # % 5 5 5 4 5 5 4 5 % % # 6 .' . 5 5 4 =B .: % 4 W ' X W (=X 4 6 . B.5 4 4 5 % 4 5 # W Q F "' 5 6 X 6 4 F"'6 . B 6 4 5 B" 5 / <B. %: % 4 4 !& # $ # 95 = & # E <( 5 5 5 % 5 4 4 # 6. # : + 4 # 4 # 4 .% : 4 5 4 F "' 4 5 5 # 4 5 6 6! % F "' % % F"' ) "' 4 % ( 6 4 5 54 5 4 D % 5 5 5 #6 5 % % 4 5 # 6 . 4 = # % 4 % .

dll" Width="640" Height="480" Background="White" > <TextBlock>Hello World!</TextBlock> </Canvas> . 4 5 4 4 5 6.com/winfx/2006/xaml" Loaded="Page_Loaded" x:Class="HelloWorld.Page. 4 5 W .assembly=ClientBin/HelloWorld.microsoft.com/winfx/2006/xaml" Loaded="Page_Loaded" x:Class="HelloWorld..Page. 4 # .03 4 5 % 5 9 $# 5 5 4 5 5 45 4 5 5 5 6"5 4 4 # 5 6'5 %# 5 6.microsoft. 4 % % : ) "' 5 % 4 % 4 # 5 Z J # :W ' X $ +% F "' 4 % % 5 5 6 6 4 6 4 5 5 9 $# 5 4 4 5 4 ) "' 5 # 78X % 5 4 : 4 5 5 4 % 5 F "'4 4 % 5 95 6 % 95 ) "'4 : Z X 5 4 6 % 5 % 4 5 5 ) "' 4 55 6 F "' ( # 6 U (+ 5 V U@ ( + 5 V <Canvas x:Name="parentCanvas" xmlns="http://schemas.dll" Width="640" Height="480" Background="White" > <TextBlock></TextBlock> </Canvas> D 5 (+ 5 6 + % 5 E 5 # : 5 % ( W Hello ( 4 6 World!X 6 # % 4 # 6.% : 4 # 6 06 H 6( # <( 4 5 4 >7 % 5 J D # 4 5 5 6 J 5 5 F "'6E+ 4 (+ 5 % 5 5 % : % % (6 X 4 8 W # 5 W 5 6.assembly=ClientBin/HelloWorld.microsoft.% .microsoft.com/client/2007" xmlns:x="http://schemas.% 4% # # (+ 5 <Canvas x:Name="parentCanvas" xmlns="http://schemas.com/client/2007" xmlns:x="http://schemas.

% (5 5 5 36 > # % 4 5 5 6 # : W ' % % X 5 W (=X (+ 5 : W (=X 4 (+ 5 6 6 # 45 6 5 %: % % J H 5 030 J :# 5 D : 9 ( 4 % 6 : x:Name=”myText” (+ 5 : <Canvas x:Name="parentCanvas" xmlns="http://schemas.dll" Width="640" Height="480" Background="White" > <TextBlock x:Name="myText">Hello World!</TextBlock> </Canvas> :# .Text = "Hello Silverlight!" End Sub ) > % 6 .microsoft. ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() myText.com/winfx/2006/xaml" Loaded="Page_Loaded" x:Class="HelloWorld.Page.' .microsoft.com/client/2007" xmlns:x="http://schemas. 6( 6# 4 Public Sub Page_Loaded(ByVal o As Object. H 6( (+ 5 ( 6# # 4 6 % % 4 % W Q ' 4 5 6 78X4 5 .assembly=ClientBin/HelloWorld.% 5 4.

03 $ +% W ) W ) 4 5 4 (+ 5 # J6 X J% X # ' <# ! # & % #> 1 @ W ) 4 ( 4 ! % % % J X 5 (+ 5 % ! 5 # % %J ! & # % ! # ! 5 4 95 % 6 95 5 % 6 % 4 5 5 = #& #" 6 6 5 5 #$ ?# 4 & 5 % .% # (5 # # 4 4 # 4 # 5 4 # 4 5 5 5 5 6 ( % 4 5 . 5 JD 4 # 5 . 5 5 4 5 % ( # .

# % 95 95 % = 5 + • • • • & & 4 5 5 5 0 4 H Z . # 4 4 5 5 5 5 . 5 % 4 % 4 6 # 4 J N ) . 95 6 4 4 5 5 % # F "' 4 6 5 5 % 5 % % % 5 # 6 5 5 6 % = # . Z M5 & 6 M 5 4 5 ! & 6 ( = 5 6 6 5 5 4 # M ' 5 # 6 4 6 + 6 % M% 36 .N N ' % # \ 4 6 5 % 4 5 5 H !'8 5 4 4 # 5 .# ' 033 3- -3&/ ! -1 1 18 January 2008 +$ % /% ' - John Bower steps up a gear.5 % 5 M J ' % 84 ' 95 > " 6 5 1 6 5 5 $ 5 # 4 4 95 . ) 6! 5 # . and examines the process of using a high-speed function loop to create a fast-paced Silverlight application. # # & # ' J N 5 4 4 5 4 4 M 5 % % # 5 M 5 6 % 4 6 %: # % % 5 5 % %. 5 . 5 76 6 5F DoEvents() 4 5 5 5 4 # 4% 4 5 # 5 5 # % 5 # # 5 5 . % # M . produces a Lamborghini. % 2 D 6 5 4 % C'.

032 & # 4 5 6 # Z % 5 5 # 5 %% N % $ +% 5 N & +4 % % $' 5 # 6 5 5 5 cParent.% 5 % # 95 F "'4 % 5 5 ND 6 5 4 % 5 5 # % #A 9 8 4 % 5 # & 5 7 % % 6 # 6 # 5 9 % 5 F "' 4 % # 5 6D % 5 5 % # %% 5 5 7 - 4 4 6 5 +$ . 6 % . M # 6 5 5 6) 5. 5 N %# 4 % 4 # # 5 % # 4 45 = % 5 4 95 6 95 6 5 # 5 4 4 E M % F "'4 6 : 5 H N 5 Partial Public Class Page Inherits Canvas Dim cParent As Canvas Public Sub Page_Loaded(ByVal o As Object. % % # . M % 5 ' . 6$ 5 6" N @ 4# N 4 4 5 5 M &?. 86 4 . ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set cParent to the canvas with the name "parentCanvas" cParent = FindName("parentCanvas") End Sub End Class % # # 5 4 # ( 6 5 % 5 cParent M 5 95 E N # # # 5 % M . % 3 # 4 % # 7 84 5 95 $ 4 # 95 M 95 % 4 ( = 5 # 5 5 % 4 4 &?'\ &?. 6 5 4 5 4 &?.

4 .Add().Source = New Uri("images/car.Add(imgSprite) End Sub 4 # # # &?. 4 45 M > % 4 5 # J 5 54 6 C +$ % $ $ $ ' # % 4% 5 & # 5 % # : # 95 & I & 4 M . 4 M # # " # 6 + 5 " # 6 .Children.Children.jpg". # % 4 .# 4 % ' 5 %. 4 V % 5 # 6 6 J %4 5 5 Page_Loaded() 4 5 Public Sub Page_Loaded(ByVal o As Object. UriKind. 4 : 5 \ 5 & I 4 4 I 4 .Relative) ' Add the Image to the Canvas cParent.5 F "'4 %M 5 95 New M% % # 6 4 5 5 . 95 VisualCollection 5 Canvas. # 4 # 5 95 6 5 4 4 % 5 %M 5 5 6 6 5 # M H 5 % # % . 2 # = 95 M ( 6H &?. % 5 % U # V (5 % % % 5 # U. ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set cParent to the canvas that called this function cParent = FindName("parentCanvas") ' Create Image attachImage() End Sub . 95 5 imgSprite 03> = Dim imgSprite As New Image .%% # % % 95 Private Sub attachImage() ' Set up the new Image object imgSprite.

Add().Resources.%% 5 6 5 % % % M 5 # 4 4 5 6D M 5 %4 5 4# 6 .Children.Add() 5 sbLoop.Begin().NameProperty. 4 $ +% 6 % 1 2 2 Dim sbLoop As New Storyboard % % 4 % 4 5 # = Private Sub attachStoryboard() ' Set up the new Storyboard sbLoop.SetValue(Storyboard. # 5 45 4 5 4 %= 4 % 5 Page_Loaded() 4 5 6 Public Sub Page_Loaded(ByVal o As Object.Begin() End Sub N 5 N 6 % 5 6 SetValue M Name 5 M Name 6& E D M 5 5 Canvas. 4 4 . Q 5 95 6 # 5 6) %# 5 % % 2 % 5 5 = 4 4 4 4 5 4 5 % % M 6 . 5 # 6 4 6 % % % 5 % Canvas.Add(sbLoop) ' Start playing the Storyboard loop sbLoop. "loop") ' Add the Storyboard to the Canvas cParent.Resources. ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set rootCanvas to the canvas that called this function cParent = FindName("parentCanvas") ' Create Image attachImage() ' Create Storyboard attachStoryboard() End Sub .031 # % 5 5 % .

intY As Integer . 95 M 5 Dim intX. movingUp As Boolean .Height) Then ' bounce movingUp = Not movingUp End If End If ' X-Axis logic If movingLeft Then intX -= 2 ' update X If intX < 0 Then ' bounce movingLeft = Not movingLeft End If Else intX += 2 ' update X If intX > (cParent. 4 % + % 6 Dim movingLeft.Height .imgSprite. 5 4 . # 5 5 0 4 % 4 5 %% % 4 8 % 4 4 %M 5 5 6 4 % # 4 5 4 % # 7 Private Sub loop_Completed(ByVal sender As Object. ByVal e As EventArgs) ' Y-Axis logic If movingUp Then intY -= 1 ' update Y If intY < 0 Then ' bounce movingUp = Not movingUp End If Else intY += 1 ' update Y If intY > (cParent.Width . 4 Q 5 95 # ?@ 4 5 4% 6+ # 4 6 .# ' 03/ E +$ % 5 %A 5 $ % / @ 5 5 ' % 0 % % # % % # . 4 6 .imgSprite. 5 5 4 6 .Width) Then ' bounce movingLeft = Not movingLeft End If End If .%% 6 # M5 % .4 6 .( % 5 % 5 95 # 4 % % # # % .

Resources.03 $ +% ' Update the Image object's position imgSprite.SetValue(Canvas. 5 $# 5 N <( .%% # A : 5 % % 5 4 # enableFrameRateCounter6 .% J # ! 4 > 5 4 # % % 5 5 J 5 5 5 % 5 4 # 4 M# 5 6 .SetValue(Storyboard.Begin() End Sub ) 4 4 5 5 movingLeft. intX) imgSprite. "loop") ' Add the Storyboard to the Canvas cParent. : 4 6 % .Completed. 4 % 5 4 % N 5 % 5 6 5 5 4 95 6 5 % .Add(sbLoop) AddHandler sbLoop.TopProperty.NameProperty. < % . intY) ' Restart the Storyboard sbLoop. 5 imgSprite # SetValue(). 4 6 % . % 5 4 5 % % 4 % # % 5 % 5 6 % 5 4 45 6 # 5 # 5 Private Sub attachStoryBoard() ' set up the new Storyboard sbLoop.SetValue(Canvas.LeftProperty. AddressOf loop_Completed ' Start playing the Storyboard loop sbLoop.Begin() End Sub . 4 4 <(5 J 4 5 % 9 4 5 6 # 4 6 5 D 5 45 F Left Top 6 4 # 4 4( 6 4 movingUp 95 % . '% ) 4 ? $ $ 4 4 % :$ 5 %6 % $ 4 '* = M 5 % ( 5 %4 M .

5 5 % TextBlock % TextBlock 4 # ' 4 4 \ $# 5 .: % .Millisecond. ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set rootCanvas to the canvas that called this function cParent = FindName("parentCanvas") ' Create Image attachImage() ' Create FPS TextBlock attachFPSText() ' Create Storyboard attachStoryboard() End Sub .Children. LastTime As Integer M5 %4 5 5 TextBlock # = Private Sub attachFPSText() ' Add the TextBlock to the Canvas cParent.Add TextBlocks % 5 4 % Page_Loaded() 4 5 = Public Sub Page_Loaded(ByVal o As Object. 6 % # 03.%% 5 4 5 Date.Now. FrameRate .# . M % % 4 5 4 5 . M % 55 % 5 6 95 5 95 5 (+ 5 txtFPS: (+ 5 txtFPS: 4 4 6 6 M M 5 5 5 . 6 % % . 4 % 5 % % Dim txtFPS As New TextBlock 5 % % 4 5 5 4 & 1 4 5 5 5 4 = Dim FrameRate.Children.Add(txtFPS) End Sub % 4 5 5 5 VisualCollection6 % TextBlock 5 4 5 4 4 % Canvas.

SetValue(Canvas. .Height . intX) imgSprite.Height) Then ' bounce movingUp = Not movingUp End If End If ' X-Axis logic If movingLeft Then intX -= 2 ' update X If intX < 0 Then ' bounce movingLeft = Not movingLeft End If Else intX += 2 ' update X If intX > (cParent.Millisecond Then txtFPS.02 Private Sub updateFPS() If LastTime > Date.Now.Now. 6 5 4 % LastTime % # 4 # 4 Date.imgSprite.TopProperty.Now. Date.LeftProperty.Millisecond Else LastTime = Date.Text = "FPS: " & FrameRate FrameRate = 0 LastTime = Date.Millisecond.Width .imgSprite.Now.Width) Then ' bounce movingLeft = Not movingLeft End If End If ' Update the Image object's position imgSprite. ByVal e As EventArgs) ' Update frame rate updateFPS() ' Y-Axis logic If movingUp Then intY -= 1 ' update Y If intY < 0 Then ' bounce movingUp = Not movingUp End If Else intY += 1 ' update Y If intY > (cParent. intY) .SetValue(Canvas.Millisecond 4 LastTime 5 # 4 FrameRate 44 # 6 A % Q 5 # 4 % \ $# 5 4 4 6 % 4 5 78 4 5 = Private Sub loop_Completed(ByVal sender As Object.Millisecond FrameRate += 1 End If End Sub $ +% <# 4 5 5 4 \ % 5 FrameRate .Now.

<( 4 . % % % 5 % 4 5 %4 . 6 I@ <<8 % % ( $ + 5 &.Begin() End Sub 5 5 4 # % %4 # 5 % 6 (+ 5 4 $ 5 6 4 45 6 . 5 45 4 5 . : . 5 5 4 5 4 # 5 95 95 : 5 6 # 5 5 5 4 % 5 .# ' 02 0 ' Restart the Storyboard sbLoop. M5 ( 5 4 N 6 7 N& % 5 4 .

21 # 1 6 21 # 1 5 5 5 E+6 5 6 E+ L =@ %%%6 @ =@ %%%6 @ 65 65 @% @% 4 @ 4 @ 5 4 @ 5 4 @ .02 $ +% 1.

M 5 % 5 # = • • • • • & < & ? & 5 % 4 <( + : 5 # 5 & <( % $' % + 4 J . 3 : + 05 February 2008 John Bower demonstrates more of the features of Silverlight. " . # 5 % 5 % 4 % # : %. % # # 5 4 = . 4 % # : 4 <( 5 %: + 4 . 5 &. and shows how one might write an application that avoids UI pitfalls by placing your design responsibility squarely on your users’ shoulders.% % # ( 5 4 4 . :# 5 4 cc # 6 % 4 # J 4 X M 5 45 4 4 % 5 5 5 : 4W 45 4 % 6 % 5 4 5 & . 45 02 3 3- -3&/ 2 3 - . . 5 4 6 ) 5 % M . 4 4 ? AH 4 # 4 M ! AH . 5 .% . it’s their fault! +5 % 0.# & . % 4 5 4 # % .% 6 4 4 * 5 d 5 4 45 6 . 45 6 4 4 4 # : 45 6 D 5 % 4 5 4 5 ! 5 A # 5 A # # 5 % % 5 4 5 (5 # 5 5 <( * 5 & + . : # 5 4 4 4 5 # 4 5 6 5 % 4 * 5 4 % 5 ' % 6 4 ( ( & . 6 5 45 . 4 5 5 % # 4 % 5 . If it looks bad. 5 # 6 * 4 4 % % G% 5 % 6 % 5 4 # 5 5 ( % . and Expression Blend. / . %4 5 4 M 5 % . 45 % . 4 4 Z # . .5 % 4 6) 5 % 5 cc # % 5 5 % &.% # .

%. 5 # <( . :# D : 6 D 5 5 4 % 5 5 5 F "'5 <( % 4 + 6 & F "' 5 5 W H <( : % % : 5 4 + ZX .02 2 =@ %%%6 5 @ 465 4% % +4 !# % 5 % # % 5 % W % 6( @( 5 5 @ % # %6 % # <( 6 # %# 95 E $ 6 +% 4 6 .%. Z X 5 X 8 # 5 5 & 6 5 5 4 7. 5 95 % 5 %& W V.%% .

$ 4 4 : % % 4 4 # 6 # = 6 ' F "' 54 56. 45 02 > D : % % 4 % <( # + 95 % 95 6$ 5 5 6 W X D 5 +$ <( + 6 . 4 % + 5 4 # : # 5 4 5 % % %: % . 4 :# 6 # 5 : E 4 6D % % 4 4 4% %% 5 # % < % .# & .

5 4 4 5 % % 4 #= 4 95 7 # 6 % % . : 9 5 4 4 6 4 6 # 6 5 ! > # 7 8 0 5 4F "' 5 4 % : 5 5 5 4 5 # 4 4 6 6D 6 % 86 5 95 6 .02 1 $ +% ) 06 : . 4 % 95 # 568 Objects and Timeline 5 % 5 # 5 95 4 % % % 92 7% 5 : # % H # 45 6 F "'4 .( 6 ( : 5 6 5 %: <( % 4% 6 # TX 5 5 5 6 6 % 5 5 % + 4 : ( 5 % F "' 5 : 5 9 45 95 : 54 # 4 * 4 W S ' 5 # 6 % % a 5 5 < 92 5 # 4 5 4 : 5 % .

# & . 45 4 45 4 4 6 5 # % 4 45 4 5 95 4 % 4 4 5 % 5 : 4 : 4 ?5 # %% 4 5 6 1 4 % 5 U % 6 1 5 % X ( # 5 5 5 # 5 45 # 54 5 5 6 . 45 02 / 0 a6 D H 5 : H 5 4 5 5 *9 6 % % 6.

02 $ +% : 5 % 7 4 8 5 5 4 5 5 + 5 6 4 5 5 5 45 4 5 % - 5 4 # 4 ?5 % % 5 4 6 W U.VX W +!X 6 <(5 %: ! 4 J % : 5 4 56 5 6 5 6 4 % 4% a 5 5 \ 5 :# 9 5 5 D 5 .% # : 5 6 # 6 4 6 H 5 5 . 5 4 . 5 5 + 5 5 56 ?5 : 5 4 # 5 5 % % : 6<5 5 % 5 + 5 H 5 / a6 / 4 # 6$ % 5 % # .

4 % ' 5 6 5 % ' %+ 5 % # % 5 %M 4 6 X 5 % 4 54 % 5 5 M% W 6 % 5 6 % % 4 6 X 4 M % %: 5 : 5 5 6 ( J 45 # 4 4 ( 6 ( 4 %6 ? W <html> = <% Dim r.cookies("BtnColorG"). 45 02 . b2 response.QueryString("rIn") g2 = Request. g.cookies("BtnColorR") g=request. g2.Expires=date+1 response.QueryString("gIn") b2 = Request. : % : E 4 # % : 6 5 % ?'c # ( % 6$ 5 95 6 5 5 4 5 5 W X D 6D 95 4 # 5 %: 4 5 4 4 6 % C +$ .cookies("BtnColorB")=0 r=0 g=0=0=0=0 b=0 end if %> : # 4 6 .QueryString("bIn") if r2 <> "" and g2 <> "" and b2 <> "" then response.cookies("BtnColorR").cookies("BtnColorR")=0 response.cookies("BtnColorB")=b2 end if r=request.cookies("BtnColorB").# & .Expires=date+1 r2 = Request.cookies("BtnColorB") if r="" or g="" or b="" then response.% : 5 % .cookies("BtnColorG") b=request.cookies("BtnColorG")=g2 response.cookies("BtnColorG")=0 response.cookies("BtnColorR")=r2 response.Expires=date+1 response. b Dim r2.

4 55 95 6 5 6 & . W 6 X %: $ 6 9 5 4 45 % ) "' 4 6 % <body> = % ( <form action="TestPage. 5 5 6 6( 5 6# X $ + # 4 $ 5 84 % $ 4 ) ' 4 4 95 % 4 % = 5 # 7 % 6H Dim btnControl As New button 5 # Dim pCanvas As Canvas 4 W 5 6( X W : 6( # # 5 X4 6 .Children.0> $ +% E +$ ( H % ' .%% # 4 % # % # % 5 45 : 6 = : 4 5 4 5 Public Sub Page_Loaded(ByVal o As Object.asp"> <input type="text" name="rIn" value="<%=r%>"> <input type="hidden" id="r" value="<%=r%>"> <input type="text" name="gIn" value="<%=g%>"> <input type="hidden" id="g" value="<%=g%>"> <input type="text" name="bIn" value="<%=b%>"> <input type="hidden" id="b" value="<%=b%>"> <input type="submit" value="Save Colour"> </form> # 5 4 4 4 : %: 6 # % % ( ( % % 6 5 % % 54 % 5 5 6 # % % F +$ # 5 . W % $ . ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set pCanvas to our root canvas object pCanvas = FindName("parentCanvas") ' add our control to the canvas pCanvas.Add(btnControl) .

#

&

,

45

0> 0

End Sub

H 5

:

#

4 6

G+
, :

%% $
:# 5 # # &, 95 % 5 95 % W 6( 6( 6# X 6

$ $ $ ,3
# 4 <( : 5 X # 6

84
95 % + <( % 5 5 6 5 4 45 %: 5 4 : 5 5 6 5 6 # % 5 4 %

<( W

Dim bg As Rectangle

:

+

#

5

=

Public r, g, b As Byte

- ( : % 5 % 6

5 2 # 5

5

6 1 2

5 5 & #

%: 4

Private Sub ChangeBG(ByVal a As Byte, ByVal r As Byte, ByVal g As Byte, ByVal b As Byte) bg.SetValue(Rectangle.FillProperty, Color.FromArgb(a, r, g, b)) End Sub

:# 5 4 " 5 H# < #

5 5

% 5 5 5

5 5

4 < 6

W

6( : 5

X %

6 %4 5 6

Private Sub mOver(ByVal o As Object, ByVal e As EventArgs) ' change the fill colour of the BG rectangle ChangeBG(255, r, g, b) End Sub Private Sub mOut(ByVal o As Object, ByVal e As EventArgs) ' change the fill colour of the BG rectangle ChangeBG(255, 0, 0, 0) End Sub

<

5

5 + 5 % % 5 5 6 5 5

4 # 4 % # 4 95

# 6 # 5 :

4 5 % 4 4 5

#

6

< % 5

4 5

:

(

6

0> % % ' : 5 5 + 5 % 4 5 5 4 5 % 5 = 5 % W ' % X % # 4 5 95 95 5 % 5

$ % 5

+% % 6 5 5

Private Sub bgLoad(ByVal sender As Object, ByVal e As EventArgs) ' set bg to the rectangle that called this sub bg = sender ' change the fill colour of the BG rectangle ChangeBG(255, 0, 0, 0) End Sub

% 5 H 4 W % 6( % % 5 X 4 45 5 W 5 6( 4 =

95 4 6 X 6 5

5 % % #

6, % 6

5

%

<Rectangle Loaded="bgLoad" Width="234" Height="84" Fill="#FFFF0000" Stroke="#FF000000" Canvas.Left="8" Canvas.Top="8" RadiusY="11.5" RadiusX="11.5" Opacity="1" x:Name="btnBG" StrokeThickness="1"/> Now find the last rectangle tag in the Canvas and add the following highlighted bit of code: <Rectangle MouseEnter="mOver" MouseLeave="mOut" Opacity="0.51" Width="234" Height="84" Stroke="#00000000" RadiusX="11.5" RadiusY="11.5" Canvas.Left="8" Canvas.Top="8"> <t;Rectangle.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#FFFFFFFF" Offset="0.174"/> <GradientStop Color="#00FFFFFF" Offset="1"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle>

) D

> + 5 J # "

% "

6 4 5 # 5 % 4 5 + 5 Z 4 % 6

#

&

,

45

0> 3

H
- % #

%'

$
%:

! 4
% 9 6

)$%
4 4 # 6 5 # 5 4 56 4 = ! 2

Z ! 8 W 6( % 6# X

5 %: 95 4 % 4 , 4 %

5

Imports System.Windows.Browser

%

5

#

%

5

:

5

=

Dim document As HtmlDocument

% 6

# 4 55 = ? 55

5 ! % + 4 5

%

6 % 5 #

6H 5 %

%% 5 # 4 # 5 % 6 ! 5 % # 4 5 2 %: : # 54 J # 5 6 4 7 % .GetElementByID("r") Dim gElement As HtmlElement = document.GetElementByID("b") btnControl. ByVal e As EventArgs) ' Required to initialize variables InitializeComponent() ' set pCanvas to our root canvas object pCanvas = FindName("parentCanvas") ' add our control to the canvas pCanvas.r = rElement.GetAttribute("value") btnControl.GetElementByID("g") Dim bElement As HtmlElement = document.Document Dim rElement As HtmlElement = document.g = gElement.b = bElement.GetAttribute("value") btnControl.GetAttribute("value") End Sub 5 8 % .0> 2 4 % 45 : 4 5 = $ +% Public Sub Page_Loaded(ByVal o As Object.Add(btnControl) document = HtmlPage.Children.

: 5 <( 4 # 5 5 " 5 P\ # ( % " 5 5 . @ 5 4 5 6 5 5 95 9 4 4 % # 4 6 : . 6 ) J 5 5 5 4 D # \ % 5 4 6D 5 5 %4 4 J % % 95 95 65 @( E+ 5 @ L % 4% = &. 45 0> > $ 5 5 D 5 6 H # .# & . M # 4 + # 4 # . 4 666 % =@ %%%6 @ . 5 4 % 4 5 5 # 5 4 5 : . # 6.

. . .. 0 3 2 6( ? <( 6# 0 . 3 3 0 20 5 95 5 31 #5 0 2 0 0> 0> 0 > E %/ / / / > 1 / 0 > ?% + 02 02 02 02 02 3 2 > 1 2 > 1 / % 0 E E I 0 . 2 2 2 2 2 2 0 3 2 1 > 1 1 1 1 1 1 1 / / / > 0 3 1 / . $F 6-< 0. 03 032 02 02 02 2 > . . . . / 00 000 002 001 02 0> 0> . 03 030 03 033 032 03> 03. 0 # 0 / 0 0 . 2 > . / #5 . > 000 003 001 00. . 0 0 3 0 3 / . . . 0 0> 3 .. . . 0 0 0 . 02 00 02 0 + 5 # 00 0 % 65 4 02 01 3 2 > 3. 31 . . . 0 0 2 > / . $# 5 00 0 " " " 3 2. 030 032 0> 0> 3 2 2 1 . 02 02 02 02 02 02 0> 0 3 2 1 . 0 00 0 03 02 01 0/ 0 0 3 2 / 3. . 3 2 > 2 >. 2 0 . 03 03 03. 3 5 02 02 0> 3 . . 3 &?'? % ?% " / 2 < 1 1 1 1 1 1 1 1 1 3 2 > 1 / . #5 0 3 0 0 / / / / / > 1 / . . 0 2 1 . 0 . 0 3 0 20 /0 . . $F 3 2 > 1 / 0 3 0 20 >0 /0 . 1 1 1 1 0 3 1 1 1 1 1 / 3 0 3 0 20 10 / 2 > 1 . % '# 0 3 0 2 0 / 00 -< 36> 0 / 0 .0> 1 $ +% 3 ! . . 0 2 > 1 / . . 0 3 00 8 ! 03 0> > 1 2 > 3 . .