About the Author

spongehousesΑσφάλεια

3 Νοε 2013 (πριν από 3 χρόνια και 9 μήνες)

65 εμφανίσεις







About the Author

Wes Weeks is president and CEO of AgileWise, a software
development and consulting firm based in Topeka, KS. He has
extensive experience in object
-
oriented technolog
ies, enterprise
application architecture, and Service Oriented Architecture design
and development.
Wes

is currently consulting for Security Benefit,
an organization named one of CIO Magazine’s Agile 100 in 2004
that has successfully employed numerous agil
e methods and
processes.


wesweeks@agilewise.com


Cutout: Developing a custom control can result in developer
productivity gains and ease making global changes to the front
-
end
by having a single control to mai
ntain




Creating Composite Server Controls in ASP.NET 2.0

Maximizing code reuse and developer productivity

by Wes Weeks


One underutilized technique for maximizing code reuse and
increasing developer productivity is the creation and utilization of
ASP.NE
T Server controls
.

Even when the problem domain is well
known and
well
understood, each new project has many developers
starting at square one, dragging
-
and
-
dropping the common Visual
Studio.NET controls onto a blank form and having to manually
repeat
the same processes that other developers in the company
have
done

in the past.

Server controls are an in
-
depth topic and it would take an
entire book to do the topic justice.

In this article, I

ll explain the
basics of how to create a ty
pe of server control called a composite
control

and
highlight some of the differences between ASP.NET
1.x and ASP.NET 2.0 server controls.


The Case for Server Controls

Most businesses have certain sets or types of information that
are ubiquitous to m
ost of the applications being developed.
Addresses, phone numbers, and customer names are just a few
examples of common input collections that are often duplicated
every
time a new application is
developed. Much of this
,

of course
,

can be copied

and pasted from one application to another, but
when changes occur, the
y

have to

be made in multiple
applications. An example of this
kind
of change would be
address
es

when
a company
has
gone international a
nd
must start
capturing the country where their customers are located. U
sing

a
server control, the display and client
-
side logic to handle this new
data can be coded once, with developers
focusing
instead
on
updating the business logic.

T
he server control in our example creates a simple user
interface (UI) for entering phone numbers. It

s composed of a
Label

control, a
TextBox

control and two validator controls, one a
RequiredFieldValidator

and the other a
RegularExpressionValidator
. One

of the advantages to using
composite controls is that you can
use
the ASP.NET controls’
built
-
in behavior to minimize the amount of code you have to
write. For example
,

you

ll be able to
tap
the validator controls


nat
ive functions without having to write any control
-
specific code.


Defining a Composite Control


[AspNetHostingPermission(SecurityAction.Link
Demand,

Level =
AspNetHostingPermissionLevel.Minimal),

DefaultProperty(
"Text"
),

ToolboxData(
"<{0}:PhoneControl
run
at=
\
"server
\
"> </{0}:PhoneControl>"
)]

public

class

PhoneControl : CompositeControl

{


private

Label phoneLabel;


private

TextBox phoneTextBox;


private

RequiredFieldValidator
phoneRequired;


private

RegularExpressionValidator
phoneRegex;


private

Style lab
elStyle;


private

Style phoneStyle;

}


One of the first things you

ll probably notice if you

re
familiar with developing server controls under the ASP.NET 1.x
model is that
the
control inherits from
System.
Web
.UI.
Web
Controls.
CompositeControl
. In
older
versions
of the .NET Framework, you inherited from
the
control and had to
implement the
INamingContainer

interface. These steps
,

along
with other functionality
,

are now taken care of by the
CompositeControl

base class. The
CompositeCont
rol

class also has
an associated control designer that ensures that child controls are
displayed on the design surface.

You

ll also notice several attributes attached to the class
definition. The
y

help define various characteristics of our control:




AspNetHostingPermission
-

This is required to ensure that
the
code that links to the control has the appropriate security
permissions.



DefaultProperty
-

Specifies the property to highlight in the
property browser when the page developer selects the c
ontrol
on the design surface.



ToolboxData
-

Used to specify the format string for the
design environment created when the control is added to a
page.


Define Composite Control Properties

The next step in our composite control is to define the public
pr
operties
that
will be accessible via the property browser in Visual
Studio.NET. Most of the properties are a direct pass through to
properties inherent
to
the controls that make up
the
composite
control example. The
code snippet
belo
w
demonstrates this pass
-
through technique:


[Bindable(
true
),

Category(
"Behavior"
),

DefaultValue(
""
),

Description(
"Validation Group the control
belongs to"
)]

public

string

ValidationGroup

{


get


{



EnsureChildControls();



return

phoneRequired.Validatio
nGroup;


}


set


{



EnsureChildControls();



phoneRequired.ValidationGroup =
value
;



phoneRegex.ValidationGroup =
value
;


}

}


One of the nice features of ASP.NET 2.0 is the ability to
assign a group name to a set of validators to ensure that validation
occurs only for controls in the specified group. This
let
s you
have
multiple separately

validated forms on a single page. In our
property example above, the validation group is assigned to both of
our validation controls during the set operation.

It doesn’t matter
which validator control’s property we returned for the
ValidationGroup

get operation and our arbitrary choice was the
value from the phoneRequired control.

There are also several attributes on the property worth
mentioning:




Bindable
-

Indicates that the property supports data binding.



Category
-

Determines which category this property will be
associated with on the PropertyGrid.



Description
-

Tells the developer using the control what the
property does.


Adding the Controls to
the Controls Collection

The next step in the process is to add the controls that make
up our composite control to the Controls collection
in
the base
CompositeControls

class. This is a fairly straightforward process
and simply involves initializing our

controls, setting any default
properties
,

and then u
s
ing the Add method to add them to the
controls collection by overriding the
CreateChildControls()

method.


protected

override

void

CreateChildControls()

{


Controls.Clear();



phoneLabel =
new

Labe
l();


phoneLabel.ID =
"phoneLabel"
;


Controls.Add(phoneLabel);



phoneTextBox =
new

TextBox();


phoneTextBox.ID =
"phoneText"
;


phoneTextBox.Attributes.Add(
"onblur"
,
"formatPhone(this)"
);


Controls.Add(phoneTextBox);



phoneRequired =
new

RequiredFieldVali
dator();


phoneRequired.ID =
"phoneRequired"
;


phoneRequired.ControlToValidate =
phoneTextBox.ID;


phoneRequired.Text =
"*"
;


Controls.Add(phoneRequired);



phoneRegex =
new

RegularExpressionValidator();


phoneRegex.ID =
"phoneRegex"
;


phoneRegex.ControlTo
Validate =
phoneTextBox.ID;


phoneRegex.Text =
"*"
;


phoneRegex.ValidationExpression =
@"(((
\
(
\
d{3}
\
) ?)|(
\
d{3}
-
))?
\
d{3}
-
\
d{4})|
\
d{10}|
\
d{7}"
;


Controls.Add(phoneRegex);

}


Define Style Properties

Defining and using styles under ASP.NET 1.x took a
consid
erable amount of work. Simply exposing the Style property
of the controls
in the composite control didn’t work as expected,
and though you

d see the Style settings displayed in the property
grid, setting them did
n

t translate into runtime functi
onality. One
of the reasons for this is that the ASPX page didn’t have a location
to store the values of the style settings. This has been remedied in
2.0 and multiple style properties can be exposed and recorded in
the ASPX page to allow styles to
be set
easily
at design time. Our
property for each style
sheet resembles any of the other properties
we

ve created. The way ASP.NET handles the Style properties can
be viewed by looking at the HTML view in Visual Studio.NET.


<
cc1
:
PhoneControl

ID
="Phon
eControl1"

runat
="server"

LabelText
="Phone"

FormatMessage
="Invalid Phone Number"

RequiredMessage
="Phone is Required"

>


<
LabelStyle

BackColor
="Red"

ForeColor
="White"

/>

</
cc1
:
PhoneControl
>


In this example we

ve set a specific style for the Label

by
setting the
LabelStyle

property in the property grid. This was
translated into the
LableStyle

element as a sub
-
element of the
phone control itself.


Creating Custom Functionality

Although creating a composite control in and of itself
provides a cons
iderable benefit in terms of code reuse and faster
development, it

s always nice to provide some additional
functionality to enhance the control

s usefulness.
In
our example,
our control
is going to be able
to
automatically put the phone
number that

s entered into a standard format.

To
do
this, we

re
going to render some JavaScript on the client that will be called
when our control loses focus to provide the formatting.


private

void

Ren
derJavaScript()

{

if(!Page.ClientScript.

IsClientScriptBlockRegistered(this.GetTyp
e(),
"formatPhone")

{


StringBuilder sb =
new

StringBuilder();


sb.Append(
"function
formatPhone(phoneTextBox){
\
r
\
n"
);


sb.Append(
"
\
tvar text =
phoneTextBox.value;
\
r
\
n"
);


sb.
Append(
"
\
ttext =
text.replace(/
\
\
D+/g,
\
"
\
");
\
r
\
n"
);


sb.Append(
"
\
tif(text.length ==
10){
\
r
\
n"
);


sb.Append(
"
\
t
\
t phoneTextBox.value =
\
"(
\
" + text.substr(0,3) +
\
")
\
" +
text.substr(3,3) +
\
"
-
\
" +
text.substr(6,4);
\
r
\
n"
);


sb.Append(
"
\
t}
\
r
\
n"
);


sb.Append(
"
\
tif(text.length ==
7){
\
r
\
n"
);


sb.Append(
"
\
t
\
t phoneTextBox.value =
text.substr(0,3) +
\
"
-
\
" +
text.substr(3,4);
\
r
\
n"
);


sb.Append(
"
\
t}
\
r
\
n"
);


sb.Append(
"}
\
r
\
n"
);


string

script = sb.ToString();


Page.ClientScript.RegisterClientScrip
tBlock(this.GetType(
),
"formatPhone"
,
script,
true
);

}

}


The call to this method to render the JavaScript occurs during
the
OnPreRender

event. The necessary control attribute to call the
JavaScript function when the textbox loses focus was rendered in
our
CreateChildControls
()

method.

One thing to note for those
who
have developed server
controls under the 1.x paradigm is the
ClientScript

property of the
Page object. The
ClientScript

property is an object of
the
ClientScriptManager

type
and should be
used

instead of the
Page.RegisterClientScriptBlock()

method
that’s

been deprecated. It has a few new arguments including asking for a
type in addition to the key field to assist in matching up the client
script to the control as well as the ability to

have the

method
automatically output the script tags by setting the
addScriptTags

argument to true,
as shown in
our
example.


Override the Render Method

The last step in the control creation is to override the render
method to provide the for
matting for our control on the page. In
our
example, we render the multiple controls that make up our
composite control
in a table to be sure the way the control gets
rendered is consistent in any page that may use
it
. This is also the
location where we render any Style attributes that are set at design
time by the page developer.


protected

override

void

Render(HtmlTextWriter writer)

{


AddAttributesToRender(writer);



writer.AddAttribute(HtmlTextWriterAttribu
te.Cellpadding,
"1"
,
false
)
;


writer.AddAttribute(HtmlTextWriterAttribu
te.Border,
"0"
,
false
);


writer.RenderBeginTag(HtmlTextWriterTag.T
able);



if

(labelStyle !=
null
)


{

phoneLabel.ApplyStyle(labelStyle);


}



if

(phoneStyle !=
null
)


{

phoneTextBox.ApplyStyle(phoneStyle);


}



w
riter.RenderBeginTag(HtmlTextWriterTag.T
r);


writer.RenderBeginTag(HtmlTextWriterTag.T
d);


phoneLabel.RenderControl(writer);


writer.RenderEndTag();


writer.RenderBeginTag(HtmlTextWriterTag.T
d);


writer.Write(
"&nbsp;"
);


writer.RenderEndTag();


writer.Rend
erBeginTag(HtmlTextWriterTag.T
d);


phoneTextBox.RenderControl(writer);


phoneRequired.RenderControl(writer);


phoneRegex.RenderControl(writer);


writer.RenderEndTag();


writer.RenderEndTag();


writer.RenderEndTag();

}


Building and Using the Control

The

last step in the process is to add our created control to the
toolbox so
we can add it to our page by using drag
-
and
-
drop. The
easiest way to do this is to open the toolbox panel, right
-
click on
add/remove items and then browse to the bin director
y containing
the assembly for the server control. This will add our control to the
toolbox and from there it can be added to a
Web

form like any of
the built
-
in controls. After adding a control to the toolbox we can
create a
Web

project. With th
e
Web

project
created, drag
-
and
-
drop
the phone control from the toolbox to a
Web

form.
Let’s also add a
validation summary control and a submit button.

Start the
Web

project and enter
the area code and phone
number without any symbols

or formatting. Notice how the
number neatly formats itself when you move off of it provided it
has either
seven
or 10 digits. If it doesn’t, formatting doesn’t occur
and the form won’t submit due to our regular expression validator.
If you

ve set the

Required

property to true, the form will prevent
you from submitting a blank number
.


Summary

This fairly simple example demonstrates how easy it is to
begin creating custom server controls
that
can be used to provide
common functionality

for your applications. It demonstrates how
to create a composite control, apply styles
,

and add custom
functionality. Spending the little bit
extra time to develop a custom
control can result in productivity gains for developers and ease
making global
changes to the front end by having a single control
to maintain.

There were several changes to the ASP.NET server control
model that improve
on the server control object model
and
ma
d
e
control development easier. The list below highlights some of the
notable

ones
:




ControlState
-

This is an alternat
e way for developers to
persist control properties in the page without using
ViewState. The advantage is that
while developer
can turned
off
ViewState
at the page level
, Contro
lState can’t so the
control developer can be assured that data that has to be
persisted for proper control function can’t be turned off.



ValidationGroup
-

Allows a group of controls to be
validated together so
the developer can enforce validation on
a subset of controls on a page.



CompositeControl
-

A new class that can be inherited from
by control developers that eases the development and
creation of composite server controls.



Styles
-

Working with CSS Styles has been made easier
by
being able

to expose Style
properties in

the property grid and
have those changes created and stored in the ASPX page at
design time.


Control Source

using

System;

using

System.Text;

using

System.Data;

using

System.Configuration;

using

System.
Web
;

using

System.
Web
.Se
curity;

using

System.
Web
.UI;

using

System.
Web
.UI.
Web
Controls;

using

System.
Web
.UI.
Web
Controls.
Web
Parts;

using

System.
Web
.UI.HtmlControls;

using

System.ComponentModel;

using

System.Security.Permissions;


namespace

ServerControlSample.Co
mpositeControls

{


///

<summary>


///

This is a phone server control tht
demonstrates


///

how to create a Composite Server
control


///

</summary>


[AspNetHostingPermission(SecurityAction.L
inkDemand,



Level =
AspNetHostingPermissionLevel.Minimal),


Defa
ultProperty(
"Text"
),


ToolboxData(
"<{0}:PhoneControl
runat=
\
"server
\
"> </{0}:PhoneControl>"
)]


public

class

PhoneControl :
CompositeControl


{



private

Label phoneLabel;



private

TextBox phoneTextBox;



private

RequiredFieldValidator
phoneRequired;



pri
vate

RegularExpressionValidator
phoneRegex;



private

Style labelStyle;



private

Style phoneStyle;





[Bindable(
true
),



Category(
"Appearance"
),



DefaultValue(
""
),



Description(
"Text to display on the
label"
)]



public

string

LabelText



{








get





{





EnsureChildControls();





return

phoneLabel.Text;




}




set





{





EnsureChildControls();





phoneLabel.Text =
value
;




}




}




[Bindable(
true
),



Category(
"Appearance"
),



DefaultValue(
""
),



Description(
"Phone number for
textbox"
)



]



public

string

Text



{





get




{





EnsureChildControls();





return

phoneTextBox.Text;




}




set




{





EnsureChildControls();





phoneTextBox.Text =
value
;




}




}




[Bindable(
true
),



Category(
"Behavior"
),



DefaultValue(
true
),



Descrip
tion(
"Is this field
Required?"
)



]



public

bool

Required



{





get




{





EnsureChildControls();





return

phoneRequired.Enabled;




}




set




{





EnsureChildControls();





phoneRequired.Enabled =
value
;




}




}




[Bindable(
true
),



Category
(
"Behavior"
),



DefaultValue(
""
),



Description(
"Message to display if
textbox is left blank"
)



]



public

string

RequiredMessage



{





get




{





EnsureChildControls();





return

phoneRequired.ErrorMessage;




}




set




{





EnsureChildControls()
;





phoneRequired.ErrorMessage =
value
;




}




}




[Bindable(
true
),



Category(
"Behavior"
),



DefaultValue(
""
),



Description(
"Validation Group the
control belongs to"
)



]



public

string

ValidationGroup



{





get




{





EnsureChildControls();





return

phoneRequired.ValidationGroup;




}




set




{





EnsureChildControls();





phoneRequired.ValidationGroup
=
value
;





phoneRegex.ValidationGroup =
value
;




}




}





[Bindable(
true
),



Category(
"Behavior"
),



DefaultValue(
""
),



Description(
"
Text to display if the
phone number is not in the correct format"
)



]



public

string

FormatMessage



{





get




{





EnsureChildControls();





return

phoneRegex.ErrorMessage;




}




set




{





EnsureChildControls();





phoneRegex.ErrorMessage =
v
alue
;




}




}




#region

Typed Style properties



[



Category(
"Styles"
),



DefaultValue(
null
),



DesignerSerializationVisibility(




DesignerSerializationVisibility.Content),



PersistenceMode(PersistenceMode.InnerProp
erty),



Description(




"The stron
gly typed style for the
Button child control."
)



]



public

Style LabelStyle



{




get




{





if

(labelStyle ==
null
)





{






labelStyle =
new

Style();





}





return

labelStyle;









}



}




[



Category(
"Styles"
),



DefaultValue(
null
),



Des
ignerSerializationVisibility(




DesignerSerializationVisibility.Content),



PersistenceMode(PersistenceMode.InnerProp
erty),



Description(




"The strongly typed style for the
TextBox child control."
)



]



public

Style PhoneTextBoxStyle



{




get




{





if

(phoneStyle ==
null
)





{






phoneStyle =
new

Style();





}





return

phoneStyle;




}



}



#endregion






public

PhoneControl()



{






}




protected

override

void

OnPreRender(EventArgs e)



{




RenderJavaScript();



}



protected

override

void

CreateChildControls()



{




Controls.Clear();





phoneLabel =
new

Label();




phoneLabel.ID =
"phoneLabel"
;




Controls.Add(phoneLabel);





phoneTextBox =
new

TextBox();




phoneTextBox.ID =
"phoneText"
;




phoneTextBox.Attributes.Add(
"onblur"
,
"f
ormatPhone(this)"
);




Controls.Add(phoneTextBox);





phoneRequired =
new

RequiredFieldValidator();




phoneRequired.ID =
"phoneRequired"
;




phoneRequired.ControlToValidate =
phoneTextBox.ID;




phoneRequired.Text =
"*"
;




Controls.Add(phoneRequired);





phoneRegex =
new

RegularExpressionValidator();




phoneRegex.ID =
"phoneRegex"
;




phoneRegex.ControlToValidate =
phoneTextBox.ID;




phoneRegex.Text =
"*"
;




phoneRegex.ValidationExpression =
@"(((
\
(
\
d{3}
\
) ?)|(
\
d{3}
-
))?
\
d{3}
-
\
d{4})|
\
d{10}|
\
d{7}"
;




Controls.Add(phoneRegex);
//





}




protected

override

void

Render(HtmlTextWriter writer)



{




AddAttributesToRender(writer);





writer.AddAttribute(HtmlTextWriterAttribu
te.Cellpadding,
"1"
,
false
);




writer.AddAttribute(HtmlTextWriterAttribu
te.Borde
r,
"0"
,
false
);




writer.RenderBeginTag(HtmlTextWriterTag.T
able);





if

(labelStyle !=
null
)




{





phoneLabel.ApplyStyle(labelStyle);




}





if

(phoneStyle !=
null
)




{





phoneTextBox.ApplyStyle(phoneStyle);




}





writer.RenderBeginTag(HtmlTex
tWriterTag.T
r);




writer.RenderBeginTag(HtmlTextWriterTag.T
d);




phoneLabel.RenderControl(writer);




writer.RenderEndTag();




writer.RenderBeginTag(HtmlTextWriterTag.T
d);




writer.Write(
"&nbsp;"
);




writer.RenderEndTag();




writer.RenderBeginTag(Htm
lTextWriterTag.T
d);




phoneTextBox.RenderControl(writer);




phoneRequired.RenderControl(writer);




phoneRegex.RenderControl(writer);




writer.RenderEndTag();




writer.RenderEndTag();




writer.RenderEndTag();



}




private

void

RenderJavaScript()



{




if

(!Page.ClientScript.IsClientScriptBlockRegis
tered(
this
.GetType(),
"formatPhone"
))




{





StringBuilder sb =
new

StringBuilder();





sb.Append(
"function
formatPhone(phoneTextBox){
\
r
\
n"
);





sb.Append(
"
\
tvar text =
phoneTextBox.value;
\
r
\
n"
);





sb.Append(
"
\
ttext =
text.replace(/
\
\
D+/g,
\
"
\
");
\
r
\
n"
);





sb.Append(
"
\
tif(text.length
== 10){
\
r
\
n"
);





sb.Append(
"
\
t
\
t
phoneTextBox.value =
\
"(
\
" +
text.substr(0,3) +
\
")
\
" + text.substr(3,3)
+
\
"
-
\
" + text.substr(6,4);
\
r
\
n"
);





sb.Append(
"
\
t}
\
r
\
n"
)
;





sb.Append(
"
\
tif(text.length
== 7){
\
r
\
n"
);





sb.Append(
"
\
t
\
t
phoneTextBox.value = text.substr(0,3) +
\
"
-
\
" + text.substr(3,4);
\
r
\
n"
);





sb.Append(
"
\
t}
\
r
\
n"
);





sb.Append(
"}
\
r
\
n"
);





string

script =
sb.ToString();





Page.ClientScript.Register
ClientScriptBlo
ck(
this
.GetType(),
"formatPhone"
, script,
true
);




}



}



}

}



Default.aspx

<%@ Page Language="C#"
AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default" %>


<%@ Register
Assembly="ServerControlSample.CompositeContr
ols"
Na
mespace="ServerControlSample.CompositeCont
rols"


TagPrefix="cc1" %>


<!DOCTYPE html PUBLIC "
-
//W3C//DTD XHTML
1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dt
d">


<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">


<title>Untitle
d Page</title>

</head>

<body>


<form id="form1" runat="server">


<div>


<span style="font
-
size: 16pt">Server
Control Test Sample:</span><br />


<br />


<cc1:PhoneControl ID="PhoneControl1"
runat="server" LabelText="Phone"
FormatM
essage="Invalid Phone Number"
RequiredMessage="Phone is Required" >


<LabelStyle BackColor="Red"
ForeColor="White" />


</cc1:PhoneControl><br />


<asp:Button ID="submitButton"
runat="server" Text="Submit"
OnClick="submitButton_Clic
k" />


<asp:Label ID="validLabel"
runat="server" Text="Phone Number is valid"
Visible="False"></asp:Label><br />


<asp:ValidationSummary
ID="ValidationSummary1" runat="server" />


</div>


</form>

</body>

</html>



using

Syst
em;

using

System.Data;

using

System.Configuration;

using

System.
Web
;

using

System.
Web
.Security;

using

System.
Web
.UI;

using

System.
Web
.UI.
Web
Controls;

using

System.
Web
.UI.
Web
Controls.
Web
Parts;

using

System.
Web
.UI.HtmlControls;


pu
blic

partial
class

_Default :
System.
Web
.UI.Page

{


protected

void

Page_Load(
object

sender,
EventArgs e)


{



}



protected

void

submitButton_Click(
object

sender, EventArgs e)


{



validLabel.Visible = Page.IsValid;


}

}