Script# and ASP.NET AJAX : Productivity and User Experience

unalaskaweepingInternet and Web Development

Jul 19, 2012 (5 years ago)

790 views




Script# and ASP.NET
AJAX : Productivity and
User Experience

Discovering Script# by creating ASP.NET AJAX
Extensions components




Simon FERQUEL

22/05/2007


SUMMARY

Prerequisites

................................
................................
................................
................................
...........................

3

Skills

................................
................................
................................
................................
................................
.....

3

Software

................................
................................
................................
................................
..............................

3

Presentation

................................
................................
................................
................................
............................

4

Script#

................................
................................
................................
................................
................................
.

4

ASP.Net Ajax Extensions
................................
................................
................................
................................
......

4

Introduction

................................
................................
................................
................................
............................

5

First step: Solution and Projects creation

................................
................................
................................
...............

6

Second step: creation of client side components

................................
................................
................................
.

10

IPV4Box

................................
................................
................................
................................
.............................

10

IPCalculator

................................
................................
................................
................................
.......................

15

Third step: using our client components in a server control

................................
................................
................

18

Conclusion

................................
................................
................................
................................
.............................

25




PREREQUISITES

SKILLS

In order to understand this article and code samples, you must be familiar
with
C# and JavaScript languages,
as
well as

ASP.
NET
, ASP.
NET AJAX
and DOM
APIs
.

SOFTWARE

In order to reproduce the code or execute the sample, you must install Visual Studio 2005 S
P1 (at least
standard edition), and
ASP.
NET AJAX
Extensions
. The AjaxControlToolkit is not needed. You also have to install
Script# 0.3
.



PRE
SENTATION

SCRIPT#

Script# is a code generation tool targeting JavaScript.
Like its e
quivalent for the Java world

GWT

(Google Web
Toolkit),
it

allows
writing

client side code in C#

(one difference is that the output JavaScript is readable by
ordinary
humans


)
. Output of the C# compilation is not MSIL, but JavaScri
pt,
browser
-
side interpreted. The
goal is to gain all
th
e

advantages
of the C# language for client
-
side
development:

typed language, natively
object
-
oriented, compile
-
time syntax validation, provided with a very rich toolset (
IntelliSense
, Refactoring,
Obj
ect Browser, Class diagram, Xml
-
Doc…)…

Script# is in an early version, but already allows improving productivity in our
Web
projects. However, when
using this tool, you must keep an eye on the generated code, and there are some issues such as no Xml
-
Doc in

the generated scripts, some code generation problems, and
obscure
compiler errors outputs.

ASP.NET AJAX EXTENSI
ONS

ASP.
NET AJAX
Extensions is an extension to ASP.
NET
Web Development framework, providing client
-
side and
server
-
side libraries allowing
to
“easily”
create AJAX
Applications compatible with most web browsers. In
version 0.3, Script# allows to create client components, behaviors and controls for ASP.
NET AJAX
.



INTRODUCTION

In order to discover Script# in an ASP.
NET
environment (with
AJAX
Exten
sions), we will crea
te a small Web
Application with
plenty of
Browser
-
Side behavior.

First, we will create a client control allowing the user to enter an IP address. Then, we will create a client library
that calculates

a subnet
address
from a
host
address

and a
subnet mask
.

In order to do this, we will create a solution with 3 projects:



IPTools.ClientScripts: Script# class Library for Atlas (
the former codename of
ASP.
NET AJAX
Extensions)



AjaxEnabledControls: ASP.
NET
Web Control library (used to integrate
generated JavaScript files to an
assembly and to create a Server Control using the client IPBox).



TestWebSite: Ajax
-
enabled
Web
Application, used to test our client and server components.

You
can download the final solution on this article
’s

dedicated Web Page.


FIRST STEP: SOLUTION

AND PROJECTS CREATIO
N

Begin
by
creating a blank solution (we will add projects in it later). In this tutorial, I call it “ScriptSharpArticle”
but its name is not important. In order to do
this
, click on “File
-
> N
ew
-
> Project…”, then select “Other Project
Types
-
> Visual Studio Solutions
-
> Blank solution”.


To create projects in it, right
-
click on the solution (in the “Solution Explorer” box), then “Add
-
> New Project…”.

Add the next 3 projects in your solution
:



IPTools.ClientScripts: Choose “Visual C#
-
> Script#
-
> Atlas Class Library”:




AjaxEnabledControls
: Visual C#
-
> Windows
-
> Web Control Library




TestWebSite: Visual C#
-
> Web
-
> ASP.Net Ajax
-
Enabled Web Application


Right
-
click on the Web Application
and click “Set as Startup project”.

Add a reference to System.Web.Extensions to the Web Control library.

Delete the generated class in the Script# project and the generated WebControl in Web Control Library.

In the Script# project, add a class named IPV4Box. It will be a client
-
side control. Some
references are

automatically added to the project and will cause

the

compilation to fail (Script# is in an early version, so the
templates are a little buggy).
Delete

all references but “aacorlib” and “MicrosoftAjax”. Replace the
generated
code
with
:


using

System;

using

Sys;

using

Sys.UI;

using

System.DHTML;


namespace

IPTools.ClientScripts

{


public

class

IPV4Box

:
Control


{


public

IPV4Box(
DOMElement

element)


:
base
(element)


{


}


}

}

We will implement this control in the next part.

Now, we will do some adjustments in our projects properties, in order to automate
the
embedding of our
generated JavaScript files in the Web
Control Library assembly.

To do this, go to the Script# project properties, in the “BuildEvents” tab. Add the following line in the “Post
-
Build event command line” entry:

xcopy /Y "$(TargetDir)*.js" "$(SolutionDir)AjaxEnabledControls"

Compile the solution.

What we have just done has for effect to automatically copy generated scripts to the
WebControl library project folder after each comp
ilation of the Script# project. Now we will include the files to
the project and set them as “Embedded as assembly resour
ce”.

In the
Solution Explorer
, select the Web Control library, and click the second icon (

Show all files

).

Select
IPTools.ClientScripts.js and IPTools.ClientScripts.debug.js, then click the first icon (“
Include
in project”). Keep
the files selected, show

the property box and set the “Build Action” option to “Embedded Resource”.

Our projects


configuration is complete. Each time the
solution is

rebuilt, the JavaScript files will be generated
and embedded as resource of the Web Control Library.



SECOND
STEP: CREATION OF CL
IENT
-
SIDE COMPONENTS

IPV4BOX

We will begin by creating a
client
-
side
control allowing the user to enter an IP
address
. It will be composed of 4
TextBoxes, separated by “.” characters. We will filter the user
’s

entries in order to
allow

only digits and make
sure that each value is not out of bounds (less than 255).

Open the IPV4Box class that we have created in the Script# project. Look at the class definition: it derivates
from Control (in fact, from the ASP.
NET AJAX
Sys.UI.Control

class). We can also see that its constructor take
s

a
DOMElement as a parameter: it will be the DIV or SPAN element in which we will render our Control.

Now, look at the generated JS script from this C# code (open the IPTools.ClientScripts.debug.js file):

// IPTools.ClientScripts.js

//



Type.registerNamespace(
'IPTools.ClientScripts'
);


////////////////////////////////////////////////////////////////////////////////

// IPTools.ClientScripts.IPV4Box


IPTools.ClientScripts.IPV4Box =
function

IPTools_ClientScr
ipts_IPV4Box(element) {


IPTools.ClientScripts.IPV4Box.initializeBase(
this
, [ element ]);

}



IPTools.ClientScripts.IPV4Box.registerClass(
'IPTools.ClientScripts.IPV4Box'
, Sys.UI.Control);


//
----

Do not remove this footer
----

// Generated using Script
# v0.3.0.0 (http://projects.nikhilk.net)

//
-----------------------------------

If you have already developped ASP.
NET AJAX
components, you should be familiar with this code. In fact, the
Script# compiler has “translated” for us our class declaration, its constructor and all
C# language constructs
such as
namespaces
and
inheritance
using the JS syntax proposed by ASP.
NET AJAX
E
xtensions. All the code
that we will write in C# (methods, member variables, properties, etc.) will be translated in the same way.

Go back to the C# code.
We will begin by implementing our control rendering code. To do this, we will write
some DOM code to
append child elements to the DIV or SPAN element passed to the constructor.

We will use member variables to keep a reference to our interface elements:

using

System;

using

Sys;

using

Sys.UI;

using

System.DHTML;


namespace

IPTools.ClientScripts

{


public

class

IPV4Box

:
Control


{


TextElement
[] _textBoxes;



public

IPV4Box(
DOMElement

element)


:
base
(element)


{


_textBoxes =
new

TextElement
[4];


for

(
int

i = 0; i < 4; i++)


{


_textBoxes[i] = (
TextElement
)
Document
.CreateElement(
"input"
);


_textBoxes[i].Type =
"text"
;


_textBoxes[i].Style.Width =
"30px"
;



this
.Element.AppendChild(_textBoxes[i]);


if

(i !
= 3)


{


//

Here, we use span elements to render the ".
", because Script# does not
know the


// "CreateTextNode"
method of the Document object. In fact if we write
Document.CreateTextNode(".")




// it will make a "compilation" error.


DOMElement

span =
Document
.CreateElement(
"span"
);


span.InnerHTML =
".
"
;


this
.Element.AppendChild(span);



//
An other solution would

have been to use

"Reflection" :


// DOMElement toAdd = (DOMElement)Type.InvokeMethod(typeof
(Document),
"CreateTextNode", ".
");


// this.Element.AppendChild(toAdd);


}


}


}


}

}

Bef
ore going further, we will modify the two other projects to allow us to test our control.

We will begin by exposing embedded Script files as a WebResource. To do this, we have to create a C# file in
the Web Control project that we will call WebResources.cs
:

using

System.Web.UI;

[assembly:
WebResource
(
"AjaxEnabledControls.IPTools.ClientScripts.js"
,
"script/javascript"
)]

[assembly:
WebResource
(
"AjaxEnabledControls.IPTools.ClientScripts.debug.js"
,
"script/javascript"
)]

By doing this, ASP.
NET
will be able to
expose the scripts to the clients using the address:
“http://server/applicationPath/WebResource.axd?*autogeneratedID+”. So, the ScriptManager will be able to
create a reference to these scripts.

Now we will modify our Web Application:

Begin by adding a ref
erence to our Web Control Library

(Right click on the project, then “Add Reference”,
“Projects” tab and double click on “AjaxEnabledControls”). Create an ASP.
NET
page “TestClientIPBox.aspx” in
which you will add a ScriptManager. Then, we will add a span el
ement in our page, reference our script thanks
to the ScriptManager, and
instantiate

our control at the Application Loading:

<%
@
Page

Language
="C#"
AutoEventWireup
="true"
CodeBehind
="TestClientIPBox.aspx.cs"
Inherits
="TestWebSite.TestClientIPBox"
%>


<!
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">


<
asp
:
ScriptManager

runat
="server">


<
Scripts
>


<
asp
:
ScriptReference

Assembly
="AjaxEnabledControls"


Name
="AjaxEnabledControls.IPTools.ClientScripts.
debug.
js" />


</
Scripts
>


</
asp
:
ScriptManager
>


<
script

type
="text/javascript">

Sys.Application.add_init(function()

{


$create(IPTools.ClientScripts.IPV4Box,{},null,null, $get(
"ipBox"
));

});


</
script
>


<
div
>


<
span

id
="ipBox"></
span
>



</
div
>


</
form
>

</
body
>

</
html
>

This is classic ASP.
NET AJAX
Extensions code.

Now, you can execute your page.
The 4 textboxes should appear.

Go back to our Script# class.

Now we will implement the behavior of our control (To do this we will also add a
new “helpers” Script# class with static method for Script# issues workarounds):

IPV4Box.cs
:

using

System;

using

Sys;

using

Sys.UI;

using

System.DHTML;


namespace

IPTools.ClientScripts

{


public

class

IPV4Box

:
Control


{


TextElement
[] _textBoxes;


DomEventHandler

_focusHandler;


DomEventHandler

_keyPressHandler;



#region

initialization and disposing


public

IPV4Box(
DOMElement

element)


:
base
(element)


{


_focusHandler = OnTextBoxFocused;


_keyPressHandler = OnTextBoxKeyPress;



_textBoxes =
new

TextElement
[4];


for

(
int

i = 0; i < 4; i++)


{


_textBoxes[i] = (
TextElement
)
Document
.CreateElement(
"input"
);


_textBoxes[i].Type =
"text"
;


_textBoxes[i].Style.Width =
"30px"
;



DomEvent
.AddHandler(_textBoxes[i],
"focus"
, _focusHandler);


DomEvent
.AddHandler(_textBoxes[i],
"keypress"
, _keyPressHandler);




//
JS allows to add dynamically fields to objects


// To do this with Script#, we will use the “pseudo
-
reflection” API
:



Type
.SetField(_textBoxes[i],
"_customIndex"
, i);




this
.Element.AppendChild(_textBoxes[i]);


if

(i != 3)


{


// Here, we use span elements to render the ".", because
Script# does not
know the


// "CreateTextNode" method of the Document object. In fact if we write
Document.CreateTextNode(".")




// it will make a "compilation" error.


DOMElement

span =
Document
.CreateElement(
"s
pan"
);


span.InnerHTML =
"."
;


this
.Element.AppendChild(span);



// An other solution would have been to use "Reflection" :


// DOMElement toAdd = (DOMElement)Type.InvokeMethod(typ
eof(Document),
"CreateTextNode", ".");


// this.Element.AppendChild(toAdd);

}


}


}



#endregion


#region

Properties


public

string

Value


{


get


{


return

(_textBoxes[0].Value ==
""

?
"0"

: _textBoxes[0].Value)


+
"."

+ (_textBoxes[1].Value ==
""

?
"0"

: _textBoxes[1].Value)


+
"."

+ (_textBoxes[2].Value ==
""

?
"0"

: _textBoxes[2].Value)



+
"."

+ (_textBoxes[3].Value ==
""

?
"0"

: _textBoxes[3].Value);



}


set


{


string
[] parts =
value
.Split(
'.'
);


Debug
.Assert(parts.Length == 4,
"Bad formated IP string"
);


for

(
int

i = 0; i < 4; i++)


{


//
Code generated by

int.Parse
i
n the current version is buggy


//
The workaround is to use

"Réflection"
to call the “parseInvariant”
method.


try


{


object

val =
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
,
parts[i]);




// IsNullOrUndefined
and

IsNan
are

defined in the Helpers class
.


if

(
Helpers
.IsNullOrUndefined(val) ||
Helpers
.IsNan(val))


{


Debug
.Fail(
"Bad formated IP string"
);


}


else


{


_textBoxes[i].Value = val.ToString();


}


}


catch


{


Debug
.Fail(
"Bad formated IP string"
);


}


}


this
.RaisePropertyChanged(
"value"
);


}


}







#endregion



#region

Event callbacks



private

void

OnTextBoxFocused(
DomEvent

e)


{


((
TextElement
)e.Target).Select();


}



private

void

OnTextBoxKeyPress(
DomEvent

e)


{


int

fieldIndex = (
int
)
Type
.GetField(e.Target,
"_customIndex"
);


if

(e.CharCode < 48 || e.CharCode > 57)


{


switch

(e.CharCode)


{


case

8:


case

9:


case

35:


case

36:


case

37:


case

39:


return
;


default
:


string

typed =
string
.FromCharCode(e.CharCode);


if

(typed ==
" "

|| (typed ==
"."

&&
Helpers
.GetSelectedText() !=
((
TextElement
)e.Target).Value))


_textBoxes[fieldIndex + 1
].Focus();


e.PreventDefault();


break
;


}




}


else


{






string

newPart =
string
.FromCharCode(e.CharCode);


string

whole = ((
TextElement
)e.Target).Value + newPart;


int

val = (
int
)
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
, whole);


if

(val > 255)


{


e.PreventDefault();


}


else


{


Window
.SetTimeout(
delegate

{
this
.RaisePropertyChanged(
"value"
); }, 10);


}


if

(whole.Length == 3 && fieldIndex < 3)


{


Window
.SetTimeout(
delegate

{ _textBoxes[fieldIndex + 1].Focus(); }, 10);


}


}


}



#endregion


}

}

Helpers.cs
:

using

System;

namespace

IPTools.ClientScripts

{


public

class

Helpers


{


public

static

bool

IsNullOrUndefined(
object

o)


{


return

(
bool
)
Script
.Eval(
"(o === null || o === undefined)"
);


}


public

static

bool

IsNan(
object

o)


{


return

(
bool
)
Script
.Eva
l(
"isNaN(o)"
);


}


public

static

string

GetSelectedText()


{


string

toEval =
"document.getSelection? document.getSelection():"
;


toEval +=
"document.selection.createRange().text"
;


return

(
string
)
Script
.Eval(toEval);




}


}

}

We can see that because of the non
-
finalized status of Script#, we must use some tricks to avoid some code
generation problems, or do comparisons to null, undefined or NaN. For example, we can use pseudo
-
reflection
or JS

code emission.

The quantity of code is not very big, but we can
still see the gain provided by Script#: compilation error
s

when
misspelling types or methods, secured operators (“if( a = “toto”) …” does not compile because of the operator
mistake),
Intelli
Sense
etc.

When typing this simple script, I made a dozen of mistakes (hey,
it
was early in the morning…) very easy to
correct thanks to the error report of the C# syntax analyser that would have been very difficult to detect and
correct in JavaScript. We
can easily extrapolate the productivity and quality gain for bigger projects. However,
there are
still some situations where Script# is not so practical: exploitation of existing JavaScript code, or
AJAX
Extensions generated proxies for Web Services.

IPCAL
CULATOR

Now we will create a client
-
side class that allows showing the subnet address corresponding to an IP host and a
subnet mask
. We want to be able to instantiate this class with the JS code:

new IPTools.ClientScripts.IPCalculator(ipv4boxAddress, ipv4b
oxMask, myDivOrSpanElement);

So, we will begin by creating the IPCalculator class in the Script# project. Be
careful

to delete invalid assembly
references

and namespace imports from the project and IPCalculator.cs file (because of the buggy template).

Then

we will implement our class (I will not detail the algorithm, as the important thing is only to understand
the code structure and see that we write client
-
side code in C# and that the syntax analyzer helps much):

using

System;

using

Sys;

using

Sys.UI;

usi
ng

System.DHTML;


namespace

IPTools.ClientScripts

{


public

class

IPCalculator

: Sys.
IDisposable


{


private

IPV4Box

_addressBox;


private

IPV4Box

_maskBox;


private

DOMElement

_resultElement;



private

PropertyChangedEventHandler

_addressOrMaskPropertyChangedHandler;



#region

initialization and cleaning



public

IPCalculator(
IPV4Box

addressBox,
IPV4Box

maskBox,
DOMElement

resultElement)


{


_addressBox = addressBox;



_maskBox = maskBox;


_resultElement = resultElement;


_addressOrMaskPropertyChangedHandler = onAddressOrMaskPropertyChanged;



_addressBox.PropertyChanged += _addressOrMaskPropertyChangedHandler;


_maskBox
.PropertyChanged += _addressOrMaskPropertyChangedHandler;



UpdateStatus();


}



public

void

Dispose()


{


_addressBox.PropertyChanged
-
= _addressOrMaskPropertyChangedHandler;


_maskBox.PropertyChanged
-
= _addressOrMaskPropertyChangedHandler;


}



#endregion



#region

Event Handlers


void

onAddressOrMaskPropertyChanged(
object

sender,
PropertyChangedEventArgs

e)


{


if

(e.PropertyName ==
"value"
)


{


UpdateStatus();


}


}


#endregion



#region

methods


private

void

UpdateStatus()


{


if

(!CheckMask())


{


_resultElement.InnerHTML =
"Subnet mask is invali
d"
;


}


else


{


int

address = GetValueFromString(_addressBox.Value);


int

mask = GetValueFromString(_maskBox.Value);



int

network = address & mask;



_resultElem
ent.InnerHTML = GetStringFromValue(network);


}


}



private

bool

CheckMask()


{



int

val = GetValueFromString(_maskBox.Value);


while

((val & 0x80000000) != 0)


val <<= 1;


return

val == 0;


}



private

int

GetValueFromString(
string

strVal)


{


string
[] strParts = strVal.Split(
'.'
);


//
int.Parse bug workaround


int

val = ((
int
)
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
, strParts[0]) <<
24)


+ ((
int
)
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
, strParts[1]) << 16)


+ ((
int
)
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
, strParts[2]) << 8)


+ (
int
)
Type
.In
vokeMethod(
typeof
(
int
),
"parseInvariant"
, strParts[3]);


return

val;


}



private

string

GetStringFromValue(
int

value)


{


string
[] strParts =
new

string
[4];


strParts[0] = ((value >> 24) & 0xFF).ToStri
ng();


strParts[1] = ((value >> 16) & 0xFF).ToString();


strParts[2] = ((value >> 8) & 0xFF).ToString();


strParts[3] = (value & 0xFF).ToString();


return

strParts.Join(
"."
);


}


#endregion



}

}

Let us create a test Web Page for our IP calculator:

<%
@
Page

Language
="C#"
AutoEventWireup
="true"
CodeBehind
="TestClientIPCalc.aspx.cs"
Inherits
="TestWebSite.TestClientIPCalc"
%>


<!
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">



<
asp
:
ScriptManager

ID
="ScriptManager1"
runat
="server">


<
Scripts
>


<
asp
:
ScriptReference

Assembly
="AjaxEnabledControls"


Name
="AjaxEnabledControls.IPTools.ClientScripts.debug.js" />


</
Scripts
>


</
asp
:
ScriptManager
>


<
script

type
="text/javascript">

Sys.Application.add_init(function()

{


var address = $create(IPTools.ClientScripts.IPV4Box,{
"value"

:
"192.168.0.1"
},null,null,
$get(
"boxAddress"
));


var mask = $create(IPTools.Cli
entScripts.IPV4Box,{
"value"

:
"255.255.255.0"
},null,null,
$get(
"boxMask"
));


new IPTools.ClientScripts.IPCalculator(address,mask,$get(
"result"
));

});


</
script
>


<
div
>


IP Address : <
span

id
="boxAddress"></
span
><
br

/>


IP Mask :
<
span

id
="boxMask"></
span
><
br

/>


Network Address : <
span

id
="result"></
span
>


</
div
>


</
form
>

</
body
>

</
html
>

Once again
, this code is very simple ASP.
NET AJAX
code.



THIRD STEP: USING OU
R CLIENT COMPONENTS
IN A SERVER CONTROL

In this part,
we will create an ASP.
NET
server control using our client control.

Firstly, add to the Control Library a new ASP.
NET
custom Web Control named “IPV4Box”:


Modify the generated code: delete the Text property and expose a Value property of type
System.Net.IPAddress:

using

System;

using

System.Collections.Generic;

using

System.ComponentModel;

using

System.Text;

using

System.Web;

using

System.Web.UI;

using

System.Web.UI.WebControls;

using

System.Net;


namespace

AjaxEnabledControls

{


[
DefaultPro
perty
(
"Value"
)]


[
ToolboxData
(
"<{0}:IPV4Box runat=server></{0}:IPV4Box>"
)]


public

class

IPV4Box

:
WebControl


{


[
Bindable
(
true
)]


public

IPAddress

Value


{


get


{


object

o = ViewState[
"value"
];


if

(o ==
null
)


return

IPAddress
.Any;


else

return

o
as

IPAddress
;


}


set


{


ViewState[
"value"
] =
value
;


}


}




}

}

Now, we will write code to automaticaly reference the client scripts and
do the
control rendering:

protected override void OnInit(
EventArgs

e)


{




base.OnInit(e);


ScriptManager

scrMgr =
ScriptManager
.GetCurrent(this.Page);


if (scrMgr == null)


throw new
InvalidOperationException
(
"IPV4Box control needs a ScriptManager"
);


scrMgr.Scripts.Add(new
ScriptReference
(
"AjaxEnabledControls.IPTools.ClientScripts.js"
,
"AjaxEnabledControls"
));



}






protected override void Render(
HtmlTextWriter

writer)


{


writer.Write(string.Format(
"<span id=
\
"{0}
\
"></span>"
, ClientID));


writer.Write(
"<script type=
\
"text/javascript
\
">"
);


writer.Wri
te(
"Sys.Application.add_init(function() {
$create(IPTools.ClientScripts.IPV4Box,{"
);


writer.Write(string.Format(
"
\
"value
\
":
\
"{0}
\
""
, Value));


writer.Write(
"},null,null,"
);


writer.Write(string.Format(
"$get(
\
"{0}
\
")"
, Clie
ntID));


writer.Write(
");"
);


writer.Write(
"});"
);


writer.Write(
"</script>"
);


}

This code is quite simple. In the OnInit method, we do exactly the same thing that we used to do in the markup
of our aspx pages. Our
control requires a ScriptManager to run properly, as it uses it to reference client scripts.
The “Render” method creates a “span” element, and write
s

some JS code equivalent to the one we wrote on
our test pages.

You can then create an ASP.
NET
page and pla
ce this control in it. Run the page to verify that the control
renders properly.

The first thing that we can see, is that at run
-
time, our control renders properly, but the ASP.
NET
designer does
not render anything. This is normal: the designer does not ex
ecute JavaScript code. To improve design
-
time
experience, we will add some code. Begin by adding a reference to System.Design, then create the following
class:

using

System;

using

System.Collections.Generic;

using

System.Text;

using

System.Web.UI.Design;


namespace

AjaxEnabledControls

{




public

class

IPV4BoxDesigner

: System.Web.UI.Design.
ControlDesigner


{


public

override

string

GetDesignTimeHtml()


{



IPV4Box

ctl = ViewControl
as

IPV4Box
;


if

(ctl ==
null
)


{


return

"<span><input type=
\
"text
\
" style=
\
"width:30px
\
" value=
\
"0
\
" />.<input
type=
\
"text
\
" style=
\
"width:30px
\
" value=
\
"0
\
" />.<input type=
\
"text
\
" style=
\
"width:30px
\
"
value=
\
"0
\
" />.<input type=
\
"text
\
" style=
\
"width:30px
\
" value=
\
"0
\
" /></span>"
;



}


else


{


string
[] ipParts = ctl.Value.ToString().Split(
'.'
);


return

string
.Format(
"<span><input type=
\
"text
\
" style=
\
"width:30px
\
"
value=
\
"{0}
\
" />.<input type=
\
"text
\
" style=
\
"width:30px
\
" value=
\
"{1}
\
" />.<input
type=
\
"text
\
" style=
\
"width:30px
\
" value=
\
"{2}
\
" />.<input type=
\
"text
\
" style=
\
"width:30px
\
"
value=
\
"{3}
\
" /></span>"
,ipParts[0],ipParts[1],ipParts[2],ipParts[3]);


}


}


}

}

Then
all we have to do is to add the following attribute to our control:

[Designer(
typeof
(IPV4BoxDesigner))]

Close the
Web
page, recompile the solution and reopen it in design mode: you can now preview your control in
the ASP.
NET
designer.

Second problem to sol
ve: we need to

update the Value property of our server control with the data entered by
the user
, and we must create
a “ValueChanged” event. To do this, we will begin by modifying our client control
to add a client state saving behavior in a hidden field w
hen the form is submitted:

using

System;

using

Sys;

using

Sys.UI;

using

System.DHTML;


namespace

IPTools.ClientScripts

{


public

class

IPV4Box

:
Control


{


TextElement
[] _textBoxes;


DomEventHandler

_focusHandler;


DomEventHandler

_keyPressHandler;


InputElement

_clientStateElement;


string

_clientStateFieldName;



#region

initialization and disposing


public

IPV4Box(
DOMElement

element)


:
base
(element)


{


_fo
cusHandler = OnTextBoxFocused;


_keyPressHandler = OnTextBoxKeyPress;



_textBoxes =
new

TextElement
[4];


for

(
int

i = 0; i < 4; i++)


{


_textBoxes[i] = (
TextElement
)
Document
.CreateElement(
"input"
);


_textBoxes[i].Type =
"text"
;


_textBoxes[i].Style.Width =
"30px"
;



DomEvent
.AddHandler(_textBoxes[i],
"focus"
, _focusHandler);


DomEvent
.AddHandler(_textBoxes[i],
"keypress"
, _keyPressHandler
);






Type
.SetField(_textBoxes[i],
"_customIndex"
, i);




this
.Element.AppendChild(_textBoxes[i]);


if

(i != 3)


{


DOMElement

span =
Document
.CreateElement(
"span"
);


span.InnerHTML =
"."
;


this
.Element.AppendChild(span);





}


}



_clientStateElement = (
InputElement
)
Document
.CreateElement(
"input"
);


_clientStateElement.Type =
"hidden"
;


this
.Element.AppendChild(_clientStateElement);



DOMElement

formElem = Element;


while

(formElem.TagName.ToLowerCase() !=
"form"
)


formElem = formElem.ParentNode;



DomEvent
.AddHandler(formElem,
"submit"
,
delegate

{ _clientStateElement.Value =
this
.Value; });


}



public

override

void

Initialize()


{


base
.Initialize();


_clientStateElement.SetAttribute(
"name"
, _clientStateFieldName);


}


#endregion


#region

Properties


public

string

Value


{


get


{


return

(_textBoxes[0].Value ==
""

?
"0"

: _textBoxes[0].Value)


+
"."

+ (_textBoxes[1].Value ==
""

?
"0"

: _textBoxes[1].Value)


+
"."

+ (_textBoxes[2].Value ==
""

?
"0"

: _textBoxes[2].Value)


+
"."

+ (_textBoxes[3].Value ==
""

?
"0"

: _textBoxes[3].Value);



}


set


{


string
[] parts =
value
.Split(
'.'
);


Debug
.Assert(parts.Length == 4,
"Bad formated IP string"
);


for

(
int

i = 0; i < 4; i++)


{




try


{


object

val =
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
,
parts[i]);




if

(
Helpers
.IsNullOrUndefined(val) ||
Helpers
.IsNan(val))


{


Debug
.Fail(
"Bad formated IP string"
);


}


else


{


_textBoxes[i].Value = val.ToString(
);


}


}


catch


{


Debug
.Fail(
"Bad formated IP string"
);


}


}


this
.RaisePropertyChanged(
"value"
);


}


}






public

string

ClientStateFieldName


{


get

{
return

_clientStateFieldName; }


set

{ _clientStateFieldName =
value
; }


}



#endregion



#region

Event callbacks



private

void

OnTextBoxFocused(
DomEvent

e)


{


((
TextElement
)e.Target).Select();


}



private

void

OnTextBoxKeyPress(
DomEvent

e)


{


int

fieldIndex = (
int
)
Type
.GetField(e.Target,
"_customIndex"
);


if

(e.CharCode < 48 || e.CharCode > 57)


{


switch

(e.CharCode)


{


case

8:


case

9:


case

35:


case

36:


ca
se

37:


case

39:


return
;


default
:


string

typed =
string
.FromCharCode(e.CharCode);


if

(typed ==
" "

|| (typed ==
"."

&&
Helpers
.GetSelectedText()
!=
((
TextElement
)e.Target).Value))


_textBoxes[fieldIndex + 1].Focus();


e.PreventDefault();


break
;


}




}


else


{


string

newPart =
string
.FromCharCode(e.CharCode);


string

whole = ((
TextElement
)e.Target).Value + newPart;


int

val = (
int
)
Type
.InvokeMethod(
typeof
(
int
),
"parseInvariant"
, whole);


if

(val > 255)


{


e.PreventDefault();


}


else


{


Window
.SetTimeout(
delegate

{
this
.RaisePropertyChanged(
"value"
); }, 10);


}


if

(whole.Length == 3 && fieldIndex < 3)


{


Window
.SetTimeout(
delegate

{ _textBoxes[fieldIndex + 1].Focus(); }, 10);


}


}


}


#endregion


}

}

Then we will modify our server control to handle PostBack data
:

using

System;

using

System.Collections.Generic;

using

System.ComponentModel;

using

System.Text;

using

System.Web;

using

System.Web.UI;

using

System.Web.UI.WebControls;

using

System.Net;


names
pace

AjaxEnabledControls

{


[
DefaultProperty
(
"Value"
)]


[
Designer
(
typeof
(
IPV4BoxDesigner
))]


[
ToolboxData
(
"<{0}:IPV4Box runat=server></{0}:IPV4Box>"
)]


public

class

IPV4Box

:
WebControl
,
IPostBackDataHandler


{


[
Bindable
(
true
)]


public

IPAddress

Value


{


get


{


object

o = ViewState[
"value"
];


if

(o ==
null
)


return

IPAddress
.Any;


else

return

o
as

IPAddress
;


}


set


{


ViewState[
"value"
] =
value
;


}


}



public

event

EventHandler

ValueChanged;



protected

override

void

OnInit(
EventArgs

e)


{




base
.OnInit(e);


ScriptManager

scrMgr =
ScriptManager
.GetCurrent(
this
.Page);


if

(scrMgr ==
null
)


throw

new

InvalidOperationException
(
"IPV4Box control needs a ScriptManager"
);


scrMgr.Scripts.Add(
new

ScriptReference
(
"AjaxEnabledControl
s.IPTools.ClientScripts.js"
,
"AjaxEnabledControls"
));



Page.RegisterRequiresPostBack(
this
);


}



protected

string

ClientStateFieldName


{


get


{


return

string
.Format(
"{0}_clientState"
,
ClientID);


}


}





protected

override

void

Render(
HtmlTextWriter

writer)


{


writer.Write(
string
.Format(
"<span id=
\
"{0}
\
"></span>"
, ClientID));


writer.Write(
"<script type=
\
"text/javascript
\
">"
);


writer.Write(
"Sys.Application.add_init(function() {
$create(IPTools.ClientScripts.IPV4Box,{"
);


writer.Write(
string
.Format(
"
\
"value
\
":
\
"{0}
\
",
\
"clientStateFieldName
\
" :
\
"{1}
\
""
, Value,ClientStateFieldName));


writer.W
rite(
"},null,null,"
);


writer.Write(
string
.Format(
"$get(
\
"{0}
\
")"
, ClientID));


writer.Write(
");"
);


writer.Write(
"});"
);


writer.Write(
"</script>"
);


}





#region

IPostBackDataHandler Members




public

bool

LoadPostData(
string

postDataKey,
System.Collections.Specialized.
NameValueCollection

postCollection)


{


string

valueString = postCollection[ClientStateFieldName];


if

(!
string
.IsNullOrEmpty(valueString))


{


IPAddress

postedValue =
IPAddress
.Parse(valueString);


if

(!postedValue.Equals(Value))


{


Value = postedValue;


if

(ValueChanged !=
null
)



ValueChanged(
this
,
EventArgs
.Empty);


}


return

true
;


}


return

false
;


}



public

void

RaisePostDataChangedEvent()


{




}



#endregion


}

}



CONCLUSION

In this article, we have seen a concrete preview of Script# by creating client JavaScript components generated
from C# code, and we have created an ASP.
NET
server control using client
-
side behavior.

Script# appears to be a very good tool to impr
ove quality and productivity of Web Application with much
browser
-
side behavior. Its early
stage of development prevents it
from being

a “must have” for every ASP.
NET
AJAX
Developer, but there is no doubt that upcoming versions will change that.