This week, we'll tackle a multi-functional, totally flexible control that you can use in your own Web projects
Technologies Used:
ASP.NET
C#
XML
Object-oriented programming (OOP) concepts
Custom server control programming model
JavaScript (client-side)
In-line code documentation
Application tracing (integrated debugging)
Level of Difficulty
Advanced
I’ve gotten lots of positive feedback from the last tutorial we did –
developing
a composite custom server control to import stock data from the NASDAQ. I’ve
been delighted to hear from the hundreds of you that downloaded the control and
are using it in your own projects. I’m also very happy to see some of the
enhancements and modifications that you’ve made to the control to make it
better. That’s what programming is all about – constantly expanding upon someone
else’s good idea.
This week, we’re going to examine more aspects of developing reusable custom
controls, with a few new twists. In particular, we’re going to look at managing
and persisting and state information through the life of a control, providing
greater customization for page developers, and documenting your code. For this
tutorial, we’re going to be developing a custom slideshow control, to loop
through a collection of images and captions. This is perfect for many
situations, ranging from a gallery of professional news images, a sampler to
demo your work to clients, or simply a personal photo scrapbook.
The best thing about this control is the freedom-of-development is gives to those who would include it within their Web sites. It gets its data by reading from an XML document and inserting the XML nodes into a jagged array. Many components that shipped with ASP 3.0 that relied on external XML data stores forced a developer to have to conform to a set XML schema – a set number of nodes, attributes, and other elements.
Here's a sample of the KUAM Slideshow control in action (I'm using KUAM reporters past and present as an example, set to reload every four seconds):
However, I’ve built-in some added features that
allow our control to be able to read from practically any XML structure, so the
developer that uses this control is free to develop their own XML structure,
being as detailed or conservative as she likes.
Here's the code in our developed class:
Within the ReadSlides method, we create FileStream and StreamReader objects and
read-in the data by calling the value of our public DataFile property, which
returns the full path of the user-defined XML file on disk. It then reads the
XML data into a DataSet object. Because a DataSet returns a DataTable, we
extract an array of the number of rows in the DataSet, letting us iterate
through the data.
This provides better customization and flexibility for different situations, and
therefore makes the control more applicable in different situations. While it’s
called the “KUAM Slide Show” custom server control, I’m going to show you how
this flexibility makes it useful in other situations.
If you’re one of the many people who also follows “Dev Dungeon” week-to-week and
diligently goes through each tutorial, you may have also noticed that I haven’t
addressed a key feature of the C# language: its self-documenting properties. A
viewer e-mail asked me to explain this, so in today’s lesson we’re also going to
not only author a great control, we’re also going to document it.
Beneath the Hood: Planning Out the Control
Our custom server control essentially functions like an automated
Web-based slideshow. It populates the slides with data contained within an
external XML document, and reloads itself on an interval, displaying a new image
and caption after reloading. We want this control to do the following:
1. Allow a developer use any custom XML data file she wishes, not having to
conform to the XML structure demonstrated here.
2. Provide customization by allowing a developer to set the time interval
between page reloads.
3. Automatically reload the page, saving the current data values to ViewState
and then calling those same saved values from ViewState upon reload.
4. Manipulate the .NET Framework’s state information so that the data and
counter for the slides are stepped through, and reset the counter once the
slideshow has reached the last slide in the collection.
5. Format the HTML in a manner compatible with the end user’s Web browser (i.e.,
CSS vs. non-CSS compliant browsers).
Sample XML Data File
I've included a sample data file here...the same used in the control
above. As you can seem it's a very simple structure, and uses CDATA
sections within the nodes to make sure the data included here (which is HTML
code) isn't parsed as XML and is written out literally to the page. Again,
your own XML structure may be similar, use different element names, or be more
simplistic.
Plan Your Code, Code Your Plan
As with most classes developed with the .NET Framework, the necessary
namespaces are imported in the first few lines of code, prefaced by the “using”
keyword. Astute developers will notice that we’re importing the
System.Diagnostics namespace, to allow for tracing statements to be included in
our control. We’ll see how these are used in a minute.
You’ll also notice throughout the code (more specifically, right before a data
member like a class-level variable, property or method is defined) in-line
documentation is noted with three leading slashes (“///”). We’ll skip over them
for now and just describe the main code for the class.
The KUAMSlideShow class defines four private data members: _datafile, _refreshrate,
_currentslide and _numberofslides. The first two are used to populate the public
properties DataFile and RefreshRate, defining the XML file used to provide the
data for the slides, and the number of seconds in between page refreshes,
respectively. _currentslide and _numberofslides are used as counter variables to
populate the page’s ViewState so we know to advance from one slide to the next,
and then reset to the first slide when the control reaches the end of the
collection.
The private helper method ReadSlides is the main gateway into the control. When
called, it returns a jagged string array (string[][]), which is an array of a
variable number of dimensions. In turn, each element within that array also has
a variable number of dimensions. Astute programmers at this point may wonder why
I didn’t opt to use another .NET collection data type, like an ArrayList,
Hashtable, SortedList, or even just a normal Array. Well first, we’re going to
need to manually populate our elements with key/value pairs, so strike the
ArrayList and Array, because they both use their own scheme of assigning unique
keys to their elements. Also, to ensure the order in which slides are displayed
is consistent with the order the nodes appear in the XML document, we can’t rely
on a Hashtable or SortedList, because each uses its own internal indexing scheme
that often causes sorts elements out of the order they you’ll want.
So although using a jagged array is a bit more work, you’ll see its well worth
the effort.
Still within ReadSlides, we call another helper method for each element in our
jagged array – GetArrayContents – passing three parameters as arguments, namely
the specific row number currently being worked on, the name of the DataTable
object, and an array of DataRows. GetArrayContents is pretty simple – it
populates a string array based on the arguments passed and manually invokes the
SetValue method on an array it creates, and returns the populaterd array to the
calling code. This populates the main jagged string array.
So now that the elements of the jagged string array are created and populated
data for our slideshow, we now need to ensure that our counter values (_currentslide
and _numberofslides) are saved into and loaded from view state before and after
each page refresh, respectively. To do this, we manually can manipulate the
ViewState values by overriding the base Control class’ implementation of the
LoadViewState and SaveViewState methods.
In LoadViewState, we cast the savedState argument to an object array and then
cast each element in that array to the native data type of each private data
member (string and integer values). SaveViewState does the exact opposite,
populating each element of an object array with the value of each private data
member after determining the total number of slides. This latter step is
achieved by calling the ReadSlides method again and extracting array’s Length
property.
The trick to informing the control when to advance to the slideshow and when to
reset it is done in a simple if...then statement, as the _currentslide variable
is evaluated and incremented by 1 as long as the current value doesn’t exceed
the total number of slides (makes sense, huh?). In the case that the current
slide variable is larger than _numberofslides, _currentslides is set to 0, and
the slide show will start from the beginning.
Integrated Debugging
There’s a lot going on in the code, as values are extracted, examined, and
saved/called, and you’ll notice trace statements throughout some of the methods
to highlight the value at runtime. While they don’t affect the code from
executing in any way, they’re meant to help you see the values behaving in
action. They are strictly optional, but if you’d like to see the values
incremented as they happen, turn on the Trace attribute in your ASP.NET Page
declaration, as follows:
<% @ Page Language=“C#” Trace=“True” %>
Developing the Control’s UI
Now for the user interface. The Render method of the base Control class is
overridden, and we make sure to call the VerifyRenderingInServerForm method.
This guarantees that the control must be placed within server-side <FORM> tags
to work (otherwise, an exception will be raised).
A client-side JavaScript function that automatically reloads the page at a set
interval is written out to the string variable clientCode. The interval in
question is determined by our RefreshRate public property, which the page
developer may set either programmatically or declaratively. The page is
refreshed by obtaining an object reference to page’s server-side form, and then
calling the object’s submit() method, submitting the form and forcing a refresh
of the parent page the control sits on.
The code for the slideshow’s UI is then written out. This part is completely at
your disposal, and if you want to use a different layout (which you probably
will), you’ll need to rewrite this portion of the code). I’ve used a simple
if...then statement to determine whether the slides themselves are displayed, or
a non-obtrusive “End of show” message.
Notice how in the for... loop the code ‘j=j+slides[i].Length’ is used to
increment the loop counter. This is necessary because keeping in mind that we’re
iterating through a jagged array (again, containing a variable number of
elements within a variable number of elements), using the normal ‘j++’ syntax to
increment the counter by 1 would only do so within the context of the current
element, displaying multiple instances of your slide on the same page.
We could hard-code this, but again, this gives the class the ability to
automatically read from a custom XML schema and run-through the data.
I’ve developed a simple UI that resembles the one used on KUAM.COM to show our
“Meet the KUAM News Team” page. In doing so, you might think this is an awful
lot of code to write. And you’re right...it is. However, using the various
enumerations of the HtmlTextWriter class is better because is allows the HTML
produced by the control to automatically degrade if the user’s Web browser isn’t
Internet Explorer 5+ or Netscape 6+ - specifically, writing out HTML within
inline style attributes:
<tr>
<td style="font-family:Verdana,Arial,Tahoma,sans
serif;font-size:28;color:#000080;font-weight:bold;">
Jason Salas
</td>
</tr>
<tr>
<td style="font-family:Arial;color:#FFFFFF;font-weight:bold;font-size:20;">
KUAM.COM Web Development
Manager/Late Edition Anchor
</td>
</tr>
</table>
If the user is running a legacy browser, the code will be rendered as follows:
<table>
<tr>
<td>
<font face=”Verdana” size=” 28”
color=”#000080”>
<b>
Jason Salas
</b>
</font>
</td>
</tr>
<tr>
<td>
<font face=”Arial” color=”#FFFFFF”
size=”20”>
<b>
KUAM.COM Web Development Manager/Late Edition Anchor
</b>
</font>
</td>
</tr>
</table>
This key feature ensures that our control will render safely on browsers that
support cascading stylesheets, and those that don’t.
Compiling Your Control
Now that we've got the code written, we need only to compile the class to a .DLL
and save it in our Web application's \BIN subdirectory to use as a component.
You can either roll it up automatically from Visual Studio .NET, or launch the
.NET Framework's C# compiler and enter the following at the command prompt:
csc /t:library /out:SlideShow.dll /doc:slideshowdocumentation.xml
slideshow.cs
Using /doc to Document the Source Code
Notice the use of the /doc switch in the compilation command (if you're using
Visual Studio .NET generating documentation is a command you can enable).
The size and scoop of the class that makes up our custom control is such that
documenting it is wise. Several months down the road if we need to review the
data members within the class or make changes to it, documenting it provides a
comfortable means of doing so without having to read through raw source code,
which can be painful and time-consuming. By including the /doc switch when
running the C# compiler, the .NET Framework looks for the in-line comments
contained with out class’ code and saves them to a separate XML file as our
program documentation. You can find the complete tutorial for using the
recommended XML comments for documenting your C# code at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkXMLDocumentationTutorial.asp.
Since the result is properly-formatted XML data, we can then format it by
binding it to an XSLT stylesheet and have it presented nicely. For this example,
I’m using the freely-available XSLT process developed by Microsoft’s Anders
Hejlsberg, the creator of C#. Anders created a XSL stylesheet and CSS definition
to have the source output nicely, which you can download at
http://dotnet.jku.at/book/docview/.
You can also download the freeware NDoc from SourceForge (http://ndoc.sourceforge.net/download.html)
to apply MSDN-style formatting to your documentation file.
Here's a look at the documentation as XML...
...and then as formatted XML when the XSLT is applied. Pretty sweet!
Using the Control
Because the nature of this control is to be self-reloading and forces the entire
parent page containing it to refresh, it's obvious that the best to stick this
would not be in the middle of your homepage. So, to work around this, you
have two options that are pretty easy to do. First, save the
SlideShow.dll component to your Web application's \BIN subdirectory on your
Web server and then add the control into an ASP.NET page (.ASPX).
You can add the control to your parent page
declaratively, like so:
<%@ Register TagPrefix="kuamcontrols"
Namespace="techtalk.guamsmrdotcom.customcontrols" Assembly="SlideShow" %>
... more page content...
<kuamcontrols:KUAMSlideShow id="mySlideshow" DataFile="slideshowcontents.xml"
RefreshRate="9" runat="server"/>
....or programmatically, like so:
public void Page_Load(object sender, EventArgs e)
{
KUAMSlideShow slides = new KUAMSlideShow();
slides.RefreshRate = 9;
slides.DataFile = "slideshowcontents.xml";
}
Then, to have the page displayed, you can:
Enjoy…and happy programming!
</jason>