IntroToASP.NET
IntroToASP.NET
NET
using Visual Basic .NET
Peter McMahon
peter@dotnet.za.net
1. Introduction
2. Installation
3. A Quick Introduction to Web Programming and VB .NET
4. Your first ASP .NET Web Form
5. Programming with common ASP .NET Web and HTML Controls
6. Moving between Web Forms and Persisting State
7. User Input Validation
8. Data Programming with ADO .NET (Incomplete)
9. XML Programming with the XML Base Classes
10. File IO
11. Sending e-mail using ASP .NET
12. Using the advanced Web Controls (Not started)
13. Creating your own User Controls
14. Web Services (Incomplete)
15. Caching
Chapter 0
Author’s Note
This book started as a project for California-based publishers Apress in December
2000. I began writing in January 2001 with the wildly optimistic goal of completing
the book by August. Needless to say I didn’t even come close. In the following
months the market for books targeting new .NET developers became increasingly
competitive, and unfortunately Apress were forced to cancel the project.
What you see before you is the state of the book as it was circa April 2002. Some
of the chapters have gone through the technical review and copy editing phases,
others have not, so the quality within the book varies somewhat, and is far from
professional. Some chapters are incomplete, and some don’t make an appearance at
all. However, I figured I’d might as well compile what I have and release it rather
than continue to let the writing rot on my hard drive. Hopefully this e-book draft will
be useful to people beginning with .NET, Web Forms development, or both.
Take note: this book was written before the release of .NET v1.1 or v2.0, and uses
Visual Studio .NET 2002. Fortunately not much changed between Visual Studio .NET
2002 and Visual Studio .NET 2003, so although all the screenshots in the book might
look at little different to what you’ll see in the .NET v1.1 release version (and indeed
Visual Studio 2005), most of the code and concepts should be very similar, if not the
same.
I’d like to thank everyone involved in the project during 2001 and 2002. Gary,
Karen, Dan, Nicolle, Grace, Tracy and everyone else at Apress, thanks for your
efforts – I learned a lot from you all. And to everyone who read early chapter drafts
and gave me feedback, I really appreciate your words of advice and encouragement!
In particular I’d like to thank Mark Balasundram, Brad Simon, Jared Blanchard, Tom
Pester, David Palmquist and Marc Pienaar.
– Peter McMahon
June 2005
Chapter 1
Introduction
The Internet has grown at an enormous rate over the past few years, and it has almost
certainly affected the lives of most people who use computers. In only a few years,
the Internet has evolved from a platform for publishing “online brochures” to an
entire architecture for developing dynamic, distributed applications. Developing these
applications was previously extremely difficult and tedious. The potential was huge,
but the tools consisted of nothing more than text editors in which applications would
be coded from scratch. This method of writing Web applications required extensive
knowledge of the “plumbing” of the Internet, and there were a very limited number of
languages to use. These factors made Web application programming available only to
dedicated programmers who had the time to invest in learning rudimentary interfaces
and unfamiliar programming languages.
Several projects and products now drastically simplify Web application
development by providing easy-to-use object models and familiar, commonly used
programming languages. Microsoft has been a leader in this field with its Active
Server Pages (ASP) technology. With the latest release, ASP.NET, which is an
integral part of the .NET Framework, Microsoft has attempted to build a technology
that will be very familiar to the millions of Visual Basic programmers. ASP.NET
enables VB programmers to easily apply their Windows application programming
skills to Web application programming without alienating existing ASP
programmers—rather, it makes Web application development drastically simpler for
them, too.
What Is ASP.NET
To fully understand what ASP.NET is, you’ll need some background and a brief
history of Web application programming. The following sections present a short
explanation of server-side programming and a summarized history of server-side
programming methods, which should help you greatly in understanding exactly what
ASP.NET does and why it’s such a groundbreaking technology.
Server-Side Programming
Simply put, server-side programming is programming that is done where the code is
executed on a server. With regard to Web application server-side programming, this
code is executed when a request is received from a visitor to a Web site, and it
generates code that Web browsers understand (HTML) and sends it back to the client
(the Web browser), as illustrated in Figure 1-1.
Insert 0015f0101scrap.tif FPO
CGI
One of the first technologies to be introduced was the Common Gateway Interface
(CGI), which is now built into almost all Web servers. For our purposes, CGI is a
method of obtaining and returning information to and from the Web server (the
official CGI documentation available at
http://hoohoo.ncsa.uiuc.edu/cgi/overview.html offers a more precise definition).
CGI, as its name implies, isn’t a language, but rather an interface to the Web
server. CGI applications can be programmed in a wide variety of languages. Possibly
the most common is Practical Extraction and Report Language (Perl), which is known
throughout the UNIX world. Perl is interpreted and therefore not very fast. It is,
however, a very powerful language, but with that power comes a difficulty level that
is significantly higher than that of Visual Basic. Another common language for CGI
programming in the UNIX world is C, which offers several performance advantages
over Perl, although Perl is definitely more popular than C for CGI application
development. CGI in UNIX is not limited to Perl and C; Python is another popular
choice. In fact, it’s even possible to program CGI applications in UNIX shell script.
Definite advantages of CGI are that it is platform independent and built into most
Web servers. So long as a language with an interpreter with multiple-platform support
(such as Perl) is used, CGI scripts will run with little change over multiple platforms,
such as UNIX and Windows. In Windows, it’s possible to develop CGI executables in
a variety of compiled environments, including Visual C++ and Delphi, which can
access every part of the operating system.
Possibly the greatest pitfall of CGI from a performance point of view in Windows
is that each client request for a CGI “Web page” requires a new process to be created,
which consumes vast amounts of system resources should there be multiple requests.
From the developer’s point of view, CGI applications are hard to maintain and very
difficult to debug. They also require the developer to perform a significant amount of
plumbing, and CGI does not offer many necessary features exposed by other
technologies such as ASP.
ISAPI
IDC
Microsoft’s Internet Database Connector (IDC) technology has been included in IIS
since its inception and is used to provide easy database connectivity for Web pages.
Each page requires two files: a query file and a template file. The query file contains
the database connection information and the SQL query. The template file contains
HTML and tags denoting where specific data from the query file must be inserted.
This method of database connectivity was very easy to use, but it had one major
pitfall in that there was no place to put business logic.
ASP
Microsoft combined the best features of IDC and ISAPI to produce Active Server
Pages (ASP). ASP pages offer the ease of use of IDC by allowing database and
business logic code to be inserted directly into pages using special tags, and they offer
the performance advantages of ISAPI. ASP is very scalable, and this has been proven
time and time again with many large sites relying on ASP, including
Barnesandnoble.com (http://www.bn.com), Fatbrain.com (http://www.fatbrain.com),
and Microsoft’s huge Web properties (MSN at http://www.msn.com, Microsoft at
http://www.microsoft.com, and so on), among many others.
ASP was a huge leap forward and offered a completely new paradigm for
dynamic Web page development when it was introduced. ASP’s unique approach
allowed for dramatic increases in productivity and opened up the exciting area of
dynamic Web page development to many novice programmers who were bewildered
by the complexity of CGI and ISAPI. This was helped largely by the fact that the
primary languages for ASP development are VBScript and JScript, which are very
easy to learn and use, especially because VBScript is a subset of the Visual Basic
language, and JScript, the Microsoft implementation of JavaScript, is a language
commonly used in client-side Web page programming.
ASP’s approach spawned a few very similar technologies, most notably
JavaServer Pages (JSP). ASP, however, isn’t perfect. ASP relies on an interpreted
programming model, which affects its performance. A significant amount of
plumbing is still required for the programmer to perform in many scenarios—for
example, controlling user access to an application. Another problem was exposed
while ASP programmers attempted to collaborate with graphic and page design teams.
Typically, a site’s design is not set in stone and graphic design teams will want to
revise designs and make modifications during the design and development phase of a
site in a corporate environment. If ASP code is used heavily in pages, this can create a
number of complications. Most Web design tools do not recognize ASP code and will
either rip it out or distort it. If the design teams hand-code their HTML, they may
unwittingly remove, replace, or dislocate code on the page. The obvious solution to
this problem is to separate the code from the page, but ASP doesn’t allow for this.
ASP has numerous other shortcomings, and the list in the following section of the
major differences between ASP and ASP.NET details some of the problems and
Microsoft’s solutions to them.
Compiled Environment
One of the most dramatic changes in ASP.NET is that it’s now a fully compiled
environment. Microsoft has implemented this very intelligently and it would be very
easy to dismiss ASP.NET as interpreted because, to the programmer and the end user,
it appears as if ASP.NET works in exactly the same way as ASP: You modify your
ASP.NET page, you refresh the page in the browser, and the changes are reflected.
Nowhere are you required to run a compiler. Compilation actually occurs the first
time a page is requested after it has been modified. This compiled copy is then kept
until the page is modified and requested again. As I’ve already mentioned, this
process is totally transparent to the user.
There’s naturally a significant performance advantage of ASP.NET over ASP
now, in addition to the obligatory scalability advantage of a compiled application. It’s
also important to note that while ASP.NET supports the previously mentioned
“compile on demand” functionality, it’s also possible to precompile ASP.NET
applications into a .NET DLL, which is the method that Visual Studio .NET uses by
default when building Web applications with it.
As its name implies, ASP.NET is an integral part of .NET and the .NET Framework.
All the core pieces of ASP.NET are implemented as .NET Framework classes. More
important, ASP.NET applications have access to the common .NET programming
model and the .NET Framework base classes, which include ADO.NET database
access, XML parsing, and file IO. This helps make programming ASP.NET
applications remarkably similar to programming Win Forms applications or Windows
executable applications in .NET. Another advantage of the .NET platform is that it’s
completely language independent. I discuss .NET and the .NET Framework shortly.
Server Controls
Microsoft has introduced the concept of server controls in ASP.NET to help simplify
Web application development, specifically for Visual Basic programmers who are
used to programmatically accessing controls such as labels and text boxes. However,
this change also greatly affects traditional ASP programmers, who will have to
become accustomed to this new paradigm in Web application programming. Simply
put, server controls allow programmatic access to HTML elements. This is heavily
tied to the next change in ASP.NET.
One of the major elements of plumbing that an ASP programmer must perform is
maintaining state. Windows application programmers take state maintenance for
granted, as it happens automatically. Basically, state maintenance is keeping the
values of fields, such as text boxes, when the page is refreshed. Server controls
maintain their own state, so this is another plumbing task that the programmer no
longer has to perform.
Server controls are designed to be intelligent and take advantage of specific
browser capabilities. Web browsers all have different features and capabilities, and
server controls will generate code according to what the browser requesting the page
can display.
It’s also possible to create your own server controls, which is a great way to
encapsulate your code and increase productivity through code reuse.
Event-Driven Programming
Tightly coupled with the server controls paradigm is event-driven programming for
ASP.NET pages. The introduction of the event-driven programming model in the
Windows programming world revolutionized the way applications were programmed.
The introduction of this model into the Web programming world should have an
equally great effect on productivity and the simplicity of application development.
Server controls expose events, such as Click, for which event handlers can be
programmed. This shields the programmer from having to handle HTTP responses
and requests, although it’s still possible to handle them manually using the respective
traditional ASP intrinsic objects.
ASP provided a special collection object where the programmer could store variables
pertaining to a particular visitor’s session. This was a commonly used feature, but it
had three major shortcomings. First, it wasn’t Web farm–friendly. Session state
variables were stored in the IIS process space, and as such, they were tied to a specific
machine. Thus, if a visitor to a site had a particular piece of information stored in a
session variable on a particular request, and he or she was then sent to another server
on the next request, the ASP code would not be able to retrieve the previously stored
value. Second, a disadvantage of storing session state in the IIS process space is that
should the IIS service have to be restarted, the session information is lost. The third
problem was slightly more subtle, but important nonetheless: ASP kept track of
visitor IDs by using cookies, which aren’t supported by all browsers, or are not
guaranteed to be enabled in all user browsers.
ASP.NET offers solutions for all the problems presented. First, it’s now possible
to store session state in a SQL Server database, which is naturally accessible to all the
servers in a Web farm. Second, it’s possible to store session state out of process,
which alleviates the problem of IIS service restarts. Third, ASP.NET now allows for
cookie-less session state management.
User Input Validation
Web Services
Web Services are a large part of ASP.NET and .NET as a whole. Web Services are
classes, or objects, that expose certain methods and make them accessible over the
Internet. Web Services are based entirely upon open standards such as Simple Object
Access Protocol (SOAP), XML, and HTTP, thus allowing interoperability with “Web
services” on other platforms, such as UNIX, as well as those built using the Visual
Basic 6 SOAP kit. Naturally, the Web Services model in .NET allows for the
consumption of Web Services as well. Web Services should prove to be an
exceptionally useful technology in the areas of business-to-business (B2B)
communication and commerce, and business-to-person (B2P) commerce.
Caching Services
ASP.NET offers two primary caching techniques: data caching and output caching.
Data caching allows programmatic access to a cache store where objects and values
can be stored. Output caching allows caching of the output of specific pages for
specific periods of time. Previously, the functionality of data caching had to be
developed from scratch by the developer, and output caching would also have to be
separately developed and implemented. Naturally, these caching features allow for
greater performance advantages.
Security
Security usually plays a large role in a Web application. In ASP, it was up to the
developer to implement his or her own security mechanisms, especially in the area of
user authorization. ASP.NET features built-in security configuration in its application
settings files. This takes a lot of tedious, yet necessary, work off the hands of the
developer.
Every programming environment is constantly striving for better, more advanced, and
easier debugging tools and methods. Web application developers have always had to
deal with very rudimentary tools, with functionality and usability far behind that of
debugging tools for Windows applications developers. ASP.NET and Visual Studio
.NET aim to solve some of these developer woes by providing more advanced
debugging tools. Tracing allows developers to inspect and gather important
application performance information. Tracing also allows developers to check
whether control flow is executing correctly. Developers can now make use of the
Output window in VS .NET, a commonly used tool familiar to Visual Basic
developers. ASP.NET also provides more detailed and useful error messages, in
addition to a .NET stack dump that details the exact steps of the compiler in relation
to the classes being used and the methods being executed.
Deployment
What Is VB .NET?
Microsoft has recognized that Web application development is currently playing and
will continue to play an important role in the lives of developers. They also recognize
that many of the 7-million-plus current Visual Basic developers aren’t prepared to
dabble with Web development due to the time investment that it takes to get up to
speed (prior to ASP.NET). One of the primary goals of ASP.NET is to make Web
application development as similar to Windows application development as possible
without sacrificing power or performance, while still making the skills that ASP
developers had learned pertinent. Naturally, Visual Basic .NET is a very important
part of this challenge, as the primary audience to help start Web development was
Visual Basic developers. Another factor was that the primary language used in ASP
development was VBScript, a subset of the Visual Basic language, and these
ASP/VBScript programmers could not be alienated.
VB .NET is the successor of the Visual Basic language. It is, as its name implies,
a .NET Common Language Specification (CLS)–compliant language. There have
been some fairly major changes from Visual Basic to VB .NET, most notably the
inclusion of full OOP support (a prerequisite of the CLS). When you refer to Visual
Basic, people normally assume that you’re talking about the development
environment as well as the actual language. VB .NET is a stand-alone .NET language,
and it’s shipped with the .NET Framework SDK. This only includes the compiler, not
the IDE. However, VB .NET can be run as part of a new global development
environment, Visual Studio .NET, which is a commercial product.
VB .NET As a Language
As previously mentioned, VB .NET is a .NET CLS-compliant language. It’s
distributed as part of the .NET Framework, and no additional downloads are
necessary. There are several significant differences between VB .NET and previous
versions of Visual Basic. VB .NET is fully typed and has full support for inheritance,
polymorphism, and encapsulation, among other important OOP concepts. Here’s a
brief list of some of the other changes implemented in VB .NET:
* No more variants
* Class oriented
* Try..Catch..Finally error handling
* Support for interfaces
* Support for delegates
* Reliance on the .NET Framework
The bane of all reasonably knowledgeable programmers, the Variant data type,
has finally been removed from the Visual Basic language. This is in accordance with
the full type-safety goal of VB .NET. Although not specifically required by the
compiler, VB .NET also encourages all type conversions to be performed explicitly,
which makes type-related errors much easier to detect, especially for less experienced
programmers.
Classes are a very important part of VB .NET. Inheritance works by starting off
with a class and modifying it. Thus, for inheritance to be properly supported in VB
.NET, classes must play a pivotal role, which they do. All applications are
implemented as classes. All Web Forms are classes. All Win Forms are classes. All
Web Services are classes. All user controls are classes. Most functionality—file IO,
data access, and so on—in the .NET languages is implemented through the base
classes. It’s very important, therefore, to understand what a class is, and this will be
dealt with briefly in the next chapter.
VB .NET finally gives Visual Basic programmers what many have been
requesting for a long time: powerful error handling structures (the .NET Framework
also provides some powerful exception handling classes). The Try..Catch..Finally
structure allows precise handling of specific errors in particular pieces of code. Gone
are the days of the awful “On Error Goto Label” structure (although it still does exist
for the purposes of backward compatibility).
Again, in line with OOP compliance, VB .NET supports interfaces and allows for
interfaces to be created and implemented. Support for delegates has also been added.
Possibly the most dramatic change to the Visual Basic language, and the one that
is most likely to affect programmers the most, is the reliance of VB .NET on the .NET
Framework. Most aspects of the language have changed because of this—from OOP
support, to data access, to debugging, to compilation, to deployment. In Win Forms
applications, all controls are .NET Framework classes. In Web Forms applications, all
server controls are .NET Framework classes. In fact, both types of applications are
inherited .NET Framework classes themselves. ADO’s successor, ADO.NET, which
provides data access, is implemented as .NET Framework classes. XML parsing is
implemented as .NET Framework classes. Even Win32 API functions are
implemented as .NET Framework interop classes. Thus, it’s extremely important that
all developers planning to use ASP.NET, or any other .NET application model, have a
thorough understanding of the .NET Framework.
VB .NET IDE
In .NET, all languages share a common Visual Studio IDE. Microsoft chose to do this
to aid cross-language development, which is very possible in .NET. For previous
Visual Basic developers, it’s easy to set up VS .NET to look very similar to the
default Visual Basic layout. Likewise, ASP developers who used Visual InterDev can
change the VS .NET environment with the click of a button.
VS .NET adds a few very useful features to what VB and VI developers know.
Possibly the most helpful is the Server Explorer, a dockable tree view containing
information about machine resources, including databases, database connections,
event logs, and performance counters. This description makes it sound fairly
superficial but, although naturally everything that can be done in it could previously
be done by using external tools (such as the SQL Server Enterprise Manager and the
Windows Administrative tools), a huge productivity boost is gained by providing
scaled-down versions of these tools in the IDE, which provides most of the
functionality that you’ll require so—you’ll rarely have to load up the external tools.
The Toolbox has been completely rebuilt and contains different tools according to
the type of application being built. As previously mentioned, it’s important to
understand that all the controls available, no matter what application you’re building,
are actually .NET classes.
VS .NET is a complete IDE for both Win Forms and Web Forms application
development. Again, as previously mentioned, Microsoft has spent a great deal of
time making Web application development similar to traditional Windows application
development, and although the IDE’s form designer does change depending on the
application being developed, both Win Forms and Web Forms user interfaces are
made by dragging and dropping controls from the toolbox.
The help system in VS .NET is completely integrated. It is a dynamic, HTML
help system, which changes the displayed topics according to the current task being
performed (or code being typed).
A task list has been added, which not only provides the obvious functionality of
allowing tasks “to do,” but also automatically adds entries based on errors in the code.
The new, tabbed window manager makes swapping between code, form design,
help, and other main views much faster and easier by providing a tabbed bar above
the windows with all the entries on it.
The code editor has been given a significant overhaul to help boost productivity.
Microsoft pioneered the red “squiggle” in Word to highlight spelling mistakes and
later introduced the green variety for grammatical errors. VS .NET now features a
similar concept for programming: the blue “squiggle.” Anything from invalid
references to omitting type conversions is highlighted and added to the task list. This
replaces the pop-up message box that Visual Basic programmers know so well.
Hovering the mouse over the code in question will result in a tool tip detailing the
problem. The editor has a few more useful productivity additions. Automatic
completion of code blocks (If…End If and forth) and automatic code indentation are
also noteworthy additions.
A fairly large change for Visual Basic programmers is the removal of the option to
view only one sub at a time. Microsoft has, however, added a new feature to replace
this: collapsible code. All functions and subroutines can be “collapsed” so that only
the declaration is visible. It’s also possible to create code “regions” for collapsing
entire sections of code (normally multiple functions and subs). The
collapsing/expanding is done via tree view–style handles.
The AutoComplete technology in VS .NET has been upgraded to extend to XML
files and DHTML, in addition to regular Visual Basic code. This offers another great
productivity boost for developers who prefer to code their Web pages by hand or use
XML.
NOTE Although it’s not a part of the .NET Framework, which is available freely,
Visual Studio .NET has been developed with the same goals in mind. As a result,
developers can create .NET applications even more productively by using
Microsoft’s .NET programming IDE, VS .NET.
The Common Language Runtime
The common language runtime (CLR) is the execution engine for all .NET
applications. It’s the part of the .NET Framework that performs all the compilations
and provides runtime services to the running .NET application. Here’s a brief list of
features pertinent to ASP.NET developers:
Third-party languages:
* Perl
* Python
* Eiffel
* COBOL
As you can see, programming in MSIL directly would be very time consuming
and isn’t desirable. This is what the .NET language compliers compile their high-level
code to. The CLR isn’t involved in this step. Depending on the type of application, the
CLR will compile this IL code to native code (for example, x86 Win32 code) either
on installation (Win Forms) or on the first time a page is accessed since modification
in ASP.NET. MSIL code is not interpreted.
Base Classes and Class Hierarchy
At first, the whole .NET Framework concept can be quite confusing. The area that’s
possibly the most confusing is the base classes. It is, however, an extremely well-
structured and logical system that will greatly increase your productivity, so it’s
important for you to understand it.
Several terms that I’ll use regularly in the book and that are important to
understand are as follows:
Class: Classes are a very simple concept, and you can find them in most
modern languages. For those who have not previously come across them,
classes are simply data structures with associated functionality.
VS .NET
Enterprise Servers
Although not explicitly tied to .NET and .NET applications, Enterprise Servers do
complement .NET applications, and as such it’s useful to be aware that they exist.
Here’s a list of the relevant packages:
BizTalk Server, simply put, is a product designed to help companies build and
deploy integrated business processes, internally and with their trading partners. It
provides features such document routing and transformation. BizTalk Server relies on
open standards, such as XML, XSLT, HTTP, HTTPS, SMTP, and SOAP.
SQL Server is a high-performance, high-scalability database server. It has intrinsic
support for XML and includes powerful administration and monitoring tools. SQL
Server will most likely play a pivotal role in your application development if
databases are involved.
Internet Security and Acceleration Server provides both firewall security and
caching services to Web applications. It has an advanced management interface for
administration and monitoring for both the firewall and caching services that it
provides.
Exchange Server is an advanced collaboration system intended primarily for use
as a large-scale corporate e-mail system. It provides full integration with both
Windows 2000’s Active Directory system and Outlook Web Access. Microsoft has
also innovated the Web Storage System, which allows for messages and documents to
be stored and accessed through the Web. Integration with Web Forms is also possible.
Blackcomb
“Blackcomb” is the code name for the 2005 release of the Windows desktop operating
system in which Microsoft aims to further their .NET vision of “software as a service”
such that most software for Blackcomb is provided through the Internet as a service
using .NET. Although there will likely be new releases of the .NET Framework and
VS .NET between now and the release of Blackcomb, the current version of .NET
will remain largely the same in structure and functionality and forms the base for
programmers using Microsoft technologies for the foreseeable future.
Summary
In the primitive days of Web application programming, developers were required to
spend large amounts of time learning complex systems, as they were required to do
most of the “plumbing” of the development. Microsoft pioneered the way to simpler,
more productive, richer application development with ASP. ASP.NET takes the
brilliant concepts of ASP and integrates them as a core part of a completely rebuilt
application programming model designed specifically with developer productivity,
ease of use, and ease of transition from Windows application programming in mind.
ASP.NET is a core part of the new .NET Framework, an entirely new, centralized,
unified, programming language–independent application development model for
Windows applications, Web applications, and Web Services.
The .NET Framework is the development infrastructure behind Microsoft’s
“software as a service” .NET vision, which encompasses and affects almost all
Microsoft products. New versions of software products will be released to integrate
with .NET Framework applications more tightly, such as Windows .NET.
Visual Studio .NET is a powerful IDE for .NET application development, which
has full, integrated support for all the .NET Framework features that are exposed,
such as cross-language development and integrated debugging.
Now that you have a decent understanding of what .NET programming is all
about, it’s time to install the software. The next chapter guides you through the setup
of Visual Studio .NET and all the other related software components that are
prerequisites for ASP.NET development.
Chapter 2
Installation
Visual Studio .NET isn’t simply a stand-alone product for building Web
applications—in fact, it relies on several entirely separate software components to
provide an end-to-end solution for designing, implementing, and testing software
programs. As I touched on in Chapter 1, there’s a distinction to be made between VS
.NET and the .NET Framework SDK. Whereas VS .NET is simply a development
environment, the .NET Framework contains the actual runtime engine and related
components for compiling and executing .NET applications, including ASP.NET
applications. However, in the case of ASP.NET, the .NET Framework is not simply a
stand-alone platform for running ASP.NET either: It relies heavily on Microsoft
Internet Information Server (IIS) for providing the basic functionality of serving data
to clients on the Internet over HTTP.
NOTE There’s obviously a lot more to IIS than simply sending pages to clients
over the Internet. It is, in fact, a fairly complex piece of software; the details of how
IIS works and all the services that it provides warrants an entirely separate book.
ASP.NET integrates into IIS, allowing IIS to handle the receiving of requests and
the final sending of data to clients. But during the processing of a request, IIS will
pass all the information known about the request to ASP.NET, which will then
perform its own processing of the requested page and finally pass the resulting data
back to IIS to send to the client. IIS therefore plays a crucial role in developing
ASP.NET applications, as it’s the component that provides the underlying
groundwork for communications between the user and the server.
Installing IIS
Because ASP.NET needs to be set up within IIS, it’s important that IIS is installed
before ASP.NET. If you’re running a server version or edition of Windows (such as
Windows 2000 Server or Windows .NET Server), IIS was probably installed by
default. However, for other editions, IIS is most likely not installed and before you
proceed, you’ll need to manually install it.
Installing IIS is a very simple process. Because it’s a Windows component, it’s
added (or removed) through the Control Panel’s Add/Remove Programs applet. The
exact steps may differ slightly by operating system, but the following general steps
apply to all of them:
Insert 0015f0201.tif
Figure 2-2. The IIS Help Web site, which is installed by default during the IIS setup
process
If the screen in Figure 2-2 does appear, IIS has been installed successfully.
However, if an error such as “Cannot find server” appears, it’s likely that the
installation failed, in which case you should try uninstalling and then reinstalling IIS;
failing that, you should contact one of your local Microsoft support services for help.
An IIS Primer
Because IIS is a Windows Service that runs in the background, it isn’t a front end, as
such. However, when IIS is installed, another applet is added to the Administrative
Tools section of the Control Panel: the Internet Services Manager (ISM), which is an
MMC plug-in that can be used to configure IIS. This is a very useful and important
tool, and although you shouldn’t need to modify the default settings of IIS,
understanding the basics of IIS is crucial when you build ASP.NET applications,
especially when you plan to deploy them to a “live” (or “production”) Web server.
Figure 2-3 shows the ISM after it has been loaded from the Control Panel.
Insert 0015f0203.tif
The simplest way to think of IIS is as a means to share files over the Internet. Just as
with sharing files in Windows over a Windows network, in IIS you need to choose a
folder whose files are shared, as you obviously don’t want to share your entire
computer’s data. By default, this folder is C:\Inetpub\wwwroot\.
In Figure 2-3, the files listed in the right pane are the files created in the
C:\Inetpub\wwwroot\ directory during the installation of IIS. These are simply a few
HTML documents (if you’re unfamiliar with HTML, it’s introduced in Chapter 3) and
related images that act as an introduction to IIS. When you visit http://localhost in
Internet Explorer (IE), IE effectively sends a message to IIS asking for the list of files
in the “home directory” (the reason this doesn’t actually happen will be covered
shortly). To access a particular file in the home directory in IE, the filename must be
included in the URL after the initial http://localhost. For example, if there was a file
called readme.txt in the home directory, you could access it in IE by browsing to
http://localhost/readme.txt. Likewise, if there was a file called hello.txt inside a
subdirectory named newfolder of the home directory (in other words,
C:\Inetpub\wwwroot\newfolder\hello.txt), the URL to access it would be
http://localhost/newfolder/hello.txt.
You can test out this concept for yourself by creating text files and putting them
into C:\Inetpub\wwwroot (or subdirectories thereof) and accessing them through IE. If
you’re on a network, you can access these files from other computers by substituting
http://localhost with http://ipaddress, where ipaddress is the IP address of your
machine on the network (such as 192.168.1.10).
The Default Document
When you visit a site such as http://www.microsoft.com, you don’t get a list of files—
you’re sent an HTML page. If you visit http://www.microsoft.com/net, you don’t get
a list of files either—you’re also sent an HTML page. In fact, it’s very rare that an
HTTP server will return a list of files to a user or client on the Internet, for a couple of
reasons. First of all, it’d be very user-unfriendly to expect a user to select the file that
he or she would like to view when he or she visits a site. The fact that the Web site is
even made up of files should be abstracted from the user, which is why we have
hyperlinks (“links”) on Web pages. Second, from a security perspective, giving the
user a list of files in a directory on a Web server is far from good practice, and can
often give an attacker information that helps him or her compromise the server.
To counter these issues, IIS includes support for a default document. This is
basically the file that IIS will return by default if the user request doesn’t specify a
specific file to be returned. For example, if the default document is set to be
index.htm, and the user submits a request for http://localhost, the browser will in fact
return http://localhost/index.htm if it exists. IIS implements this functionality as a list
of “default filenames,” so that it’s possible to specify that if a directory is requested
by the user, IIS will attempt to return the first file in the list, and if that does not exist,
then the second file, and so on. To configure the list, right-click the Default Web Site
node in the ISM, click Properties, and go to the Documents tab, as shown in Figure 2-
4.
Insert 0015f0204.tif
Figure 2-4. The property pages for the Default Web Site
This is the reason why when you visit http://localhost, an HTML page is returned
rather than a list of files. If you look in the home directory, you’ll find that there is in
fact a file named using one of the prescribed default document names in the Default
Web Site Properties dialog box.
Virtual Directories
1. Right-click the Default Web Site node in the ISM and click New !
Virtual Directory. The Virtual Directory Creation Wizard should
appear, as shown in Figure 2-5.
Insert 0015f0205.tif
Insert 0015f0206.tif
Figure 2-6. The ISM with the newly created virtual directory selected
Notice how the virtual directory’s icon is different from the actual subfolders in
the home directory. The alias that you chose when creating the virtual directory
describes how the virtual directory is named and accessed. A virtual directory is
simply accessed as if it were a normal subfolder of the home directory. For example,
to access the hello.txt file in IE, you would use http://localhost/mytestsite/hello.txt as
the URL—the virtual directory looks and behaves exactly as if it were a regular
directory, and the user is none the wiser.
However, although this use of virtual directories can be very helpful, they do also
serve another different, yet very important function: They inform IIS that the content
of the virtual directory is an entirely new Web application, as opposed to a regular
directory that’s assumed to be part of the Web site or application being hosted in the
home directory. Although the importance of this may not be clear now, as you begin
to build and test your ASP.NET applications, it should become so, especially in the
last chapter of this book, which deals with the deployment of ASP.NET applications
to real, live Web servers. For now, all you need to understand is that for each
ASP.NET application that you build, a new virtual directory will automatically be
created for it that identifies it to IIS as an entirely separate application.
Insert 0015f0207.tif
Figure 2-8. The first step in the installation process: the Windows Component Update
Click Continue. During this part of the setup, numerous system restarts may be
required. To avoid having to re–log in each time, the setup allows you to enter in your
login details and then it will automatically log in for each restart during this process.
This allows you to start the installation and leave it running without having to
constantly check back to see whether you need to reenter your login details. This
functionality is very handy, and I strongly recommend you make use of it (see Figure
2-9), as it definitely makes the Windows Component Update process less tedious.
Insert 0015f0209.tif
Figure 2-9. Set up the Windows Component Update installer to automatically log in
after the numerous system restarts during setup.
Once you’ve entered your login details, click Install Now! and the setup will
proceed to install all the required components.
Once the setup has completed (it may take up to an hour, depending on the speed
of your machine and the number of required components), click Done. You’ll return
to the original Visual Studio .NET Setup menu. Click option 2, Install Visual Studio
.NET.
Once you’ve entered in your product key and accepted the EULA, you can click
Continue. The following screen allows you to customize your VS .NET installation,
as shown in Figure 2-10.
Insert 0015f0210.tif
Figure 2-10. The Visual Studio .NET IDE (and tools) setup application
The default settings should suffice, but after you’re satisfied with the available
options, you can proceed by clicking Install Now!
NOTE For those of you interested in the Microsoft Visio and Microsoft Visual
SourceSafe components, you must install these individually—you won’t find an
option to install them in the Options screen of the VS .NET installation.
Again, depending on the speed of your machine and the chosen configuration, VS
.NET may take up to 2 hours to install. During this time, CD installations will require
the CD media to be swapped periodically. Once this step of the installation is
complete, you may want to use option 3 of the Visual Studio .NET Setup menu to
check for service releases for VS .NET or continue to install additional components
such as Visio and SourceSafe (if applicable to your version of VS .NET), but the core
installation is now complete, and VS .NET is ready to work with.
Summary
IIS is an integral component of ASP.NET application development, and it’s important
that you familiarize yourself with the Internet Services Manager (ISM) console,
particularly with regard to the Default Web Site Properties dialog box, which includes
a wealth of options that govern much of how Web sites are hosted in the IIS
environment. Understanding what virtual directories are and how to administer them
is also very important, as VS .NET makes heavy use of them and creates a new virtual
directory for every ASP.NET project that you create in VS .NET.
Beyond IIS, the installation of VS .NET and the .NET Framework (which
integrates ASP.NET into IIS) is very simple, and you should be up and running
without any headaches.
Chapter 3 provides an introduction to HTML and Web design techniques for the
benefit of Visual Basic programmers who haven’t worked with raw HTML before, as
a basic understanding of how static Web sites are built is absolutely essential when
programming ASP.NET applications. Topics covered include HTML, client-side
JavaScript, CSS, and more, so there might even be something in there for the ASP
developers among you as well.
Chapter 3
HTML Primer
HTML is a standardized metalanguage used primarily on the Web for formatting
documents. Web browsers are effectively HTML “interpreters.” All the Web
applications you develop will, once they have been processed, produce HTML for
Web browsers. A significant part of your Web applications will also be raw HTML
that is not produced via code. Visual Studio .NET’s Web Form Designer (which I
introduce in the next chapter) provides a “What You See Is What You Get”
(WYSIWYG) interface for designing the user interfaces of Web applications (which
is effectively what HTML is for: designing Web user interfaces). This means that you
can use the Designer as you would a rich word processor, such as Microsoft Word, to
write static text, format it, add images and tables, and so forth, and Visual Studio
.NET will produce the HTML code behind the scenes.
However, when programming ASP.NET applications, you are really dealing with
producing HTML. Having a solid understanding of what HTML is, what rules apply
to it, and how it works will greatly affect your understanding of the inner workings of
ASP.NET applications, which is crucial to developing solid, stable, usable, and cross-
browser–compatible applications. Additionally, developing Web application user
interfaces is vastly different from developing desktop application user interfaces in
environments such as Visual Basic 6.0. In traditional Visual Basic, developers never
really had a need to look at and understand the code that was generated behind the
scenes when controls were dropped onto a form. With Web applications, this
understanding is almost essential, and the code generated behind the scenes is, for the
most part, HTML.
What Is HTML?
HTML, or Hypertext Markup Language, is a standardized language that enables
documents to be formatted and linked together.
These documents are stored in ASCII text format and all formatting is in the form
of tags, which are formatting commands (often abbreviations) placed inside the < and
> characters. HTML was designed from the very beginning to be cross-platform and,
as such, HTML itself contains nothing that is proprietary to any one platform. HTML
is synonymous with the Internet, and in particular, the Web. This is because HTML is
the standard used for transmitting documents on the Web. All Web sites are formatted
using HTML (although it is possible to make files available for download—for
example, Microsoft Word documents).
Possibly one of the most important characteristics of the Internet is the capability
for documents to be linked to one another. It is this feature that has set the Web apart
from most other data sources. For example, a news article can contain a link to a
related story, such that if the user decides to click the link, the related story will
display. HTML also natively provides this functionality for linking documents.
NOTE You may be wondering who comes up with these specifications for HTML.
In 1994, upon the original request of Tim Berners-Lee, the creator of HTML, the
World Wide Web Consortium (W3C) was formed. Its aim is to act as a standards
body for Web-related technologies. Microsoft, Netscape, Sun Microsystems, IBM,
and many other influential companies are members of the W3C, and all are allowed
to make standards recommendations.
Standardization is an extremely important issue, and although previously standards
were not adhered to (particularly in the area of Web browsers and HTML),
companies are becoming much more aware of the consequences of not following
the standards laid out and adding their own features at will. For example,
Microsoft’s implementation of the SOAP and XML specifications in .NET are almost
perfectly in line with those set out and agreed upon by the W3C.
<body>
</body>
</html>
Insert 0015f0301.tif
Another important concept in HTML is the handling of white space. HTML only
produces one space at a time, unless a special character entity is used. The first line in
the code that follows produces exactly the same output as the second line:
This is some text.
This is some text.
Several other formatting tags are commonly used. The next example introduces
two more, <u> for underlining and <i> for italicizing text, to demonstrate several
other HTML concepts. Listing 3-2 demonstrates the combined use of the <b>, <u>,
and <i> tags.
Listing 3-2.
<html>
<head>
<title>Test Page</title>
</head>
<body>
</body>
</html>
Insert 0015f0302.tif
<body>
Correct: (embedded)<br>
<u>This text is underlined <b>and bold</b> </u><b>and now it's bold
and
not underlined</b>.<br><br>
Incorrect: (overlapping)<br>
<u>This text is underlined <b>and bold </u>and now it's bold and not
underlined</b>.
</body>
</html>
Both examples will presumably produce the output shown in Figure 3-3.
Insert 0015f0303.tif
Insert 0015f0304.tif
<body>
</body>
</html>
Insert 0015f0305.tif
<body>
</body>
</html>
Insert 0015f0306.tif
<body>
<font color="#000000">Black</font><br>
<font color="#333333">Dark Grey</font><br>
<font color="#CCCCCC">Light Grey</font><br>
<font color="#FF0000">Red</font><br>
<font color="#00FF00">Green</font><br>
<font color="#0000FF">Blue</font><br>
<font color="#FFFF00">Yellow</font><br>
<font color="#FF00FF">Fuchsia</font><br>
<font color="#00FFFF">Aqua</font><br>
<font color="#FF6600">Orange</font><br>
<font color="#CCFF00">Bright Green</font><br>
<font color="#009900">Grass Green</font><br>
<font color="#6699CC">Sky Blue</font><br>
<font color="#990099">Purple</font><br>
<font color="#990000">Maroon</font><br>
<font color="#000066">Dark Blue</font><br>
</body>
</html>
Insert 0015f0307.tif
<body>
</body>
</html>
Insert 0015f0308.tif
Figure 3-8. Formatting text using different font faces, font sizes, and font colors
Numerous other tags deal with the formatting of text; however, those omitted
replicate the functionality of the tags already introduced in this chapter, and they are
used normally to more accurately describe the reason for formatting a particular piece
of text. For example, the address tag generally produces the same result as using the
italic (i) tag.
All HTML documents contain a body tag. This tag is not only used to define the
bounds of the content in the document, but it also features numerous attributes for
setting default values and changing the way the document looks. The pertinent
attributes for the moment are bgcolor, background, text, link, vlink, and alink.
The bgcolor attribute specifies, as its name suggests, the background color for the
document, which is by default white (in most browsers). The values that this attribute
accepts are identical to those of the font tag’s color attribute: color constants or
hexadecimal color values. Listing 3-8 shows the use of the bgcolor attribute and
Figure 3-9 shows the output of Listing 3-8.
Listing 3-8.
<html>
<head>
<title>Body Tag Demo</title>
</head>
<body bgcolor="#000000">
</body>
</html>
Insert 0015f0309.tif
<body background="bg.jpg">
The background of the document is the image you selected, tiled if necessary.
</body>
</html>
Insert 0015f0310.tif
Begin sidebar
There are essentially two different ways in which you can reference external
documents in HTML: absolute and relative file paths.
Absolute paths use the entire URL of a file, such as
http://www.dotnet.za.net/images/top_title.jpg. The use of absolute paths
should be avoided if at all possible because they restrict portability. For
example, if a site using absolute paths is moved to a different location, all the
paths in all the files need to be changed to reflect the new location.
Relative paths, however, do not include the entire URL of a file. Relative paths
provide the location of the file in relation to the file referencing it. For example,
assume there is an HTML file located at
http://www.dotnet.za.net/somefile.html. This page is required to use a
background picture located at http://www.dotnet.za.net/images/bg.jpg. The
page would reference the image by using images/bg.jpg rather than including
the full URL. If the image was located at
http://www.dotnet.za.net/images/backgrounds/bg.jpg, the page would
reference the image by using images/backgrounds/bg.jpg.
Relative paths easily reference files not only in higher levels, but also in lower
directory structure levels with the level down string (..). Assuming that an
image is located at http://www.dotnet.za.net/bg.jpg and a page is located at
http://www.dotnet.za.net/anydir/somepage.html, the page would reference the
image in its parent directory by using ../bg.jpg. If the page was located at
http://www.dotnet.za.net/anydir/anotherdir/somepage.html, it would reference
using ../../bg.jpg.
The final type of relative path is where neither of the files is in different
directories. In this case, the path is simply the filename, as demonstrated in
Listing 3-9. The last important item to remember is that all paths in HTML take
the same format, whether they are absolute or relative.
End sidebar
TIP If a background image is being used, it is useful to include the bgcolor attribute in
addition to the background attribute with a color value as close to the dominant color or
shade of the image. Should the image take a while to load on slow connections, a contrast will
still be maintained so that the text on the page can be read.
The text, link, vlink, and alink attributes of the body tag all take color values. The
text attribute is used to define the default font color for all text on the page (naturally,
this can be overridden using the font tag). The link attribute is used to define the color
of unfollowed links (links that have not been clicked) on the page; vlink defines links
that have been followed; and alink defines links that have been clicked, but the file
that they link to has not started loading.
Linking Pages
One of the major advantages of the World Wide Web is the capability of pages to be
linked to each other. Links are created using the a, or “anchor,” tag, in conjunction
with the href attribute, which contains the path (absolute or relative) of the file to link
to. Listing 3-10 demonstrates the use of the anchor tag to create links. Figure 3-11
shows the output.
Listing 3-10.
<html>
<head>
<title>Anchor Tag Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0311.tif
NOTE The file that is being linked to does not necessarily have to be an HTML
document. You can create links to ZIP files, Microsoft Word documents, and so
forth. Modern Web browsers will display a dialog box to allow the user to save the
file to his or her hard drive. This is because browsers detect that the content being
sent is not HTML. This is how file downloads are created—they are simply links to
files other than HTML documents.
Regions
It is possible to define regions of content in an HTML page. For now, the primary
purpose of this is for alignment, although there are several other important reasons to
define regions. The two tags for defining regions are div and span. Put simply, the
difference between the two is that div takes up the entire width of the page and span is
only the width of the content in it. It is for this reason that span elements are more
typically used for formatting through style sheets than for alignment.
The div tag has an align attribute, which accepts left, center, or right. Listing 3-11
uses all three. Figure 3-12 shows the output.
Listing 3-11.
<html>
<head>
<title>Div Tag Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0312.tif
Lists
HTML provides two sets of tags for creating lists: ol for “ordered lists” and ul for
“unordered lists”; as well as the li for “list item” tag for adding items. The advantage
of using lists in HTML is that alignment and numbering (in the case of ordered lists)
is done automatically. Listing 3-12 demonstrates the use of both types of lists, and
Figure 3-13 shows the output.
Listing 3-12.
<html>
<head>
<title>Lists Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0313.tif
Images
You can insert images into a page by using the img tag. This tag has several important
attributes:
* The src attribute is the source of the image—the path to the image
file.
* The width and height attributes define the dimensions of the image.
If they differ from the actual dimensions of the image, the image is
distorted accordingly.
* The alt attribute allows for a short description that will be displayed
until the image has started loading.
* The align attribute allows for specifying alignment, which can
override the settings given to it if it is in a division with alignment
settings.
Like the break tag, the image tag does not have a closing tag. Listing 3-13 shows
how to insert an image with alignment specified against that of a division, and Figure
3-14 shows the result.
Listing 3-13.
<html>
<head>
<title>Image and Alignment Demo</title>
</head>
<body>
<div align="right">
<img src="fire.jpg" width="151" height="113" alt="Fire" align="left">
This is a picture of a fire.
</div>
</body>
</html>
Insert 0015f0314.tif
Tables
Tables are an extremely important aspect of HTML. The purpose of a table is
normally to represent data. In HTML, however, tables play a much larger and more
important role in page layout.
Tables consist of rows and columns. In HTML, a table is defined by the table tag;
rows are defined with the tr, or “table row,” tag; and data (and hence columns) is
defined with the td, or “table data,” tag. Listing 3-14 shows how a simple table is
created without using any special attributes, and Figure 3-15 shows the output.
Listing 3-14.
<html>
<head>
<title>Table Demo</title>
</head>
<body>
<table>
<tr>
<td>Day:</td>
<td>Rainfall:</td>
</tr>
<tr>
<td>Sunday</td>
<td>0mm</td>
</tr>
<tr>
<td>Monday</td>
<td>25mm</td>
</tr>
<tr>
<td>Tuesday</td>
<td>35mm</td>
</tr>
<tr>
<td>Wednesday</td>
<td>14mm</td>
</tr>
<tr>
<td>Thursday</td>
<td>10mm</td>
</tr>
<tr>
<td>Friday</td>
<td>25mm</td>
</tr>
<tr>
<td>Saturday</td>
<td>30mm</td>
</tr>
</table>
</body>
</html>
Insert 0015f0315.tif
NOTE To exclude certain cells from having a border when the table’s border
attribute is set to include a border, you need to use style sheets. Style sheets are
covered later in this chapter.
The table tag also has two important attributes that deal with spacing: cellpadding
and cellspacing. The cellpadding attribute defines the amount of space between the
content of a cell and its borders. The cellspacing attribute defines the amount of space
between the cell borders. Figure 3-16 shows the relationship between the table border,
the intracell borders, the cell content, the cell padding, and the cell spacing.
<body>
</body>
</html>
Insert 0015f0317.tif
Figure 3-17. The difference between cell padding and cell spacing
As you can see in Figure 3-17, there is quite a significant difference between cell
padding and cell spacing. Certain circumstances require only cell padding to be used,
some require only cell spacing, and others require both.
The only three other particularly important attributes of the table tag are the align,
width, and height attributes. The align attribute specifies the alignment of the table,
either left, center, or right. The width and height attributes take either pixel values or a
percentage of the available space. The table data tag contains several attributes that
are important to know: the bgcolor, or “background color,” attribute; the align
attribute; and the valign, or “vertical align,” attribute. There are also two dimension
attributes: width and height. The table row tag also contains these six attributes, and if
set in a table row, they are applied to all cells in that row. Listing 3-16 demonstrates
the use of most of these attributes, and Figure 3-18 shows the output of Listing 3-16.
Listing 3-16.
<html>
<head>
<title>Table Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0318.tif
<body>
Column Span:<br>
<table border="1">
<tr>
<td colspan="2">Cell 1</td>
<td>Cell 2</td>
</tr>
<tr>
<td>Cell 3</td>
<td>Cell 4</td>
<td>Cell 5</td>
</tr>
</table><br>
Row Span:<br>
<table border="1">
<tr>
<td rowspan="2">Cell 1</td>
<td>Cell 2</td>
<td>Cell 3</td>
</tr>
<tr>
<td>Cell 4</td>
<td>Cell 5</td>
</tr>
</table><br>
Column Span and Row Span:<br>
<table border="1">
<tr>
<td rowspan="2">Cell 1</td>
<td colspan="2">Cell 2</td>
</tr>
<tr>
<td>Cell 3</td>
<td>Cell 4</td>
</tr>
</table><br>
</body>
</html>
Insert 0015f0319.tif
TIP Designing tables that involve multiple row and column spans can easily become very
complicated. It often makes it significantly easier to plan the table on a piece of paper first
before attempting to code it.
When cell spanning is used in conjunction with a borderless table and the width
and height attributes, tables can be very useful tools for laying out entire pages.
Listing 3-18 and Figure 3-20 show a simple example.
Listing 3-18.
<html>
<head>
<title>Page Layout Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0320.tif
Forms
Forms were introduced in HTML to allow for a certain amount of interaction between
the user and the site. There are essentially two parts to interactivity on Web sites: the
user interface (which is the forms) and the actual programming. Client- and server-
side scripting is where the programming comes in. This section only covers the user
interface design.
The bounds of a form are defined by the form tag. Although it is possible to have
multiple forms on one page in HTML, it is best to get in the habit of using only one
form per page, as this is a general limitation of most ASP.NET applications. The form
tag has two pertinent attributes: action and method. The action attribute defines where
the data in the form must be submitted to; it is a URL value. In ASP.NET
applications, this is an ASP.NET page, however, it can point to CGI applications or
ISAPI applications. The method attribute describes how the data from the form must
be sent to the file specified in the action attribute. It takes a value of either post or get.
The difference between post and get will be dealt with later, but for now, use post
unless instructed otherwise.
An HTML form is really a collection of controls where data can be inputted.
HTML provides single and multiline input boxes, check boxes, radio buttons, combo
boxes, and buttons. The values of these controls are submitted to the location
specified in the form tag.
Almost all of the controls provided by HTML are inserted using the input tag.
This tag has three important attributes: type, name, and value. The type attribute
accepts the values text, checkbox, radio, button, and submit. The name and value
properties contain the unique reference to the control and its default value,
respectively. Forms normally contain at least one submit button (an input tag with the
type attribute set to submit). When a user clicks the submit button, the browser sends
the data in the form to the form’s action URL.
Listing 3-19 and Figure 3-21 show the use of the controls inserted using the input
tag.
Listing 3-19.
<html>
<head>
<title>Form Demo</title>
</head>
<body>
</body>
</html>
Insert 0015f0318.tif
<body>
</body>
</html>
Insert 0015f0322.tif
<body>
</body>
</html>
Insert 0015f0323.tif
Figure 3-23. Client-side script using JavaScript to change the browser’s status bar
text
The first unusual thing about this example is the use of the “tag” <!-- --> inside
the script block. This is actually an HTML comment. Anything inside it will be
ignored by the HTML processor. The reason the comment is included is so if the page
is viewed using a browser that does not support client-side script, the code will not be
displayed, as the HTML processor will take the code as a comment.
Probably the next most interesting part is that there is no main() function, no event
handlers, and no other structures. Client-side script was intended to be quick and easy,
and as such, it is not very involved; there is no requirement for classes or the like.
There are also no library “imports.” Everything that can be done using client-side
script is automatically made available.
Internet Explorer and Netscape allow client-side script to have access to and
manipulate many elements of an HTML page, as well as some elements of the
browser itself. In Listing 3-21, the window object is used. You can think of the
window object as the object that represents the browser and the functionality exposed
by it, as well as the container for the HTML document. As you can see, the status
property refers to the text of the status bar in the browser. The assignment operator is
the equal sign (=) in JavaScript, and statements must end with a semicolon (;). The
delimiter for strings is the quotation mark (").
Many HTML elements have event attributes where JavaScript code can be
inserted. JavaScript also has support for user functions, which can return values or
not. Listing 3-22 and Figure 3-24 demonstrate HTML events and a simple function
working together.
Listing 3-22.
<html>
<head>
<title>Script Demo</title>
<script>
<!--
function changeWindowStatus(newstatus) {
window.status = newstatus;
}
-->
</script>
</head>
<body>
<form>
<input type="button" name="Button1" value="Click Me"
onClick="changeWindowStatus('Hello World!');">
</form>
</body>
</html>
Insert 0015f0324.tif
<body>
<form name="form1">
Name: <input type="text" name="txtName"><br>
<input type="button" value="Greet" onClick="SayHello();">
</form>
</body>
</html>
Insert 0015f0325.tif
CSS
Style sheets are actually a very simple concept. Their primary purpose is to remove all
style information from content in HTML documents. The advantages of doing so are
far from apparent in a five-page site, but when they are used in any sizeable project,
the time saved by style sheets can actually be astounding. In addition to providing
productivity gains, style sheets also provide unprecedented control over the rendering
of most HTML elements—control that is simply not available via normal HTML tags
and attributes.
There are essentially two ways in which CSS can be applied to HTML elements.
The first is via inline style, and the second is via a style sheet, which can either be
embedded in the document or in an external file.
All HTML elements that can be manipulated via CSS have a style attribute. The
value of this attribute is all the CSS statements that need to be applied to that
particular element. CSS statements closely resemble, yet are not always identical to,
their HTML counterparts with regard to naming. CSS statements are totally different
from HTML in syntax, however, as they do not use tags, but rather “property: value”
pairs. Listing 3-24 and Figure 3-26 show a basic example of how CSS can replace
HTML styling.
Listing 3-24.
<html>
<head>
<title>Inline Style Demo</title>
</head>
<body>
<div>
<font face="Arial" size="2" color="#FF0000">
Style without using CSS
</font>
</div>
<div style="font-family:Arial;font-size:10pt;color:#FF0000;">
Style using CSS
</div>
</body>
</html>
Insert 0015f0326.tif
<body>
<div style="background-color:#000000;color:#FFFFFF;">
Style using CSS
</div>
</body>
</html>
Insert 0015f0327.tif
.code {
font-family: Courier New;
font-size: 9pt;
background-color: #CCCCCC;
}
.header {
font-size: 16pt;
}
#abstract {
font-style: italic;
}
-->
</style>
</head>
<body>
<div class="header">
Some article
</div>
<div id="abstract">
This article deals with x, y and z.
</div><br>
<div>
This is ordinary text in the article.
</div><br>
<div class="code">
Sample code.
</div><br>
<div>
More ordinary text.
</div>
</body>
</html>
Insert 0015f0328.tif
Figure 3-28. Using embedded style sheets to define formatting and style
As you can see, style sheets are embedded in style tags. As with script, an HTML
comment is wrapped around all the CSS code, so that should the browser not support
CSS, the code will not be displayed. The first block of code is a typical example of
CSS:
div {
font-family: Arial;
font-size: 10pt;
}
This code instructs all division elements to be rendered with the text formatted in
10-point Arial font, unless those settings are overridden. Therefore, all text within div
tags use 10-point Arial font unless otherwise instructed (by font tags or other, more
specific styles). The div in this code block could be replaced with any HTML element
that accepts CSS, such as body, span, or even td.
The HTML 4 specification makes allowance for a class attribute, which every
element that is accessible via CSS has. This class attribute allows distinction between
a particular element and other elements, so that in this example only some division
elements are formatted in a certain way:
.header {
font-size: 16pt;
}
The period preceding “header” indicates that the style rules inside this CSS block
must only be applied to elements that have a class="header" attribute. In the example,
this was applied to a division, and because it applies more specifically, the original
font-size value of a div is ignored. The same applies to the .code selector (a selector is
the name given to the part of the CSS block that specifies what it applies to). The hash
mark (#) in the #abstract selector means that the style rules in that block will be
applied to all tags with an attribute of id="abstract ". This example has only been
included to demonstrate what the hash mark means in a selector; however, the class
method is preferable, as the id attribute of tags is often analogous to the name
attribute, which may cause problems in both client- and server-side scripts.
The primary advantage of using a style sheet is that if one property in a block is
changed, the changes are immediately effected in the entire document where those
rules are used. However, going back to Listing 3-26, if there were lots of articles on
the site all using that format (and thus style sheet), it would still be a major time-
consuming effort if, say, the background color of the code regions needed to be made
#999999 instead of #CCCCCC. Each individual article would have to be opened and
the style sheet modified accordingly. This seems rather silly, as all the documents use
the same style sheet. The solution is an external style sheet. An external style sheet is
a plain text file with the CSS code normally inside a style element residing in it. Each
document that uses this style sheet creates a reference to it in the HTML code so that
it is included. HTML provides the link tag for this purpose. The major advantage of
using an external style sheet is that it only takes one change in one file for all the
HTML documents using it to be updated. Listings 3-27a and 3-27b (CSS and HTML
files respectively) and Figure 3-29 demonstrate how an external style sheet would be
used for Listing 3-26.
Listing 3.27a
div {
font-family: Arial;
font-size: 10pt;
}
.code {
font-family: Courier New;
font-size: 9pt;
background-color: #CCCCCC;
}
.header {
font-size: 16pt;
}
#abstract {
font-style: italic;
}
Listing 3.27b
<html>
<head>
<title>External Style Sheet Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="header">
Some article
</div>
<div id="abstract">
This article deals with x, y and z.
</div><br>
<div>
This is ordinary text in the article.
</div><br>
<div class="code">
Sample code.
</div><br>
<div>
More ordinary text.
</div>
</body>
</html>
Insert 0015f0329.tif
Web Servers
The term “Web server” can be used in the context of a computer and a piece of
software. A Web server, for the purposes of this book, is a computer that is
permanently connected to the Internet and runs software such that when a request for
a file is received, it returns the requested file. The machine itself does not have to be
very different from powerful desktop machines. Entry-level servers normally have
CPUs, memory, and hard drives similar to those of high-end workstations. Servers do
not normally feature advanced multimedia functionality and will generally have
bottom-of-the-range video adapters and no sound cards. Microsoft’s customers
generally opt for the use of a “scale out”, rather than “scale up” policy to achieve
scalability (the capability of a Web server to serve many simultaneous users) In a
scale up policy, more users are catered to by adding more CPUs and memory to the
server. In a scale out policy, if an application needs to serve more users, a server is
simply added to the farm as part of a load-balancing environment.
Using this scale out policy, farms serving applications using Microsoft
technologies rarely have servers with more than two processors and 1GB of RAM.
When the current servers in a farm are not adequate to efficiently serve the requests,
more servers are added to the farm.
An extremely important factor in serving Web applications is Internet connectivity
bandwidth. Server farms should not be connected to the Internet via dial-ups, ISDN,
DSL, or cable connections. They should be connected via fast, permanent, reliable
connections—at a minimum, a single T1 or T3 line.
In a Microsoft environment, the Web server software used is Internet Information
Server (IIS), which ships with Windows 2000 Server and above. This is the software
that intercepts requests on the HTTP (Web) TCP/IP port 80. It then interprets the
request and sends a response back to the user that made the request with the
appropriate data. The full version of IIS, which ships with Windows 2000 Server, has
the capability to serve up multiple sites on different domains. However, Windows
2000 Professional ships with a version of IIS that has the limitation of only being able
to serve one site on one domain. This version is perfectly adequate for the purposes of
ASP.NET development, which obviously requires IIS, because it is essentially an
“add-on” to a Web server. (This is why Chapter 2 provided instructions for installing
IIS.)
An important fact to know is that although Web browsers only render HTML
documents, which are the format of Web documents, IIS and other Web server
software can serve up any documents, not only HTML documents—for example, zip
archives. How the user’s browser handles this depends on the browser, but Netscape
and Internet Explorer both handle responses from the Web server similarly. If the
response is HTML, it is rendered in the browser. If the response is not HTML, a
dialog box displays that asks where the user would like to save the incoming file to on
his or her hard drive.
NOTE sThe default browser behavior of opening up a ‘Save As’ dialog when a
non-HTML file is opened is not the case in all instances. For example, Internet
Explorer will display Microsoft Office documents in a special mode of the browser if
the server sends an Office document. However, for the most part, non-HTML files
result in a Save As dialog box being displayed.
Server-Side Programming
Chapter 1 covered the concept of server-side programming. To recap, in server-side
programming, all code is executed on the server and the result is sent to the client. In
this book, server-side programming consists of requests to the server, which take the
form of HTTP requests; the code executed on the server, which is ASP.NET code
using the Visual Basic .NET language; and the result, which is HTML, or DHTML if
the client supports it, which is again transported to the client using a standard HTTP
response. From the client’s viewpoint, it is sending a request for a file and receiving a
response—no different from any other HTML file that it requests. The execution of
the code on the server is completely transparent.
This chapter briefly introduced client-side scripting in HTML documents, in
which JavaScript code is sent with the HTML and is executed by the browser when it
is received. Here is a brief summary of the advantages and disadvantages of server-
side scripting as compared to client-side scripting.
Advantages:
Disadvantages:
* All interaction between the user and the application requires the
user to submit information, which is then sent to the server,
processed, and returned. This makes interaction slow, due to the fact
that the Internet is relatively slow. Client-side scripts can execute
immediately.
* Processing scripts on the server use up server resources and thus
make server upgrades and additions more prevalent than in client-
side–only applications.
VB .NET also has support for arrays, which are declared using the Dim statement.
The size of arrays cannot be fixed and the ReDim statement can be used to modify
array size. You can declare arrays using either of the following:
Dim ArrName(Size) As Type
Dim ArrName() As Type = {InitialValue1, InitialValue2..., InitialValueN}
Although array size is not constant, the number of dimensions must be fixed. The
preceding examples create one-dimensional arrays. Multidimensional arrays are
created using the following syntax:
Dim ArrName(Dimension1Size, Dimension2Size..., DimensionNSize) As
Type
Operators
VB .NET contains several assignment operators, many similar to those found in
C/C++-style languages. Table 3-2 shows all the assignment operators available, their
syntax, and their purpose.
Table 3-2. Visual Basic .NET Assignment Operators
However, the If statement provides both the ElseIf and Else options to extend the
basic If statement, as follows:
If condition Then
Statements
Else
Statements
End If
or
If condition Then
Statements
ElseIf condition Then
Statements
ElseIf condition Then
Statements
End If
or
If condition Then
Statements
ElseIf condition Then
Statements
Else
Statements
End If
Unlike the C/C++ switch structure, the Select structure does not require an Else
section.
There are three primary looping structures for VB .NET, each with its own slight
variation. The For loop is an iteration loop and can take one of two forms:
For counter = start To end
Statements
Next counter
or
For Each item In collection
Statements
Next
or
Do Until condition
Statements
Loop
or
Do
Statements
Loop While condition
or
Do
Statements
Loop Until condition
The last looping structure, the While loop, is also a conditional loop:
While condition
Statements
End While
VBScript and VB programmers should note that the Wend keyword has been
replaced by End While, although VS .NET makes this change automatically.
Subroutines and Functions
There are different structures in VB .NET to define procedures that return values and
those that don’t. Functions that don’t return values (void functions in C/C++) are
created using the Sub structure:
Sub SubName(param1 As Type..., paramN As Type)
Statements
End Sub
The Return statement is used to define the result of the function. This can be
replaced with a statement assigning the result to the name of the function. The type of
the return is defined by the As Type after the argument list parentheses.
When functions or subs are called from code, parentheses must be included unless
no arguments are taken, in which case the parentheses can be omitted. This is a
change from VBScript and VB 6.0, where parentheses were not required on calls to
subs, even if they had arguments. Subs and functions are both called simply by using
the name thereof, followed directly by a pair of parentheses in which parameters are
placed, separated by commas. The following are examples of valid sub and function
calls:
A = SomeFunction(x, y, z)
B = AnotherFunction
C = AnotherFunction()
SomeSub(x, y)
AnotherSub()
AnotherSub
Member Scope
Member scope is important in the development of classes. It defines which variables
and properties of the class can be accessed, and from where. The actual development
and role of classes in VB .NET and ASP.NET is discussed in more detail later. Table
3-5 outlines the keywords in VB .NET that relate to defining member scope.
Table 3-5. Member Scope Keywords
Error Handling
Visual Basic previously provided an On Error statement in conjunction with labels to
provide error handling. VB .NET introduces a completely new error-handling
structure that is very similar to that provided in Java and C++. The following code is a
basic representation of the syntax:
Try
'Statements that may cause exceptions
Catch exception1 As Type
'Statements to deal with exception
Catch exceptionN As Type
'Statements to deal with exception
Finally
'Statements to perform cleanup
End Try
Method Overloading
Method overloading is not an OOP concept—it is a new capability that VB .NET has
and the .NET Framework uses commonly. Method overloading is used when a
procedure (function or sub) could take arguments with different types. Without
method overloading, this eventuality would result in multiple methods being made,
with their names reflecting the arguments that they take. Listing 3-28 shows a simple
example of this.
Listing 3-28.
' Procedures in some class
Sub DisplayInt(ByVal value As Integer)
' Code
End Sub
Sub DisplayString(ByVal value As String)
' Code
End Sub
a=5
DisplayInt(a)
b = "Hello"
DisplayString(b)
a=5
Display(a)
b = "Hello"
Display(b)
Classes
A class is a data structure with related code to perform the functionality of the class.
Classes are normally built to perform specific operations and are designed to be
reusable. Classes can contain methods, properties, and variables whose scopes are
defined using the keywords detailed in the previous section. Another feature of
classes in VB .NET is that they can contain constructors that take parameters.
A common use for classes in ASP.NET is to to hold all shared business logic.
Functionality such as authentication, which is required throughout the application,
may be included in this class. A class could also be used to perform the data access
for an application—this class would work with the business logic class.
The terms “class” and “object” are normally used interchangeably, but there is a
difference between the two. An object is a class that has been instantiated, so
technically a File “object” is not an object unless an instance of it has been created. It
is simply a class—a piece of code defining a data structure with associated
functionality.
Inheritance
One of the core principals of OOP is inheritance. Inheritance is the capability of a
class to be derived from another class. In other words, a new class can start off by
“inheriting” all the properties and methods of another class and then adding its own.
This has several advantages. Namely, if a few classes are needed that all perform the
same set of functions, as well as a few of their own, a base class can be created with
these common properties and methods, and then the other classes can all inherit from
the base class and add their own properties and methods, thus reusing the common
code rather than rewriting it.
Polymorphism
You can think of polymorphism as a “capability” of inheritance, although it is actually
another OOP concept of its own that is heavily tied to inheritance. When one class is
derived from another, the methods and properties are inherited. Polymorphism
addresses the problem of modifying one or more of the inherited methods or
properties. This prevents having to skip using inheritance simply because one method
or property is not compatible.
The formal definition of polymorphism is the capability of classes to provide the
same method or property, and the code calling or using it does not have to know
which class it belongs to.
Summary
This chapter has given you a very brief introduction to the Web-related technologies
that are essential in understanding Web application development—namely, HTML,
CSS, and JavaScript. I explained the roles of client and server-side scripting, and
presented a short overview of Web servers and server-side programming.
Additionally, you took a quick look at the Visual Basic .NET language and the
changes that have been made to it, and you learned some of the core VB .NET and
.NET Framework concepts. In the next chapter, you’ll be introduced to the Visual
Studio .NET IDE and build your very first ASP.NET Web Form.
Chapter 4
Insert 0015f0401.tif
Insert 0015f0402.tif
Starting Up
1. Start VS .NET.
2. Click New Project in the Get Started section of the Visual Studio
Start Page. The New Project dialog box appears, as shown in Figure
4-3.
3. Click the Visual Basic Projects folder in the left pane of the New
Project dialog box.
4. Click the ASP.NET Web Application icon in the Templates
window.
5. Click OK.
Insert 0015f0403.tif
Figure 4-3. The New Project dialog box
I discuss what Visual Studio does to set up the project in more detail later, but
essentially all it does is create a collection of files that form the skeleton of an
ASP.NET application and set the application up in IIS.
Design View
Once you’ve created a new project, Visual Studio should look similar to Figure 4-4,
depending on the settings in the My Profile section of the Visual Studio Start Page.
Insert 0015f0404.tif
HTML View
The name “HTML view” is not entirely accurate. This view, which you access by
clicking the HTML button at the bottom of the Web Form Designer as shown in
Figure 4-5, shows the UI code for a Web Form. This code is primarily HTML,
although there are several important differences between the UI code and vanilla
HTML, which will be clearer later in this chapter and in the next chapter. Figure 4-6
shows the HTML view.
Insert 0015f0405.tif
Insert 0015f0406.tif
Insert 0015f0407.tif
Insert 0015f0408.tif
Solution Explorer
The Solution Explorer is a module that allows for solution management. A solution in
Visual Studio is analogous to a group of projects. However, the Solution Explorer is
much more than a simple embedded file manager. It provides the facility for
References to other local .NET classes. It also provides the Web References
functionality, which allows for consumption of Web Services. Figure 4-9 shows the
Solution Explorer for the current project with only one Web Form.
Insert 0015f0409.tif
File Purpose
Web.config Holds configuration information for the entire project (e.g., authentication
requirements and so forth).
Global.asax Provides the ability to add code to event handlers for events such as the start and end
of the application, the start and end of a user session, and the start and end of a request.
Styles.css This is a CSS document that is linked to by all Web Forms and holds the styles for
the entire project.
WebForm1.aspx This is the UI file for the one Web Form that is created automatically when a Web
Forms application is created. This is the file that is currently loaded in the Web Forms Designer, either
in Design or HTML mode.
WebApplication1.vsdisco This file holds information about the project for the Dynamic Discovery
specification. It deals with the discovery of Web Services exposed by the project, and it is rarely
necessary to manually edit the contents of this file.
AssemblyInfo.vb This Visual Basic file uses attributes to describe the assembly that will be created for
this project.
There is actually one more file that has been created, although it is not shown in
the Solution Explorer by default. It is the code-behind file for WebForm1.aspx.
WebForm1.aspx only holds the UI and references to event handlers for that specific
Web Form. The code that drives the page is contained in a file named
WebForm1.aspx.vb. This will be shown when the code editor is introduced.
Class View
The Class View is displayed by selecting the Class View option from the View menu.
The Class View shows all the classes that apply to the project, including those in the
project’s References and Web References, as well as classes that the project itself
owns (each Web Form is in itself a class).
The Class View is a powerful tool and provides a constructive and useful
overview of a solution. Members of classes, such as methods and properties, are
represented as child nodes in the Class View treeview, and their respective scopes are
shown through the use of icons. Figure 4-10 shows the Class View.
Insert 0015f0410.tif
Toolbox
The Toolbox in Visual Studio is the primary means of adding components to Web
Forms using the drag-and-drop paradigm. Although the Toolbox provides essentially
the same functionality it did in previous versions, it has been given a major overhaul.
Although divisions between types of components in the Toolbox was previously
available, it was rarely used and not a default feature. VS .NET’s Toolbox is split into
five sections:
* Web Forms
* HTML
* Data
* Components
* Clipboard Ring
* General
NOTE Although ActiveX/COM technologies and components are not part of the
.NET Framework in any way, tools have been provided to convert COM
components to .NET and create COM wrappers for .NET components, so a certain
level of interoperability and backward compatibility still exists.
Web Forms
The Web Forms section of the Toolbox includes visual components that you can place
onto Web Forms pages—that is, the components are used for building the UI of the
application, along with the items in the HTML tab of the Toolbox. The actual
relationships among these components, the .NET classes, and the HTML elements
that are generated when the Web Form is viewed in a Web browser is discussed in
Chapter 5. Figure 4-11 shows the Toolbox with the Web Forms section selected.
Insert 0015f0411.tif
Name Description
Label Displays text
TextBox Text user input
Button Action user input
LinkButton Action user input
ImageButton Action user input, but uses a user-definable image instead of a regular button
Hyperlink Displays a link to other pages/sites
DropDownList User input through choices
ListBox User input through choices
DataGrid Displays data from databases in grid format
DataList Displays data from databases in list format
Repeater Flexible data display mechanism
CheckBox Boolean user input
CheckBoxList Boolean user input
RadioButtonList User input through choices
RadioButton User input through choices
Image Displays an image
Panel Container object for components on a page
PlaceHolder Container object for components on a page
Calendar Displays a calendar
AdRotator Banner advertisement rotation and display
Table Displays data in table format
RequiredFieldValidator Ensures a specific field is entered
CompareValidator Compares the value of a specific field to that of another using a variety of
comparisons
RangeValidator Ensures that a field’s value is within a specific range
RegularExpressionValidator Ensures that a field’s value complies with a specific regular
expression
CustomValidator Ensures that a field’s value complies with a custom-build validation routine
ValidationSummary Displays a summary of all validation errors, either inline on the page, in a
message box, or both.
Xml Displays XML or XSL transform results
Literal Displays text, but styles are not allowed
CrystalReportViewer Displays a Crystal Report in HTML format
HTML
The HTML section of the Toolbox is the only one that is not strictly tied to .NET. The
HTML tab allows certain HTML elements to be inserted into the page. These HTML
elements are exactly that—just HTML elements. Simply HTML code is inserted into
the page. Because they are not server-side controls, they will not be available to be
manipulated by server-side code and should only be used for building noninteractive
parts of the UI or for parts where only client-side code is being used. The components
in the Web Forms tab are .NET components, and although the net result when a page
is viewed in a browser is the same, the Web Controls provide built-in functionality
and are accessible via server-side code.
Although the HTML “controls” that are inserted are not server-side controls by
default, it is possible to convert them so that they can be accessed on the server-
side, as you will be shown in Chapter 5.
Table 4-3 shows the contents of the HTML tab and information pertaining to each
item. The Code Inserted column shows the actual HTML code inserted into the Web
Form page.
Table 4-3. Code Inserted by HTML Controls
Data
As the name suggests, this section of the Toolbox contains components that deal with
interfacing with databases. More specifically, these are nonvisual components that
provide access to OLE DB–compliant and SQL Server databases through the
ADO.NET interface. The components are again all .NET classes. Table 4-4 shows the
components available in this tab and their respective functions. All these components
are nonvisual and are used either programmatically or for data-binding purposes.
Table 4-4. Toolbox Data Section
Name Description
DataSet Adds a typed or untyped dataset to the form
OleDbDataAdapter Adds a data adapter for OleDb databases to the form, which allows insert,
update, delete, and select operations to be performed on a data source
OleDbConnection Creates a database connection to an OLE DB database
OleDbCommand Creates a Command object for OLE DB databases for performing specific SQL
queries
SqlDataAdapter Adds a data adapter for SQL Server databases to the form, which allows insert,
update, delete, and select operations to be performed on a data source
SqlConnection Creates a database connection to a SQL Server database
SqlCommand Creates a Command object for SQL Server databases for performing specific SQL
queries
DataView Controls the properties of a table that controls can bind to
Components
Name Description
FileSystemWatcher Monitors changes to a specified file or directory
EventLog Manipulates both system and custom event logs
DirectoryEntry Manipulates and monitors Active Directory entries
DirectorySearcher Performs queries against Active Directory
MessageQueue Accesses the operating messaging system
PerformanceCounter Manipulates and monitors Windows performance counters
Process Manipulates and monitors Windows processes
ServiceController Starts, stops, and monitors Windows services
Timer Time-based trigger functionality
ReportDocument Manipulates a Crystal Report document
Clipboard Ring
Similar to the functionality of the Microsoft Office 2000 Clipboard toolbar, the
Clipboard Ring section of the Toolbox actually has nothing to do with inserting
components, but rather has to do with storing multiple items that would normally only
be available in the Windows clipboard, one at a time.
The Clipboard Ring is very easy to use and adding items to it as well as “pasting”
items from it involve dragging and dropping. To add text to the Clipboard Ring,
simply select it and drag it onto the Clipboard Ring window. To “paste” that text, drag
the button that was created for it in the Clipboard Ring to where the text must be
pasted. A context menu is available for removing items as well as renaming them
(through the Delete and Rename Item options, respectively). Figure 4-12 shows the
Clipboard Ring with several items added.
Insert 0015f0412.tif
General
The General section of the Toolbox is empty, save for the ubiquitous Pointer option.
This section is dedicated solely to custom third-party components that have been built
by either third-party control vendors or development teams.
Server Explorer
The Server Explorer in VS .NET may look very familiar to users of Visual InterDev.
Visual InterDev shipped with a feature allowing developers to access and manipulate
remote data sources, all in an integrated part of the IDE. The Server Explorer in VS
.NET provides this functionality and adds a whole lot of its own. A full list of
resources that you can access with the Server Explorer follows (see Figure 4-13):
* Data connections
* Crystal Services
* Event logs
* Message Queues
* Performance counters
* Services
* SQL Server databases
As you can quite clearly see, considerably more than just data access functionality
is provided. The Server Explorer virtually eliminates the requirement for multiple
copies of Microsoft Management Console (MMC) to be open, monitoring things such
as server performance. Most information that developers require about the computing
environment is available at, literally, the click of a button.
Insert 0015f0413.tif
Data Connections
Connections to OLE DB data sources, as well connections directly through the SQL
managed provider, are shown in the Data Connections treeview of the Server Explorer
(see Figure 4-14). From here, you can edit and create tables, manipulate stored
procedures, and perform many other common database tasks. Naturally, this facility
does not provide all features for all databases, but it does provide a broad variety of
functionality that will help you avoid loading separate management applications for
most routine development tasks.
This feature and its relationship to database connectivity in Web Forms
applications is discussed later in Chapter 8. .
Insert 0015f0414.tif
Crystal Services
Several editions of VS .NET feature the Crystal Reports software, for building both
complex Windows Forms and Web-based, data-driven reports. Crystal is an entirely
separate product in its own right, and it beyond the scope of this book to cover it, but
this integration with VS .NET helps Crystal developers access and administer the
reporting software more easily.
Event Logs
Developers who have only been developing applications for and using the Windows
9x platform will probably not be familiar with the concept of Event logs. Event logs
are essentially a feature of Windows NT/2000 that allows applications to write critical
moments in their execution to a central repository of application events, from which
administrators can assess the situation of a machine. Events are usually written when
something critical happens that may be of use to the systems administrator or
developer in troubleshooting. Events such as the start and termination of a critical
service are a common example.
The Event Logs tree of the Server Explorer allows developers to view a particular
machine’s logs, helping with both application debugging and troubleshooting, as well
as ensuring that the application is posting the correct events at the correct time itself.
Again, all this functionality, which is roughly equal to that exposed by the MMC
event log viewer, is integrated right into Visual Studio.
Message Queues
Message Queues are often an essential part of many enterprise applications and deal
with providing guaranteed message delivery (messages normally contain instructions,
such as operations to be performed on a database, like adding a row). Message
queuing basically involves a series of machines running Microsoft Message Queue,
which allows messages to be sent and received. The topic of message queuing to build
ultrareliable applications is beyond the scope of this book, but suffice it to say that the
Message Queues tree allows developers to easily control and monitor the messages
being sent to and received by various machines.
Performance Counters
Services
This node lists all the services available on the machine, along with their respective
status (started/stopped). Checking running services is often a common place to start
non–programmatic-related troubleshooting. Items such as the SMTP service are often
critical to an application, and the ability to quickly and easily check on and modify the
status of all the services is another useful feature exhibited by the Server Explorer.
SQL Servers
If SQL Server is installed on the target machine, all the databases available on it are
accessible through this node. You can use the Visual Studio data tools, which are
integrated database development tools inside Visual Studio, to modify most aspects of
the databases, including tables and stored procedures. This functionality is very useful
when developing database applications, as it alleviates the requirement to use the SQL
Server Enterprise Manger or Query Analyzer (or some such generic SQL query
application), as most of the required common functionality is integrated directly into
VS .NET.
Properties Window
Visual Basic developers will be immediately familiar with the Properties window (see
Figure 4-15). It is used to modify properties of visual components on forms and, more
pertinent to Web Forms, modify properties of HTML Controls and Web Controls that
are placed on Web Forms. However, its functionality is not limited to the Design view
of Web Forms, and the Properties window is still fully functionality when in HTML
view. However, it cannot be used in code view, when VB .NET code is being edited.
Insert 0015f0415.tif
1. Either start a new Web application or use the one already created at
the start of the chapter if it is still open.
2. Drag a Button control from the Web Forms section of the Toolbox
onto the Web Form in Design view.
3. Make sure that the button is selected (if it is, six black handles will
appear around it). If it is not, click it once.
4. Scroll down to the Text property in the Properties window, and
modify the property by clicking in the cell adjacent to the property
name that says “Button” and editing that text to read “Hello
world!”. Notice that when that edit field loses focus (when you click
out of it or press Enter), the Design view immediately shows the
modification.
5. Scroll down to the BackColor property and choose a color from the
drop-down list that appears for that property.
This quick introduction to the Properties window demonstrates two of the three
types of properties that have slightly different representations in the Properties
window. The first is the basic property where a value is entered, with the second
being one where a choice is given (with color values and Booleans being the most
common property values where this happens). The third is the set, or collection of
properties within one central property. The most common example of this is the Font
property, which does not have any value itself, but contains other properties to
describe the font, such as size, name, and stylistic functionality (bold, underline, italic,
and strikeout). You can easily recognize properties that are sets in the Properties
window, as they will have a small plus sign (+) next to them, denoting that you can
expand them to expose subproperties.
IDE Summary
There is much more to the Visual Studio IDE than I’ve covered in this chapter, but
you have learned about the basic dialog boxes that you will most commonly use in UI
design. As you can clearly see already, Visual Studio is far from a souped-up text
editor for writing code with some cheap WYSIWYG tools thrown in. It is a powerful,
rich environment for designing Web user interfaces. The fully integrated coding and
debugging tools will be introduced shortly to provide the full picture of what Visual
Studio is and why it is such a productive development environment.
Building a Simple Web Form
This section is intended to demonstrate how to build a simple Web Form and also to
explain some of how ASP.NET Web Forms work from the inside out. Along the way,
you’ll be introduced to several new elements of the Visual Studio IDE.
Starting Up
Creating a new Web Forms application, as described near the beginning of this
chapter, is where almost all new ASP.NET applications begin. You will learn shortly
what actually happens when a new application is created, along with the uses of the
myriad of files that are created. For now, create a new Web Forms project, and call
the project MyFirstWebApp instead of WebApplication1. Simply put, Visual Studio
now creates a comprehensive framework for the application that can easily be built
upon, along with one Web Form to start from.
NOTE There are two modes in which you can create Web Forms: Grid Layout
mode or Flow Layout mode. The difference between the two is essentially that in
Grid Layout mode, the form design is performed almost identically to form design in
Visual Basic applications. When a component is dropped onto the form, it snaps to
the nearest points on a grid, giving the UI designer almost 100 percent precision
over the positioning of controls on the form. Flow Layout mode follows a word
processor style of design. The position of controls is relative to the other controls
on the page. In this book, Flow Layout mode is used unless otherwise specified. I
present instructions for modifying the layout mode of the form shortly.
You can insert all content on the form that does not need to be programmatically
accessed by simply typing on the form, as you would with a word processor.
However, any content that needs to be programmatically accessed must be in the form
of Web or HTML Controls, which you insert by dragging and dropping from the
Toolbox.
To start with your form, add one Button and one Label Web Control to the form.
However, before doing this, you need to change the layout mode of the form. To do
this, choose DOCUMENT in the Properties window and change the pageLayout
property to FlowLayout. After you add the two controls, the Form Designer should
look similar to Figure 4-16.
Insert 0015f0416.tif
Figure 4-16. Web Form in Flow Layout mode with Button and Label Web Controls
added
After you’ve added the Web Controls, switch to the HTML view of the Web Form.
The code displayed should look similar, if not identical, to Listing 4-1.
Listing 4-1.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="WebForm1.aspx.vb"
Inherits="MyFirstWebApp.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta content="Microsoft Visual Studio.NET 7.0"
name="GENERATOR">
<meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
<meta content="JavaScript" name="vs_defaultClientScript">
<meta content=http://schemas.microsoft.com/intellisense/ie5
name="vs_targetSchema">
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<asp:Button id="Button1" runat="server" Text="Button"></asp:Button>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</form>
</body>
</HTML>
There are three points to note about this “HTML” code that has been generated.
The first is the runat attribute of the form element. The HTML standard does not
include a runat attribute for the form element, but because this file is going to be
processed before it is actually sent to the client browser, ASP.NET files can contain
syntax that is not strictly HTML. In this case, the runat attribute exists so that
ASP.NET knows to process this form for things that need to happen on the server side
before the file is sent to the client.
The second snippet of note is the <asp:button> “element.” Again, HTML does not
contain such an element. This is actually the ASP.NET UI syntax for a Web Control,
and it signals for ASP.NET to process this element on the server side. Note that this
“element” also contains a runat attribute.
Third is the <asp:label> element. Identical to the previous point, this is the syntax
used in ASP.NET to denote a Web Control—this time, a label.
End Sub
End Sub
End Class
The code between #Region and #End Region may not be visible and replaced by
Figure 4-17.
Insert 0015f0417.tif
With the exception of the control definitions and Button1 click event handler, all Web
Forms created using Visual Studio will by default have the server-side code shown in
Listing 4-2.
The Public Class WebForm1 statement defines the start of a class for the Web
Form. Note the End Class statement at the end of the code.
The Inherits statement is used to instruct the compiler to inherit all the inheritable
members (properties, methods, and events) of the System.Web.UI.Page class.
For those unfamiliar with inheritance, this essentially means that the WebForm1
class contains everything a Page class does, and then adds its own members. This is
important, as the Page class contains many members that are critical to interacting
with both the server and the client, and forms the base for all ASP.NET Web Forms.
Next is the code region. Code regions are used for grouping together related,
adjacent pieces of code. In Visual Studio this results in a collapsible region being
created.
The private InitializeComponent sub is used for any component initialization that
needs to occur. This does not currently contain any code, but this event handler is
normally used when nonvisible controls are added to the page.
The Page_Init Sub is an event handler for the Page’s Init event. Event handlers
generally take the form of ObjectName_EventName. In this event handler, the
InitializeComponent routine is called. All code that may need to be included in this
event handler should follow that call.
The Page Init event is triggered when a page is loaded, followed by the Load
event. The Page_Load method is the event following the Init event.
Finally, the Button1_Click event handler is what was created when the Button on
the form was double-clicked. This method is called when Button1 is clicked.
Adding code to the event handler is, well, exactly as you would expect it to be. Place
the cursor in the space between Protected Sub Button1_Click(…) and End Sub and
type the appropriate code in. For now, all the code will do is change the text of the
label (Label1) to, you guessed it, “Hello world!”. Type in the following code, and
pause after you type the period:
Label1.Text = "Hello world!"
Pausing after typing the period should result in a list box being displayed, as
shown in Figure 4-18.
Insert 0015f0418.tif
Insert 0015f0419.tif
Every time a new Web application is created in Visual Studio, several operations are
performed. First, a new directory is created in the IIS default site’s physical root. By
default the physical root is c:\inetpub\wwwroot. This directory is of the same name as
the name chosen for the Web application in the New Project dialog box. Second,
several files are created that form the basis of the application. These include such files
as global.asax, web.config, styles.css, as well as one Web Form file set with the same
name as the application (each Web Form consists of two files—one aspx file
containing the UI code, and one aspx.vb file containing the server-side code). Third,
Visual Studio solution and Visual Studio project files are created and placed in the
appropriate directories. Finally, a virtual directory is set up in IIS.
NOTE A virtual directory in IIS allows directories other than those in the physical
Web root to be accessed. For example, if the physical Web root was
c:\inetpub\wwwroot, and c:\somesite needed to be accessed, and that folder could
not be moved, you could set up a virtual directory so that users who visited
http://machinename/virtualdirname would actually receive content from c:\somesite.
However, Visual Studio sets up a virtual directory for each application for a different
reason—in order for events from IIS to be handled by ASP.NET on a per-
application basis, the application must be in a virtual directory.
Application Execution
1. somefile.aspx is hit.
2. ASP.NET realizes that the files in the directory have already been
compiled and this is not a first hit, so it only recompiles
somefile.aspx.
3. somefile.aspx is served up to the user.
4. All subsequent hits to somefile.aspx are served immediately.
In summary, ASP.NET is intelligent in its compilation policy and performs all
compilations without any user intervention required, thus combining the speed of
compiled environments with the flexibility and ease of use of interpreted
environments.
Summary
This chapter has given you a brief introduction to the Visual Studio .NET
environment and has guided you through creating a very simple Web application.
Before moving on, take some time to familiarize yourself more with the Visual Studio
environment, and in particular, try out the different Web and HTML Controls and
modify their properties through the Properties window and programmatically.
The next chapter details programming the common Web and HTML controls.
Chapter 5
Although the official names are “Web Server Controls” and “HTML Server
Controls”, they will be referred to as “Web Controls” and “HTML Controls”
throughout the rest of the chapter, to help the chapter read more easily.
For starters, the two entries in the .aspx file look vastly different! Secondly, and
more importantly, the second label doesn’t have a green “play” icon on its top-left
corner, like the Web Control Label does. This icon indicates that the control it is on is
a “server-side control”, with the effect that it can be manipulated using server-side
ASP.NET code. All server-side controls must include one attribute in their declaration
tags – “runat”. This attribute must have the value “server”. When ASP.NET is parsing
a page and it reaches a tag that has this attribute on it, it knows that it must make the
control available to server-side code. Notice that the Web Control Label has a runat
attribute, but the declaration for the HTML Control doesn’t. This is because HTML
“Controls” aren’t actually server-side controls by default. However, HTML controls
can obviously be made to be accessible on the server side by one of two methods –
you can either manually add the runat attribute to the control’s tag in the HTML view,
or in the designer, you can right click on the control, and choose the “Run As Server
Control” option from the context menu, as shown in figure 5.2.
[ Figure 5.2 ]
Once this option is toggled, the HTML for the control will be modified to include
the runat attribute, and the necessary entries will be made in the code-behind
(.aspx.vb) file.
Controls that can be inserted only as Web Controls (such as the Calendar control)
are made up as a combination of HTML elements, and do not have a direct HTML
equivalent, so could not exist as an HTML Control.
Web Controls are represented in the UI code in a custom format that is not valid
HTML code. This will naturally be converted to HTML when the page is loaded, but
when building Web Forms it is important to understand this.
In the first difference “Web Controls are “server-side” by default; HTML Controls
aren’t”, in Listing 5.1, the different UI code for two labels was shown – the first was
for a Label Web Control, and the second for an HTML Control label. Here is the code
for two server-side Labels:
<asp:Label id=Label1 runat="server">Label</asp:Label>
This may lead you to believe that the Web Controls set of controls includes
everything the HTML Controls set does, and then some, and this is largely true –
for the most part, there is an equivalent Web Control for all the HTML Controls,
all for but two exceptions – there is no file upload Web Control, and if you’d like to
let the user upload files to your application, you will need to use the HtmlInputFile
HTML Control. There is also no “hidden input” Web control, such as the
HtmlInputHidden HTML Control.
It is important to note that the choice to use one over the other is still largely a
matter of personal preference, and you should decide for yourself which control set
you’d prefer to use once you’ve learned the basics of both. It is obviously important
that you do know how to use both, even if you decide that in your applications you
will use only one or the other, so that you can understand code examples and the like
that may use a different method from you.
[ Figure 5.3 ]
This text can be manipulated not by using properties (because it’s not in a
control), but by using the “Formatting Toolbar”, which allows basic manipulations
such as font name, color and size, along with the ability to make text bold, italic or
underlined.
The “style” dropdown in the Formatting Toolbar can be very useful for applying CSS styles to
text (and other page content). To demonstrate this, insert the following code into the
<head> section of a UI file in the HTML View:
<style>
.mystyle
{
font-family:Arial;
font-size:10pt;
}
</style>
Switch back to Design View and select the text you want to apply the style to. When you
choose an option from the “style” dropdown in the Formatting Toolbar, the style will be
applied to the text, as shown in Figure 5.4:
[ Figure 5.4 ]
With the ability to display formatted text without a Label control, the inclusion of
it may seem worthless, until you actually need to modify the text (or the formatting) at
run time. Text that is inserted at design time by simply typing in the designer cannot
be modified at run-time (at least not easily), which is obviously a major issue, or
would be, if the Label control were not available. The Label control is a server-side
control and can be manipulated using server-side code. Its text can be changed, along
with formatting options and many other properties, which will be shown shortly.
Labels at Design-Time
Labels are very easy to use and manipulate, since they don’t really contain much
functionality. After all, all they do is display text. However, most of the properties of
labels are available to the other, more advanced Web and Html Controls, so they’re a
good starting point and will introduce several properties that can be used on other
controls. To show how the Html Control Label and Web Control Label are used,
create a new Web Form and insert a Label Web Control and Label Html Control onto
the form by dragging-and-dropping from the Toolbox. To make the Html Control
Label run on the server-side, you need to right-click it and choose “Run As Server
Control”. Figure 5.5 shows the designer after the controls have been added and the
Html Control converted to run as a server-side control.
[ Figure 5.5 ]
If the page were loaded in a browser now, the two Labels would look identical.
However, when changing the properties of and manipulating the labels, the
differences between the two are more apparent.
At the moment, you might be wondering why the designer puts a grey border
around the Html Control and not the Web Control Label. This is because by default
the Html Control has a specific width in pixels set and the border shows the
physical size of the control, whereas the Web Control Label is “free form” and will
extend automatically according to the size of its contents. This is because of the
different ways that the two types of label are rendered in HTML, and is a technical
constraint of HTML.
Changing the properties of controls is very simple. As was shown in the previous
chapter, the Properties Window in the IDE will display editable properties for the
control that is currently selected. Selecting the Web Control Label will reveal
numerous properties, including the following:
[ Table 5.1 ]
Property Description
ID Sets the name/ID of the control
BackColor Sets the background color of the control
BorderColor Sets the border color of the control (if a border is enabled)
BorderStyle Sets the style of a border for the control.
BorderWidth Sets the width of a border for the control
CssClass Sets the CSS class that will be applied to the control
EnableViewState Sets whether the state of the control is maintained across postbacks or
not
Font A set of properties describing the font (including name, size etc)
ForeColor Sets the color of the text
Text Sets the actual text to be displayed
Visible Sets whether the control is visible or not
These properties can be changed both at design-time (using the Properties
Window, or by going into the HTML View and manually editing the properties) and
at run-time (in server-side event handlers). The properties provide quite a lot of
control over the appearance of the control, and some also influence its functionality.
Most, if not all, of these properties are also available on the rest of the Web Controls.
Change the ID property to lblWebControl and modify the appearance of the
control using the Properties window, as shown in Figure 5.6.
[ Figure 5.6 ]
Notice that when the properties controlling visual aspects of the control are
modified, the control is updated in the designer. Edit the Text property, so that “Hello
World from a Web Control Label!” is displayed.
Select the Html Control Label, and you will notice that an almost entirely different
set of properties is available, and a much smaller list at that. Table 5.2 lists the three
most important ones.
Property Description
ID Sets the name/ID of the control
Class Sets the CSS class that will be applied to the control
Style Sets the inline CSS that will be applied to the control
Besides the formatting properties, possibly the most notable omission is that of the
“Text” property, which was used to set the text to be displayed in the Web Control
Label. In the Html Control Label, the content of the control is actually stored “inside”
the control (in the HTML markup, the text is shown between the opening and closing
tags), and although a property is exposed for modifying this text at run time, at design
time, the text is modified by selecting the control, and then clicking on the current
text, as shown by figure 5.7:
[ Figure 5.7 ]
Because the text is now being edited in the designer, rather than the Properties
window, the Formatting Toolbar can be used to modify the appearance of the text
(when a Web Control Label is selected, the Formatting Toolbar is disabled). The
formatting is applied directly to the text using raw HTML (which is also included
within the opening and closing tags of the Label), rather than using properties, as is
the case with the Web Control Label. Formatting text is simple – select the text, and
choose the appropriate formatting options, such as bold, italics, color or font name.
The control also has a “style” property, so it is possible to format the control using
an inline style sheet. The Visual Studio designer provides a very useful “Style
Builder” tool for this purpose, and is an alternative to using the formatting toolbar. To
open the Style Builder, select the “style” property in the Properties window, and click
on the “ellipsis button” on the right side of the item. Figure 5.8 shows the Style
Builder after a few options have been changed to format the text as navy blue in color,
Arial font and bold.
[ Figure 5.8 ]
Using either the Formatting Toolbar, or the Style Builder, the same effects can be
achieved with the Html Control Label as with the Web Control Label using its
properties. For the purposes of this section, change the ID of the Html Control Label
to “lblHtmlControl”, and modify its contents so that it displays the text “Hello World
from an Html Control Label!” The control will probably need to be resized to fit the
text on one line – the six “handles” surrounding the control when selected can be
dragged using the mouse pointer to resize it.
After modifying the properties and styles of the two controls, figure 5.9 shows the
page loaded in Internet Explorer.
[ Figure 5.9 ]
As you already know, ASP.NET pages, or “Web Forms”, are split into two parts – the
user interface, which is contained in a file with the extension .aspx, and the code-
behind file containing all the server-side code for manipulating the objects in the UI,
which is contained in a file with the extension .aspx.vb. There are several ways to
switch from the UI designer to the server-side code, but the easiest is simply to right-
click anywhere on the form and choose “View Code” from the context menu, as
shown in Figure 5.10. You could also choose “View Code” from the context menu of
the UI file in the Solution Explorer.
[ Figure 5.10 ]
The “default” code that is displayed in the code-behind file (the code that is
displayed for a blank Web Form) was explained in the previous chapter, but briefly, a
class is created to represent the page, and each control on the form is declared as a
variable so they can be accessed programmatically. One event handler is also
automatically inserted – Page_Load. This is called each time the page is loaded, and is
the perfect place to put in any code to initialize control values. The procedure looks
like this:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
End Sub
Although typically you’d set the text for a Label to display in the UI file rather
than in the Page_Load event, it is certainly possible to do so. Listing 5.2 shows the
Page_Load event handler that will change the text of the two Label controls (Web and
Html) programmatically when the page loads.
[ Listing 5.2 ]
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
lblWebControl.Text = "This is a Web Control Label"
lblHtmlControl.InnerText = "This is an Html Control Label"
End Sub
The text of the Web Control Label will display “This is a Web Control Label”,
and the Html Control Label will display “This is an Html Control Label” when the
page is viewed in the browser. However, it is important to take note that for the Web
Control Label, the Text property was used, whereas the Html Control Label exposes
the InnerText property, and doesn’t have a Text property.
For both types of Label, all the properties listed in tables 5.1 and 5.2 can be
programmatically modified by the respective control. Insert the following line into the
Page_Load event handler, and the Web Control Label’s background color will be
silver when the page is viewed again:
lblWebControl.BackColor = Color.Silver
All “Color” properties (eg. BackColor, BorderColor, ForeColor etc) on all controls
(not just labels) take a value of type Color. It is possible to generate custom colors,
but generally you will choose a color from the selection in the
System.Drawing.Color structure. These include Color.Black, Color.White,
Color.Red, Color.Green, Color.Blue, Color.Gray and so forth. See the .NET
Framework SDK documentation for a full list.
The Font property of the Web Control Label is however slightly different from the
rest of the properties in that rather than accepting a single value, it actually contains a
set of properties that describe the font. This is because the property is of type
FontInfo, which is a class that is used to describe a font through its own properties.
Listing 5.3 demonstrates the use of a couple of the Font properties.
[ Listing 5.3 ]
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
lblWebControl.Text = "This is a Web Control Label"
lblWebControl.Font.Name = "Courier New"
lblWebControl.Font.Size = FontUnit.Point(16)
lblHtmlControl.InnerText = "This is an Html Control Label"
End Sub
This would change the font to “Courier New”, with a size of 16pts. The
Font.Name property takes a string containing the name of the font that must be used,
although the Font.Size property is slightly more complicated. It requires a FontUnit
object be assigned to it. The System.Web.UI.WebControls.FontUnit structure
contains a method, Point(), that should be passed an integer value representing the
point-size of the font, and the method returns an instance of the FontUnit structure to
represent this value.
Table 5.3 lists the FontInfo class’s properties that can be used to manipulate the
Label’s font display using the Font property.
[ Table 5.3 ]
Figure 5.11 shows the result in Internet Explorer when the page is loaded.
[ Figure 5.11 ]
Table 5.4 lists some of the more common colors that are included for use in the
System.Drawing.Color structure.
[ Table 5.4 ]
Colors can be created by mixing certain amounts of red, green and blue. In the RGB
color model, the amount of the red, green and blue “color components” is specified
on a scale of 0 through 255, where 0 is the least and 255 is the most. For example,
to get a shade of purple, red and blue must be mixed, but no green, so an RGB
value of 200, 0, 200 would define a purple color.
Although less abstracted than the Web Control Label method of formatting text,
this still works. However, there is one particular major pitfall that will be encountered
when using this method – if the formatting of the control is set via CSS (i.e. the Style
Builder was used to format the control at Design Time), there is no way to
programmatically modify those particular settings. The Label does expose a Style
property at run-time, but this is read-only, so cannot be changed. The issue lies in the
precedence that HTML gives to CSS over regular HTML formatting tags – when CSS
is used, HTML will always use the formatting provided in the style over any
formatting using HTML tags. If however the style doesn’t specify a value for a
particular type of formatting, the HTML formatting tag will be applied. For example,
if the style specifies that the color of the text must be red, and that it must be bold,
there is no way that the color can be changed or the text can have the bold removed,
but it will still be possible to make the text underlined and change the size of the font.
Needless to say, this is a very significant weakness in the Html Control Label, and
as such it would probably be better to simply using the Web Control Label when a
Label is needed.
If you need to remove a Label from the form (or any other type of control), you
simply right-click on it in the Web Form designer, and click “Delete” on the context
menu that appears. Take note though, that even if you remove a control from the Web
Form, any references to it in the server-side code will not automatically be removed,
so if you remove a control that is programmatically modified in the Page_Load event
handler, you will need to manually remove the lines in that event handler that involve
that specific control, otherwise the application will return an error saying that it can’t
find the control you’re attempting to reference when you next try to run the
application. Incidentally, the same applies to when you rename Label controls (by
changing their ID properties in the Properties Window), as any references to the old
control names in the code-behind file will not be changed automatically, and you will
need to manually modify the code to ensure that the new names are used.
End Sub
This will make the button’s caption change to “I was clicked!” when the user
clicks on it. Create an event handler for the Html Control Button, also by double-
clicking on it in design view, and then place the following line into it:
btnHtmlControl.Value = "I was also clicked!"
Similarly this will make the Html Control Button’s caption change to “I was also
clicked!” when it is clicked. Load the page in the browser and click on the first button.
Figure 5.13 shows what should be displayed.
[ Figure 5.13 ]
This is probably the result that both ASP and VB programmers would have
expected, but clicking the “Html Control Button” may just result in a very pleasant
surprise for the previous ASP programmers. Give it a try. Nothing momentous
happened, as the Html Control Button’s caption changed as expected, and the VB
programmers might be wondering what the pleasant surprise is. When the 2nd button
was clicked and its caption changed, the 1st button’s caption didn’t revert back to
“Web Control Button”, but rather stayed as “I was clicked!” Previously in ASP it
would have been necessary to manually re-assign that value to the button, but
ASP.NET automatically maintains the state of controls across postbacks, so there’s no
longer any need to re-assign values to controls once they’ve already been assigned.
The ASP programmers amongst you might be wondering where the submit
button is on the form, and how the form submits without a submit button. In
fact, the answer in the case of the Button Web Control is quite simple. As you
know, all ASP.NET Server Controls output regular HTML to the browser when
they’re rendered - not specialized markup, or anything fancy, just plain HTML.
The Button Web Control actually renders as an HTML submit button, using
the <input type=submit> tag. If you view the source of the page in Internet
Explorer, you’ll be able to verify this.
However, the Button Html Control is slightly more complicated – it renders as
an HTML button using the <input type=button> tag, which you, if you’re an
HTML guru, will know doesn’t submit the form automatically. In this case,
ASP.NET creates a client-side JavaScript that submits the form when the
button is clicked.
So, whether you’re using the Button Html or Web Control, the form will be
submitted in a similar fashion to if a regular submit button was used.
Understanding the page cycle is very important and will help you develop more
robust ASP.NET applications. This section will give you a very brief overview of
what’s actually happening when a button is clicked and a postback occurs.
When the page is first loaded in the browser, the following steps occur:
This is an encoded string containing the values of the variables that ASP.NET
must restore on the next postback. When you click the other button and ASP.NET
loads the page, it receives this string in the request, decodes it and assigns the value to
the 1st button so that it’s value is “remembered”, even though the page didn’t
explicitly restore it – ASP.NET handled it in the background. In the next chapter we’ll
advance on this and show how to put your own custom information into ViewState.
Page.IsPostBack
Quite often it’s very useful to be able to perform operations only the first time the
page is loaded. Normally these will occur in the Page_Load event, and will consist of
setting control properties or other variable values. However, code in the Page_Load
event will ordinarily be executed every time the page is loaded, not just the first time,
which could obviously have very strange effects on the functioning of the application
as every time a button is clicked, or a postback occurs otherwise, the default values
will be reset. However, the Page object includes a property, “IsPostBack” that returns
a Boolean value – true is the page was loaded through a postback, and false if not.
Since a postback occurs (normally through a button being clicked) when a page sends
a request back to the server for itself in order to be updated, the only request that is
not a postback is the initial, first request for the page. So it is therefore possible to
ascertain whether the page is being loaded for the first time by using the
Page.IsPostBack property. Listing 5.8 shows a sample Page_Load event handler
where the code will only be executed the first time the page is shown. In this code, a
button, btnWebControl, has its caption set.
Listing 5.8
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
If Not Page.IsPostBack Then
btnWebControl.Text = "Default Value"
End If
End Sub
Take note that the If condition uses the Not operator, as the page must not be a
postback. Since if it was, this would mean that the page isn’t being loaded for the first
time. This technique is a very useful tool, and although it may not seem so now since
you can easily set control properties in the IDE, and there is no need to set them
programmatically, it is often necessary to do so when inserting values from a
database, for example, as these obviously cannot be set at design time.
Figure 5.14 – The Web Forms Designer with several controls added
Using the Properties Window, set the ID property of the controls to
txtWebControl, txtHtmlControl, lblDisplay and btnSubmit respectively. Change the
Text property of lblDisplay to nothing, and of btnSubmit to “Submit”. Double click
on the Button so that a Click event handler is hooked up, and the server-side code is
displayed. We’ll now insert code so that when the button is clicked, the label will
display the contents of both TextBox controls. Insert the following line of code into
the event handler:
lblDisplay.Text = "txtWebControl contains " & txtWebControl.Text & ", and "
_
& txtHtmlControl contains " & txtHtmlControl.Value
As with the Label controls, accessing the contents of the TextBox controls is
slightly different for the Html and Web Control varieties – the Web Control exposes a
Text property for this purpose, and the Html Control has a Value property. However,
in this example we’re extending the functionality slightly by not simply assigning the
text of the label to be the value of a TextBox, but are concatenating, or joining, the
values from both TextBox controls together, along with a portion of static text. The
ampersand (&) is the concatenation operator in Visual Basic.NET and can be used to
join various strings together. Figure 5.15 shows the page loaded after values have
been entered into the two TextBoxes and the button has been clicked.
In the above example, we join several strings using the ampersand. However, in
VB.NET this is an extremely inefficient way of joining strings. If you’re building a
small application that isn’t going to be used by many people, then this performance
hit isn’t going to be noticeable at all. However, if the application is expecting many
users, or the string joining procedure is occurring inside a loop, then you should rather
use the StringBuilder class to join the strings, as I’m about to show you.
We’ll continue with the example of joining a few static strings and the contents of
two TextBox controls, so delete the line currently in the btnSubmit click event
handler, and we’ll rewrite the code to perform the string joins using a StringBuilder
object. Listing 5.9 shows the code, and we’ll dissect it line by line.
Listing 5.9
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
Dim objStringBuilder As New System.Text.StringBuilder()
objStringBuilder.Append("txtWebControl contains ")
objStringBuilder.Append(txtWebControl.Text)
objStringBuilder.Append(" and txtHtmlControl contains ")
objStringBuilder.Append(txtHtmlControl.Value)
lblDisplay.Text = objStringBuilder.ToString()
End Sub
In the first line, we are creating a new StringBuilder object, and a reference to it
called objStringBuilder. As you know, all classes in .NET are organized into
namespaces, and the StringBuilder class is in the System.Text namespace. Now that
we have an instance of the StringBuilder class, we can work with it. The
StringBuilder’s Append method has many overloaded versions (i.e. it can accept
many different types and numbers of parameters), and the one we are using accepts
only one string as a parameter. As the name suggests, the StringBuilder is used to
build strings, and when the Append method is called, it adds the string in the
parameter to the string that is being created (but without the performance hit of using
the ampersand to concatenate the strings).
Since our string join is fairly simple, we only need to use the Append method.
However, Insert and Remove methods are also available to, as their names suggest,
insert substrings into the string that is being built at specific positions, and remove
parts of the string being built.
Once the string has been built, calling the StringBuilder’s ToString method returns
the string. In this case, the string is assigned to the lblDisplay Label’s Text property.
When the page is viewed in IE and the same data is inputted, exactly the same result
will appear as when the string was created using the ampersands. It’s easy to be lazy
and avoid using the StringBuilder for string joining since it requires more effort than
the operator method, but remember that the performance of the StringBuilder over
concatenation using the ampersand operator is exponentially greater when many joins
are being performed, so for real applications, try to avoid using the ampersand for
joins wherever possible.
When you’re getting input from a user, be it with a TextBox, or any one of the other
input controls we’ll discuss in this chapter, you’ll typically include a caption next to
the control so that the user knows what data or information he/she should provide. To
make the layout more aesthetically pleasing, it’s good practice to use a 2-column table
– the 1st column for the captions, and the 2nd column for the input controls.
Fortunately Visual Studio.NET provides functionality in the designer that makes
designing tables simple.
To practice, we’re going to take the page we’ve created in the first part of this
section and modify it so that all the controls appear within a table, and each TextBox
control has a caption.
Firstly, we need to add a table to the page. To do so, move the cursor in the
designer to the top left of the page (the easiest way to do this is by pressing Ctrl-
Home on your keyboard). In the main menu, click “Table”, then “Insert”, then
“Table”. A dialog should appear allowing you to configure the table that will be
inserted. Change the “Rows” to 4, the “Columns” to 2, and the “Border size” to 0.
Figure 5.16 shows what the dialog should look like after changing those settings.
Figure 5.16 – Insert Table Dialog
Click “OK”, and the table should be inserted at the top of the page. Click in the
first cell (top left), and type in “WebControl”. Click in the left cell in the second row
and type “HtmlControl”. You will notice that the second column has suddenly shrunk
– this is simply because of the way in which tables are rendered in HTML, and when
content is inserted the column with “grow” again. Drag and drop the first TextBox,
txtWebControl, into the second cell in the first row. You will notice that the column
has resized to accommodate the control. Drag and drop the rest of the controls into the
second table column, each in their own cell, one underneath each other. Figure 5.17
shows how the page should look in the designer.
Figure 5.17 – Layout out an input form using a table
By default the table has a set width of 300 pixels, which is useful when designing
the form since the cells are a reasonable size and are easy to drop controls into or
move the cursor into to type captions (take note that you simply type in the text in the
designer for captions, and don’t use Label controls). However, once you’ve finished
designing, it is better to simply remove the table’s default width and let the table
automatically adjust its size according to its contents. To do this, select the table from
the Properties dropdown in the Properties dialog, as shown in Figure 5.18, and clear
the text in the “width” property.
Figure 5.18 – Selecting a specific control in the Properties Window
You can now run the application, which should display the same message as
before when the Submit button is clicked, but now the fields are nicely in line, and
have captions, labelling each one.
The Table Web and Html Controls are covered later in the chapter, which allow
you to programmatically modify the table, but for layout purposes the plain, non-
server control table is adequate.
Deleting a Table
If you need to delete a table in the designer, the easiest way to do it is by placing the
cursor to the immediate right of the table and pressing, “Backspace” on your
keyboard. Remember that if you delete a table, you automatically delete all the
controls contained in the table, but any references to the controls inside the server-side
code will not automatically be removed, and needs to be done manually.
TextBox controls can be used to gather any type of data – it might simply be a string,
but it could be an Integer, or a floating-point number, or a date. Since the Text and
Value properties of the TextBox controls automatically assume that the contents of
the control is a string, in order to be able to work with and manipulate inputs that
aren’t strings, they must first be converted to their correct data type. Likewise, when
results are displayed in Label controls (or in any other control), a string is normally
expected, so another conversion must take place to convert the result from its data
type to a string before it can be displayed.
Using our existing form containing two TextBox controls, one a Web Control and
another an Html Control, a Label and a Button, we’ll build a tool that adds two
Integers, entered in the TextBox controls, together, and displays the result in the
Label. It’s a very simple task, but it requires that three type conversions take place –
both inputted numbers must first be converted to type Integer, and then once the result
of the addition has been calculated, that must be converted to a string before it can be
displayed using the Label.
In Visual Basic.NET there are two ways to perform type conversions (and in some
cases, three). The first is specific to the VB.NET language and won’t work in any of
the other .NET languages, and the second is through using a class provided for this
specific purpose in the .NET Base Class Library, which is available to all .NET
languages.
Visual Basic and VBScript developers alike will recognise the intrinsic VB
functions such as CInt, CStr and so on. These are again available in Visual
Basic.NET, and work in exactly the same way as before, so you should feel right at
home with them. For those of you who haven’t encountered these conversion
functions before, they all accept one parameter of any basic type (such as string,
Integer, Long, Date etc), and will return that variable cast as the type that their
namesake suggests. For example, the CInt function could have a string passed into it,
and it will convert that string to an Integer and return that value. Likewise, CStr could
have an Integer passed into it, and the string representation of the Integer will be
returned. Table 5.5 shows the full list of these functions.
Table 5.5 – Intrinsic VB.NET Type Conversion Functions
Try loading the page and entering in two numbers to add and clicking “Add”.
Figure 5.19 shows a sample of how the page should look.
Figure 5.19 – Adding two Integers and displaying the result
Your first reaction may be, “Why are there 7 lines of code just to add two
numbers together?” However, the code could easily be reduced to a one-liner, but it’s
in an extremely verbose form here to separate each conversion that needs to take
place. First, three variables of type Integer are declared – they will store the two
numbers to be added and the answer. Second, a string variable is declared, which is
where the answer will be stored in string form. Next, the first number is converted
from a string in txtNumber1.Text to an integer using the CInt function, and the result
is assigned to the variable intNumber1. Similarly the number in txtNumber2.Value (to
show that these functions work with strings from any control) is cast as an Integer and
assigned to intNumber2. intAnswer is then assigned as the sum of intNumber1 and
intNumber2. The next line uses the CStr function to convert the intAnswer Integer to
a string, which is assigned to the strAnswer variable, and this is assigned to
lblDisplay’s Text property.
Obviously all these variables aren’t required, since they’re all only used once each
and don’t need to be manipulated multiple times, so this whole code block can be
condensed into the following line of code:
lblDisplay.Text = CStr(CInt(txtNumber1.Text) + CInt(txtNumber2.Value))
Using the .NET Framework conversion functions is equally easy. They are all
stored as static, or shared, methods of the System.Convert class, and operate very
similarly to their intrinsic VB.NET counterparts. Table 5.6 lists the most commonly
used methods in Convert class.
Table 5.6 – Commonly used System.Convert Type Conversion Methods
As you can see, the numbers from txtNumber1.Text and txtNumber2.Value are
converted into Integers using the Convert.ToInt32 method (VB.NET Integer variables
are 32-bit Integers), and the Convert.ToString method is used to convert the answer
back to a string. Again, this can also be done as a one-liner, as follows:
lblDisplay.Text = Convert.ToString(Convert.ToInt32(txtNumber1.Text) +
Convert.ToInt32(txtNumber2.Value))
Data type conversions are very important in .NET programming, since VB.NET
and the other .NET languages ensure type safety by requiring that for the most part
type casting should be done explicitly, either using the VB.NET-specific functions or
using the Convert class’ methods.
There are two pitfalls that you should watch out for when typecasting though.
Firstly, you may encounter problems, particularly with the number data types, when
the type you’re converting can hold a larger value than the type you’re converting to.
For example, if you convert a 32-bit Integer to a Byte (8-bit Integer), or a 16-bit
Integer, it is very possible that you’ll receive an overflow error because the number
you’re trying to cast into a 16-bit or 8-bit Integer is larger than the capacity of that
particular data type. The second problem you may encounter is that some data types
have certain restrictions about what they can hold that others don’t, and this may also
cause errors to be raised. For example, a string containing “Abc” obviously can’t be
converted into an Integer. Another important point to remember is that when
converting from a floating-point number type to an Integer type, the decimal point
obviously can’t be kept and is rounded. Chapter 7, “User Input Validation” shows you
how to ensure that users enter data correctly, so if an Integer is expected, the user
cannot enter anything except for an Integer, for example.
If when you type out the code below, and all references of the txtHtmlControl
object are underlined in blue, it probably means that you’ve forgotten to right-click
the TextBox Html Control in the designer and choose “Run As Server Control”.
When a statement is underlined in blue in the code editor, it means that VS.NET
doesn’t recognize the statement because it hasn’t yet been defined. In this case,
since the TextBox Html Control hasn’t been set to be available for manipulation on
the server side, a declaration for it has not been added to the top of the Page class,
and therefore VS.NET can’t find a reference to it.
Listing 5.12
Private Sub btnSwap_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSwap.Click
Dim strTemp As String
strTemp = txtWebControl.Text
txtWebControl.Text = txtHtmlControl.Value
txtHtmlControl.Value = strTemp
End Sub
Figure 5.20 shows the two multi-line inputs, after text has been entered and then
swapped.
Figure 5.20 – The Multi-line Text Input Controls
Obviously this isn’t how you’d implement a ‘real’ login, which entails quite a lot
more effort – this simply demonstrates the use of the password field, which is a
small part of building a login and authentication system.
The Checkbox
The CheckBox control allows the user to provide a yes or no (checked or unchecked)
input, and is most often used when the user should be able to choose one or more
options from a list pertaining to a specific topic. For example, if the user is asked to
choose his/her favorite food(s) from a list, and should be able to choose more than
one, then using CheckBox controls would be a possibility for handling the input, since
each type of food (e.g. Steak, chops) could have its own CheckBox, and the user
would be able to check each type of food that they like.
The Web Control and Html Control varieties of the CheckBox are very similar,
although there are a few minor differences. To demonstrate the two, we’re going to
build a page that allows you to modify the formatting of text in a Label Web Control.
In the designer, add a Label, lblTitle, and set its Text property to “Title”. Below it,
add two CheckBox Web Controls, underneath each other. Set the first one’s ID to
chkBold and the second’s to chkItalic, and set their Text properties to “Bold” and
“Italic” respectively. Next, add two CheckBox Html Controls, also underneath each
other, and set their IDs to chkUnderline and chkOverline respectively. The Html
Control CheckBox doesn’t have a Text property, so to add a caption to the controls
you need to move the cursor to the right of the control and type the caption there.
Give the first control the caption, “Underline” and the second one, “Overline”.
Finally, add a Button Web Control, btnSubmit, with Text property “Submit”. Figure
5.22 shows how the form should now look in the designer.
Figure 5.22 – CheckBox controls in the designer
Double click the Button to create the Click event handler for it. In this event
handler, the code must update the Label according to which options have been chosen
for the formatting with the CheckBox controls. Take note that all of the options are
Boolean properties, so if the corresponding CheckBox for a property is checked (i.e.
True), then that property of the Label must be set to true. Because of this,
implementing the functionality for the page is actually very simple, as shown in
Listing 5.14.
Listing 5.14
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
lblTitle.Font.Bold = chkBold.Checked
lblTitle.Font.Italic = chkItalic.Checked
lblTitle.Font.Underline = chkUnderline.Checked
lblTitle.Font.Overline = chkOverline.Checked
End Sub
Both the Web Control and Html Control CheckBoxes expose a “Checked”
property, of type Boolean, that is True if the CheckBox is checked, and False if it is
not. Therefore in the first line, if chkBold is checked, then the Label’s Bold property
is set to True, and if it isn’t then it is set to False, because the chkBold.Checked
property would be false. Figure 5.23 shows the page in action.
Figure 5.23 – CheckBox controls manipulating the Font property of a Label
AutoPostBack and the CheckedChanged and ServerChange
Events
One of the features that many Web Controls have that Html Controls do not is the
ability for the control to automatically cause a postback (i.e. submit the form) when it
is changed. In fact, we’ve actually already covered a control that has this functionality
– the TextBox Web Control. However, it is not normally used with TextBox controls
as there aren’t very many constructive uses for this feature with them, but when
specific choices are involved, such as with CheckBoxes, as well as Radio Buttons and
DropDowns, automatic postbacks can prove to be very useful.
To demonstrate the AutoPostBack, we’re going to make the page automatically
update itself every time one of the Web Control CheckBoxes is changed. Firstly, to
enable AutoPostBack, set the AutoPostBack property of both Web Control
CheckBoxes to True. Since the Button isn’t going to be clicked, its Click event isn’t
going to be raised, so the code to make adjustments to the Label’s formatting must go
elsewhere. The CheckBox control exposes a CheckedChanged event, which fires
during a postback if the Checked property of the control has changed since the last
time the page was loaded. To create an event handler for the CheckedChanged event
for the first CheckBox, simply double click it in the designer. This event handler only
pertains to the chkBold CheckBox, so insert the following line of code in it:
lblTitle.Font.Bold = chkBold.Checked
Switch back to the designer and double click on chkItalic, and place the following
line in the event handler that is created for its CheckedChanged event:
lblTitle.Font.Italic = chkItalic.Checked
Now when you load the page in IE and click either of those two CheckBoxes,
you’ll notice that the page automatically ‘posts back’, and the changes are visible in
the Label without having to cause a postback by clicking the Button.
The AutoPostBack functionality is particularly useful in longer forms where a part
of the form might rely on a choice that is made further up. For example, a form might
exist where the user is asked to choose his/her favorite type(s) of magazine (e.g.
Current Affairs; Humour; Lifestyle etc.), and then based on what they chose there,
they could be asked what magazines they purchase regularly later on, with the choices
of magazines limited to those in the genres that they selected. You’ve probably come
across this before during your travels on the web, but with ASP.NET, all the hard
work is done for you, so implementing a form like this isn’t difficult at all, as will be
shown with the next control to be overviewed, the CheckBoxList.
However, before you decide to implement all your forms using Web Controls with
AutoPostBack enabled, bare in mind the performance cost of the postback to the user
– the user doesn’t enjoy waiting for pages to load at the best of times, so you
shouldn’t use the AutoPostBack feature unless you’re actually going to add enough
value to what the user is doing to offset the performance hit. If using an
AutoPostBack is going to make the user experience better, go for it by all means, but
don’t use AutoPostBacks needlessly where they’re not really essential and will only
frustrate users with slow connections.
Even though the CheckBox Html Control doesn’t provide AutoPostBack
functionality, it does have an event that fires during postbacks if it has changed since
the last postback, namely the ServerChange event. Likewise, even though the
CheckBox Web Control can automatically post the page back to the server when it is
changed, it doesn’t have to (simply leave the AutoPostBack property set to False), but
its CheckedChanged event will still fire during a postback if the control’s Checked
property has changed, even though AutoPostBack is not enabled.
To demonstrate the Changed events on both the Html and Web Controls, without
AutoPostBack, start by reverting the AutoPostBack property on chkBold and chkItalic
back to False. The CheckedChanged event handlers for those two controls have
already been created, so move on to the two Html Controls. Double click on
chkUnderline, and an event handler for the ServerChange event should be created.
Insert the following line into it:
lblTitle.Font.Underline = chkUnderline.Checked
Switch back to the designer and create a ServerChange event handler for
chkOverline and insert the following line:
lblTitle.Font.Overline = chkOverline.Checked
Now that all the CheckBox controls are handling the formatting that they are
responsible for in the Label, there’s no need for the Button’s Click event handler, so
you can either delete the contents of the event handler, or the entire subroutine itself.
When you load the page, you’ll find that it now operates identically to how it did
originally – proof, if it was ever needed, that there’s almost always more than one
way to do something in programming! There is actually a slight difference between
the two that doesn’t effect the user, but exists nonetheless though – using the first
method, every time the Button is clicked, all of the 4 font properties on the Label are
assigned, even though they may not necessarily change. However, using the “Change
event” method, a font property is only ever assigned when the corresponding
CheckBox is changed, because the CheckedChanged and ServerChange events only
fire when it does.
The CheckBoxList
The CheckBoxList is a Web Control (and doesn’t have an Html Control
implementation, since it doesn’t exist as a pure HTML element) that displays a list of
checkboxes. The power of this control is that it allows you to programmatically access
the list, so you can dynamically add, edit or remove checkboxes from the list, as well
as being able to loop through them, manipulating each checkbox item as you go
along. This provides a great deal of flexibility and is especially useful when it is
necessary to change the options available to the user dynamically, according to other
choices that they have made previously.
We’ll start off by creating a CheckBoxList with several options manually at
design-time. To do this, place a CheckBoxList control onto the page, and set its ID to
lstMagazines. The control will be used to display a list of magazines, and the user will
be able to select which ones he/she purchases regularly. To add checkboxes to the list,
select the Items property in the Properties Window, and click on the button to the
right of it that appears when the property is selected. The ListItem Collection Editor
should appear. Click “Add” to add a new item to the list, and in the right pane, you
can choose whether the checkbox item will be selected by default, and provide the
Text and Value properties for it. Leaving the Value as the same as the Text property is
perfectly adequate (once you move on to creating a new item, the editor will
automatically fill in the Value to be the same as the Text if you didn’t enter a value
into it), unless you have a specific reason for not doing so. However, when using the
CheckBoxList where the choices are retrieved from a database, you will normally use
the Value to store the identity (ID) field from the database table. Figure 5.24 shows
the Collection Editor after a few items have been added.
Figure 5.24 – The ListItem Collection Editor
Using the Up and Down arrows to the right of the left pane, you can modify the
order of the checkboxes, and you can remove the selected item by clicking on the
“Remove” button. Once you have finished adding items, click on the “OK” button to
close the editor. You will now notice that the CheckBoxList on the page has been
updated to show the checkboxes that you have entered in the Collection Editor.
Add a Button Web Control, btnSubmit, to the page, set its Text property to
“Submit” and place it below lstMagazines. Below it, place a Label Web Control,
lblSelected, with its Text property set to nothing. When the Button is clicked, the
Label will show a list of all the items that are selected in lstMagazines. To do this,
create a Click event handler for btnSubmit by double clicking it, and insert the code in
listing 5.15.
Listing 5.15
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
Dim strSelectedOptions As String = ""
Dim i As Integer
For i = 0 To lstMagazines.Items.Count - 1
If lstMagazines.Items(i).Selected Then
If strSelectedOptions <> "" Then
strSelectedOptions = strSelectedOptions & ", "
End If
strSelectedOptions = strSelectedOptions & lstMagazines.Items(i).Text
End If
Next
If strSelectedOptions = "" Then
lblSelected.Text = "No options selected."
Else
lblSelected.Text = "Selected options: " & strSelectedOptions
End If
End Sub
Before assigning any value to the Label, the procedure stores the list of selected
items in a String variable, strSelectedOptions, declared in the first line. The crux of
the procedure is in the For loop. The loop iterates through all the items in the list –
take note that the Items.Count property returns the number of items in the list, but the
indexes of the items in the list are 0-based, so 1 is subtracted. The Items property of
the CheckListBox controls contains the collection of the items in the list (a collection
of System.Web.UI.WebControls.ListItem objects), and it is possible to access an
individual item in the list by using the statement CheckBoxList.Items(itemindex).
Assuming that a corresponding ListItem for the itemindex passed into the paramatized
Items property exists, this statement will return a reference to a ListItem object, which
can then be manipulated. In the first If statement inside the loop, the current
ListItem’s Selected property is inspected. If it is True, then the corresponding
checkbox must be checked, and so be added to the string containing the list of
selected options, strSelectionOptions. However, if it is not selected, nothing must
happen, and the loop must simply continue to the next iteration. The following code
appears inside the If block.
If strSelectedOptions <> "" Then
strSelectedOptions = strSelectedOptions & ", "
End If
strSelectedOptions = strSelectedOptions & lstMagazines.Items(i).Text
The If statement is simply there to ensure that the list of selected options is
comma-separated. The first selected option must obviously not have a comma in front
of it, so when strSelectedOptions is empty, a comma must not be added. However, if
there is text in strSelectedOptions, a comma should be added.
Finally, the ListItem’s Text property is used to retrieve the Text from the
checkbox and this is appended to strSelectedOptions.
The code to update the display of lblSelected follows the loop. If
strSelectedOptions is empty, then no options are selected, so a message to that effect
is displayed. However, if strSelectedOptions contains text, then at least one option
was selected, so strSelectedOptions must be outputted to the Label.
To test out the code, load the page in IE, select a few options and click the
“Submit” button. Figure 5.25 shows sample output from the page.
Figure 5.25 – The CheckBoxList in action
The For Each loop is a native VB.NET language structure that is designed
specifically for iterating through collections. In the “declaration” of the loop,
For Each Item In lstMagazines.Items
Item represents a single item in the collection that is being looped through, and the
object after “In”, lstMagazines.Items, is that collection. For each iteration of the loop,
the Item object is reassigned as a reference to the current item in the collection.
Because of this, the code inside the loop uses the Item object to obtain information
about the current ListItem.
If you load the page again, you’ll find that it operates exactly the same as before,
as the code is functionally identical.
As was mentioned at the beginning, one of the more useful aspects of this control is
that it is simple to add and remove items to and from it dynamically, during run-time.
We’ll start off by simply allowing the user to add his/her own choices to the magazine
list, and because the existing code to report which options are selected iterates through
all the items using a loop, the new options will automatically be recognized in the
code.
In the Design view, above lstMagazines, add a TextBox Web Control, txtNew,
and a Button Web Control, btnAdd, and set its Text property to “Add”. The user will
type in the name of the item to add in the TextBox, and when the “Add” button is
pressed, it will be added to the list. To implement this, double click on btnAdd to
create a Click event handler for it, and use the code shown in Figure 5.17.
Listing 5.17
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnAdd.Click
Dim NewItem As New ListItem()
NewItem.Text = txtNew.Text
lstMagazines.Items.Add(NewItem)
txtNew.Text = ""
End Sub
The first line creates a new ListItem object, NewItem. Next, the Text property of
NewItem is set, and if necessary other properties, such as Selected (which is False by
default), and Value, could also be set. After setting the property(ies) on the NewItem
object, it is added to the lstMagazines CheckBoxList through the Items’ property’s
Add method. The Add method accepts a ListItem object as a parameter (although
there is another overloaded version of it that accepts a string and creates the ListItem
for you, this version is more flexible), and adds the ListItem to the collection. Finally,
the txtNew TextBox is cleared.
As was mentioned earlier, since the “Submit” Button’s event handler loops
through all the items in the CheckBoxList, any additions to the list will be included.
Figure 5.26 shows the page after several extra options have been added and the
Submit button has been clicked.
Figure 5.26 – Dynamically adding items to a CheckBoxList
Removing an item can actually be slightly trickier than adding one, depending on
how you do it. When adding an item, you simply provide the text for it and call the
Add method. However, when removing an item, you obviously have to know which
item you want to remove from the list, and the item might be the first in the list, or the
last – and you might not know its exact position. For this reason, the CheckBoxList
control gives you a choice – you can either remove an item according to its “index”
(its position in the list, keeping in mind the fact that the first item in the list is 0), or
you can remove an item according to a reference to the actual ListItem object, or
simply the text that the item displays.
First off, add a Button Web Control next to the “Add Item” button, and call it
btnDelete. Set its Text property appropriately and create a Click event handler for it.
Listing 5.18 shows the code for the event handler that removes the item named in the
TextBox.
Listing 5.18
Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnDelete.Click
lstMagazines.Items.Remove(txtNew.Text)
txtNew.Text = ""
End Sub
When running the page, to remove an item, simply type in its name (case-
sensitive) in the TextBox and click the “Delete Item” button.
The SelectedIndexChanged Event
As with the CheckBox, and many other Web Controls, the CheckBoxList provides an
event that fires when the user modifies it. In the case of the CheckBoxList, this event
is SelectedIndexChanged, and fires during a postback if any of the ListItems
(checkboxes) Selected properties have changed since the last postback. The
CheckBoxList also has an AutoPostBack property, which causes an immediate
postback when any of the checkboxes in the list are clicked. In the final section of this
introduction to the CheckBoxList control, we’ll make use of both these features.
We’re going to modify the current form, because it’s not a very realistic
representation of something you might do in a real application. The user will almost
never be responsible for manually adding or removing items to and from the
CheckListBox, so instead of the TextBox and the two Button controls at the top, we’ll
have several CheckBox controls, providing different categories of magazines. When
one of these CheckBox controls is checked, the CheckBoxList must show options in
that category of magazine, and if the user unchecks the control, the related options
must be removed from the CheckBoxList. Although its not necessary, and is often not
good practice, in the spirit of demonstrating what the CheckBoxList is capable of,
we’ll also make use of its AutoPostBack functionality, as well as that of the
CheckBox controls, to ensure that the Label control listing the currently-selected
options is always up-to-date.
First up, we want to move the code that is currently in btnSubmit’s Click event
handler to a separate, standalone subroutine. To do this, insert the following code into
the codebehind file, preferably just before the End Class statement:
Private Sub UpdateLabel()
End Sub
Now copy and paste the code inside the btnSubmit Click event handler (but not
including the Private Sub and End Sub statements) into the newly created
UpdateLabel subroutine. This procedure will be called from numerous different event
handlers, so instead of duplicating the code, we’ve made an entirely separate
procedure that can be called when needed. Since we’ll be using AutoPostBack you
can delete the entire btnSubmit Click event handler (the btnSubmit_Click subroutine),
and then switch to Design mode and delete the btnSubmit Button. Select lstMagazines
and set its AutoPostBack property to True. Double-click on the control
(lstMagazines), and the IDE should switch over to the server-side code and have
created a new event handler for the SelectedIndexChanged event. As has already been
discussed, this event fires when an item in the list changes (because it is clicked), so
therefore the Label displaying the list of currently selected options must be updated.
To do this, simply place the following line into the event handler:
UpdateLabel()
With this done, you can now load the page in IE and try out the AutoPostBack
functionality of the CheckBoxList. The form operates similarly to before, except
instead of clicking the “Submit” button on the form to update the Label, the form
submits itself every time a checkbox is clicked, and the Label is then updated
automatically.
With that out of the way, we can progress with the more interesting parts. First
off, switch to the server-side code and remove the Click event handlers for the btnAdd
and btnDelete Button controls. Switch back to the Design mode and remove the
TextBox and the Button controls, and in their place, put three CheckBox controls. Set
the first ones ID property to chkCurrentAffairs, and set its Text property to “Current
Affairs”. Do similarly with the next two controls, but with the IDs chkBusiness and
chkComputers, and the Text, “Business” and “Computers” respectively. Set the
AutoPostBack property of all three to True. Select lstMagazines and remove all the
current items using the Items property and Collection Editor. The form in the
Designer should now look similar to Figure 5.27.
Figure 5.27 – A form using both CheckBox controls and a CheckBoxList
The final code that still needs to be implemented is that for the three CheckBox
controls’ CheckedChanged events. Starting with chkCurrentAffairs, when its
CheckedChanged event fires, items must either be added to or removed from
lstMagazines. If chkCurrentAffairs.Checked is True, then items, such as “Time” and
“Newsweek” must be added because the checkbox has just been checked. However, if
it is False, then these items must be removed. Finally, to ensure that the lblSelected
Label is updated, the UpdateLabel procedure must be called.
Create CheckedChanged event handlers for all three CheckBox controls by double
clicking them, and insert the code in listing 5.19.
Listing 5.19
Private Sub chkCurrentAffairs_CheckedChanged(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles
chkCurrentAffairs.CheckedChanged
If chkCurrentAffairs.Checked Then
lstMagazines.Items.Add("Time")
lstMagazines.Items.Add("Newsweek")
Else
lstMagazines.Items.Remove("Time")
lstMagazines.Items.Remove("Newsweek")
End If
UpdateLabel()
End Sub
Each event handler does essential the same thing – it checks whether the
CheckBox control that is handles is checked. If so, it adds items to the CheckBoxList.
If not, then it removes the items. Finally, UpdateLabel is called, in case items that
were selected were removed. Figure 5.28 shows the final page after its been loaded in
IE.
Figure 5.28 – Dynamically creating and removing CheckBoxList items
The Radio Button
The RadioButton control shares several similarities with the CheckBox control,
starting with the fact that both controls come in both the Web Control and Html
Control varieties. Similar to the CheckBox, the RadioButton’s functionality also
revolves around providing the user with a choice from numerous options, and exists in
either a checked or unchecked state. However, whilst the CheckBox control allows
the user to select (i.e. check) more that one option, the RadioButton control exists for
scenarios where only one option can be selected at a time. For example, when asking
the user which age group they fit into, you would not use CheckBox controls to
provide the options “Under 18”, “18 – 25”, “26 – 35” etc, because only one of the
options could possibly be valid, so RadioButton controls would be used.
The RadioButton control is very easy to use, and is implemented very similarly to
the CheckBox. However, while the CheckBox doesn’t have any mandatory properties,
so to speak (the ID property might be considered mandatory, but even if you leave it
at its default value, the functionality of the CheckBox won’t be hindered), the
RadioButton control, in both the Web Control and Html Control implementations, has
one property in each variety that must be set in order for the controls to function
correctly. This is the GroupName property, in the case of the Web Control, and the
name property for the Html Control. RadioButton controls must be “grouped”
together, so that all the options that are related to a specific question are explicitly
“connected”. This is very simple to do, and all that is required is that all the
RadioButton controls that related to a specific question must all have the same
GroupName property value (or name property value, if Html Control RadioButtons
are being used). This is a string property, so for a question relating to the age of the
user, the GroupName (or name) property could be set to “Age” for all the
RadioButton controls that provide options for the answer. If you ever have problems
with RadioButton controls, where either the user can select more than one option for a
particular question, or the user can only select one option between two (or more!)
questions, then it’s more than likely that you’ve either forgotten to set the GroupName
or name properties, or have set them incorrectly.
With the theory out of the way, we can move onto actually using the RadioButton
controls. To demonstrate the two different types (Web and Html), and the “grouping”
functionality for each, we’ll set up a questionnaire containing 4 questions, two using
Web Control RadioButtons for the options and two using Html Control RadioButtons.
The form should look like Figure 5.29 in the Designer.
Figure 5.29 – RadioButton controls in the Designer
The first two questions use Web Controls, and the second two use Html Controls.
As with the CheckBox controls, the Web Controls provide a Text property for the
caption accompanying each option, but the Html Controls do not, so you must enter
the text in the Designer. Add the required question text and all the RadioButton
controls to the page, and set the Text property, or add the text in the Designer, for
each RadioButton using the caption text shown in the figure. Set the ID properties for
the RadioButton controls as follows:
For the first question, rdoAgeUnder18, rdoAge18to30, rdo31to49 and rdoOver50.
For the second question, rdoCitizenYes, rdoCitizenNo.
For the third question, rdoSectorAcademia, rdoSectorGovernment,
rdoSectorBusiness.
For the fourth question, rdoEducationHighSchool, rdoEducationBachelors,
rdoEducationMasters, rdoEducationDoctorate.
Take note of the structure of the IDs. Although obviously not mandatory, it is
good practice to prefix the ID of each control that are options for the same question
with the same string, so for the first question, which deals with age, the prefix is
“rdoAge”. This helps avoid possible confusion later on when programming the server-
side code as it helps to reinforce what options “belong” to which questions.
After setting the ID and Text properties, the final property to set is the
GroupName or name property. For the options for the first question, set each control’s
GroupName property to “Age”, and for the second question’s options, set it to
“Citizen”. The thirds and fourth questions use Html Controls, so for the third
question, set the name property (not ID, but name) of each control to “Sector”, and for
the final question, set it to “Education”.
It is possible to set the value of a property on numerous controls at the same time by
selected all the controls and then changing the property in the Properties window. To do
this, hold down the Ctrl key whilst clicking controls, and you’ll see that each control is simply
added to the selection. Once all the controls you want are selected, you can modify their
properties all at once. This technique also works for converting HTML controls to Server
Controls – simply select all the controls you want to convert, and then right click one of
them and choose “Run As Server Control”.
Another property that you may want to set on each of the first RadioButton
controls for each question is Checked, setting it to True to make the first option of
each question selected.
With all the RadioButton controls set up, you can add a Button Web Control,
btnSubmit (with its Text property set to “Submit”), and a Label, lblSummary, beneath
it, with its Text property set to nothing. When btnSubmit is clicked, the Label will
display the chosen options of the user. To implement this, double click btnSubmit in
the Designer to create an event handler for its Click event, and use the code in listing
5.20.
Listing 5.20
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
Dim objStringBuilder As New System.Text.StringBuilder()
Dim strTemp As String
objStringBuilder.Append("You are in the age group: ")
If rdoAgeUnder18.Checked Then
strTemp = "Under 18"
ElseIf rdoAge18to30.Checked Then
strTemp = "18 to 30"
ElseIf rdoAge31to49.Checked Then
strTemp = "31 to 49"
ElseIf rdoAgeOver50.Checked Then
strTemp = "Over 50"
End If
objStringBuilder.Append(strTemp)
objStringBuilder.Append(". ")
If rdoCitizenYes.Checked Then
strTemp = "You are a US Citizen."
Else
strTemp = "You are not a US Citizen."
End If
objStringBuilder.Append(strTemp)
objStringBuilder.Append(" You work in ")
If rdoSectorAcademia.Checked Then
strTemp = "the Academia"
ElseIf rdoSectorGovernment.Checked Then
strTemp = "Government"
ElseIf rdoSectorBusiness.Checked Then
strTemp = "Business"
End If
objStringBuilder.Append(strTemp)
objStringBuilder.Append(". Your highest level of education is ")
If rdoEducationHighSchool.Checked Then
strTemp = "High School"
ElseIf rdoEducationBachelors.Checked Then
strTemp = "a Bachelor's degree"
ElseIf rdoEducationMasters.Checked Then
strTemp = "a Master's degree"
ElseIf rdoEducationDoctorate.Checked Then
strTemp = "a Doctorate"
End If
objStringBuilder.Append(strTemp)
objStringBuilder.Append(".")
lblSummary.Text = objStringBuilder.ToString()
End Sub
While the listing is fairly long, it doesn’t include any particularly complex code,
and clearly demonstrates the reason why the RadioButton control isn’t a good choice
when numerous options are available to the user, as each one has to be coded
individually on the server-side. The only RadioButton property that is used is the
Checked property, which exists in both the Web and Html Control varieties, and is
True when an option is checked, and False when it is not. However, to generate the
summary string (the StringBuilder object, which was introduced earlier in the chapter,
is used to create the string, since it involves numerous concatenations), each
RadioButton has to have its Checked property inspected manually, as there is no easy
way to loop through a specific “group” of options and return which option is selected.
For this reason, I strongly recommend that you rather use the RadioButtonList control
when you need to use radio buttons to provide options to the user, as this control,
which is analogous to what the CheckBoxList is to a CheckBox, provides a
programmable list of radio buttons, and provides properties and methods for quickly
and easily obtaining the selected option. However, we’ll deal with that control in the
next section. For now, figure 5.30 shows the page loaded in IE after the questionnaire
has been answered and the form has been submitted.
Figure 5.30 – RadioButton Web and Html Controls being used in a questionnaire
As with the CheckBox Web Control, the RadioButton Web Control also features
an AutoPostBack property, so when the RadioButton either is checked, or becomes
unchecked, a postback will be forced. Additionally, both the Web Control and Html
Control varieties of the CheckBox provide events that trigger during a postback when
their Checked properties have changed since the last postback. For the Web Control,
the event in question is CheckedChanged, and for the Html Control it is
ServerChange. In both cases, double-clicking on the control in the Designer will
create the server-side event handler for the respective events.
The RadioButtonList
As was mentioned in the RadioButton section, the RadioButtonList provides a
programmable interface to a list of radiobuttons in a very similar way to how the
CheckBoxList contains a list of checkbox items and allows this list to be modified
programmatically. In the RadioButton example, there was a significant amount of
code to perform what should really be quite a simple operation – determining which
RadioButton was selected. The RadioButtonList control greatly simplifies this
process, and in fact reduces it to exactly one line of code. Unless you have a
compelling reason to use several RadioButton controls instead of a RadioButtonList, I
strongly recommend you avoid all the extra effort required by RadioButton controls
and use the RadioButtonList.
Since the RadioButtonList is so similar to the CheckBoxList, which was
introduced fairly thoroughly, we won’t spend too much time on the RadioButtonList,
as it should seem quite familiar, as there is very little that separates the two.
The CheckBoxList and the RadioButtonList actually derive from the same base class,
the ListControl class, so most of their functionality does actually overlap internally,
and is why they expose such similar programmatic interfaces.
To demonstrate the RadioButtonList control, we’ll create a form that performs the
three major operations on it – adding new items, removing items, and determining
which item is selected. Figure 5.31 shows how the form will look in the Designer.
Figure 5.31 – The RadioButtonList control
Since the facility to remove items will be added, this event handler must cater that
eventuality. Ordinarily, the only line needed would be the following:
lblSelected.Text = lstLanguage.SelectedItem.Text
In both cases the appropriate Items property method (Add and Remove
respectively) is called, and the text in the TextBox is cleared. When the page is
loaded, the user can add and remove items, and the selected item’s text will be
displayed in the Label when the Submit button is clicked. Figure 5.32 shows the page
after the language Spanish has been removed and Afrikaans added, and English is
selected.
Figure 5.32 – The RadioButtonList
The ListBox and DropDownList
Both the ListBox and DropDownList controls have Web Control and Html Control
implementations. Whilst the functionality between them is almost inseparable, there
are a few differences between the Web Controls and the Html Controls when
designing the page, which we will discuss shortly. In essence, the purpose of these
two controls is to provide the user with a choice from a list of options. When the user
needs to be presented with a list of choices, there are three controls that you could use
– a RadioButtonList, a Web Control that doesn’t have an Html Control equivalent; a
ListBox Web or Html Control; or a DropDownList Web or Html Control. Which one
you use depends largely on personal preference, but the number of options available
should definitely be a factor in your decision. If there aren’t very many options (less
than 5), then the RadioButtonList and ListBox controls can be used, but if there are
more than 5 options, then these two controls aren’t very practical as they display all
the options on the page, whereas the DropDownList only displays the currently
selected option until its “dropdown menu” is opened to display the other options.
The ListBox and DropDownList Web Controls behave almost identically to the
RadioButtonList both in the Designer, and during run-time. In the Designer, their list
of options is modified using the ListItem Collection Editor, and during run-time their
Items and SelectedItem property are used to add, edit and remove options and obtain
information from the selected item in exactly the same way as is done with the
RadioButtonList.
The Html Control varieties of the ListBox and DropDownList differ quite
significantly from their Web Control equivalents in both design-time and run-time
functionality. At design-time, these two controls do not have a ListItems Collection
Editor, so the options for these controls must be entered manually in the HTML view,
which simply makes them more tedious to use. During run-time, an Items property is
exposed, so options can be added, edited and removed programmatically. However,
the controls do not expose a SelectedItem property, which makes obtaining
information about the selected option slightly more difficult than with the Web
Controls, although it isn’t a major issue.
To demonstrate the controls, we’re going to create a page that allows several font
properties of a Label Web Control to be set, including colors, fonts and font sizes.
Figure 5.33 shows the final result in the Designer, so you know what you’re working
towards. To begin with, add a Label in the Designer, set its ID to lblTitle, and its Text
property to “Title”. We’re going to organize our controls in a table, so next add a table
to the form underneath the Label using the “Insert-Table” command from the “Table”
menu. The table should have 5 rows and 2 columns. The first choice the user will
have is that of the Label’s font color; so in the first cell on the left hand side, enter a
caption “Font Color”. In the right-hand column, insert a DropDownList Web Control.
Set its ID to lstFontColor, and using the Item property’s ListItem Collection Editor,
add four options – Black, Blue, Green and Red. The second choice will be of font
size, so add an appropriate caption in the next row, and in the right column, insert a
ListBox control, lstFontSize. Add the following options to it: XXSmall, Small,
Medium, Large and XXLarge. Set the Small item’s Selected property to True.
The Web Controls are quick and easy to set up. However, we’re also going to
demonstrate the ListBox and DropDown Html Controls, but these involve quite a bit
more manual work to set up. To start off, a DropDown Html Control will be used to
control the Label’s Background Color, so in the 3rd table row, add an appropriate
caption in the left column, and in the right column, add a DropDown with ID
lstBackgroundColor, remembering of course to check its “Run As Server Control”
option. To add items to the control at design time, you need to switch to HTML view,
so do so. You need to find the line(s) of code that reads as follows.
<SELECT id=lstBackgroundColor name=Select1 runat="server">
<OPTION selected></OPTION>
</SELECT>
This is the declaration of the DropDown Html Control in the UI code. Take note
of the runat attribute in the SELECT tag – this, as was discussed at the beginning of
the chapter, signifies that this is a server control. To add options to the list, you use
the same markup as if this were a regular HTML DropDown control – the OPTION
tag. Each item needs to be declared using an OPTION tag in the following format:
<OPTION VALUE="value">text</OPTION>
A selected option has an additional attribute, and would be declared like this:
<OPTION VALUE="value" SELECTED>text</OPTION>
With this done, you can now switch back to the Design mode and you should see
that the control now shows that it does contain options. The final list control will be to
change the font name, so add the appropriate caption to the last row, and in the right
column, add a Listbox control from the HTML palette, setting it to run as a server
control. Set its ID to lstFontName, and switch to the HTML view so that you can add
items to it. Find the following line(s) of code:
<SELECT id=lstFontName size=2 name=Select1 runat="server">
<OPTION></OPTION>
</SELECT>
As you can see, the only attribute that differentiates a Listbox from a Dropdown
Html Control is the inclusion of the size attribute in the SELECT tag. Since native
HTML uses the SELECT tag for both dropdowns and listboxes, the Html Controls do
to (since their real objective is to map as closely to native HTML as possible). Indeed,
the class that is dealt with on the server-side is the same for both controls – the
System.Web.UI.HtmlControls.HtmlSelect. Add the necessary OPTION elements so
that the lstFontName definition is as shown in listing 5.24:
Listing 5.24
<SELECT id=lstFontName size=2 name=Select1 runat="server">
<OPTION value="Arial">Arial</OPTION>
<OPTION value="Courier New">Courier New</OPTION>
<OPTION value="Times New Roman" selected>Times New
Roman</OPTION>
<OPTION value="Verdana">Verdana</OPTION>
</SELECT>
You can now switch back to the design view and add a Button, btnSubmit, to the
last row in the right column, with the Text, “Submit”. The form should now look
similar to Figure 5.33.
Figure 5.33 – The ListBox and DropDownList Web and Html Controls
All that’s left to do is complete the implementation of the Button’s Click event
handler. Double click on btnSubmit to create the event handler, and use the code in
Listing 5.25.
Listing 5.25
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
lblTitle.ForeColor = Color.FromName(lstFontColor.SelectedItem.Text)
lblTitle.Font.Size = FontUnit.Parse(lstFontSize.SelectedItem.Text)
lblTitle.BackColor =
Color.FromName(lstBackgroundColor.Items.Item(lstBackgroundColor.Sele
ctedIndex).Text)
lblTitle.Font.Name =
lstFontName.Items.Item(lstFontName.SelectedIndex).Text
End Sub
Notice that even for the Html Controls, one line is all that is needed to obtain the
required details about the selected item in each case. The first property to be modified
is “ForeColor”. As was mentioned in the section detailing the Label control, this
property modifies the font color. However, a System.Drawing.Color structure must be
assigned to it – not a string. However, the Color structure includes a shared (i.e. non-
instance) method, FromName that accepts a string as a parameter and returns the
corresponding Color structure for the color name that was passed. To obtain the name
of the color that was selected, the lstFontColor.SelectedItem property’s Text member
was used, which returns the text property of the selected ListItem.
The Font.Size property, which was also covered in the earlier section dealing with
the Label control, accepts a FontUnit structure, and again a shared method of this
structure is used (Parse) to convert the string input into the acceptable structure. The
syntax to obtain the text from the selected item is the same as for the DropDownList.
The BackColor property, which modifies the background color of the Label, also
accepts a Color structure so again the Color.FromName method is used. However, the
Dropdown Html Control needs a little more effort to obtain the text of the selected
option. First of all, the lstBackgroundColor.SelectedIndex property provides an
Integer representing the index of the selected item. The lstBackgroundColor.Items
property returns a collection, and the Item index property of that is used to obtain a
specific item from the collection by specifying the item’s index. Therefore
lstBackgroundColor.Items.Item(lstBackgroundColor.SelectedIndex) returns the
selected ListItem object for manipulation, from which point the Text property is used
to obtain the text of the item.
Finally, the Font.Name Label property accepts a string of the font name to use, so
there’s no need to modify the Text property value of the selected item in the Html
Control Listbox – it can simply be assigned to the Font.Name property. Since the
Html Control Listbox and Dropdown are actually both
System.Web.UI.HtmlControls.HtmlSelect objects, the method to obtain the selected
item’s text is obviously the same.
When the page is loaded, you can modify the properties of the Label by selecting
various combinations of options in the list controls and clicking the “Submit” Button.
Figure 5.34 shows the page in action.
Figure 5.34 - The ListBox and DropDownList Web and Html Controls in action
Should you wish to add or remove items programmatically, you can do so using
the Items.Add and Items.Remove methods in exactly the same was as with the
RadioButtonList. This applies to both the Web and Html Controls, even though their
design-time behavior is significantly different.
The HyperLink
Much of the basis behind the World Wide Web relies on the concept of HyperLinks
that connect related pages of information. The Visual Studio.NET IDE includes a
menu option that will convert the selected text in the Designer into a hyperlink. To do
this, simply type some text into the Designer, select the part you want to be a link, and
click the Insert menu and choose the Hyperlink option. Figure 5.35 shows the dialog
with a URL for the hyperlink inserted.
Figure 5.35 – The Hyperlink dialog
However, just like it is possible to add text to the page by simply typing in the
Designer and yet there is still a Label control, even though you can add links without
the aid of a server control, a HyperLink Web Control is still available. This provides
the advantage of being able to programmatically modify where the link points to at
run-time, and can play a very important role in state maintenance using querystrings,
as will be discussed further in the next chapter.
To demonstrate the HyperLink Web Control, we’re going to build a page where
both the text of the HyperLink and the URL that the control is pointing to will be
changed via two TextBox Web Controls. To do this, first add two TextBox controls to
the page and set their IDs to txtText and txtURL. Next add a Button with its ID set to
btnSubmit, and finally add the HyperLink control at the bottom and set its ID to
lnkDynamicLink. Double click btnSubmit to create a Click event handler, and use the
code shown in listing 5.26.
Listing 5.26
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
lnkDynamicLink.Text = txtText.Text
lnkDynamicLink.NavigateUrl = txtURL.Text
End Sub
The Text property of the HyperLink sets (or returns) the text that is displayed on
the page by it, and the NavigateUrl property is the URL that the anchor points to, and
where browser will navigate to when the link is clicked. Figure 5.36 shows the
HyperLink after its Text and NavigateUrl properties have been set programmatically.
Figure 5.36 – The HyperLink Web Control
The HyperLink will typically not be used in this fashion, and for the most part, its
properties will be set “behind the scenes” to help store application state by appending
querystrings and so forth, but the basic concept of a programmable link persists and
can prove to be very useful.
Conclusion
The purpose of this chapter has been two-fold. First and foremost, it has been to give
you a solid introduction to the basic controls available for building your user
interfaces, and to show you how to use these controls in both the design-time
environment, and during run-time. If you are comfortable with using the controls in
the Designer, I also encourage you to learn, or at least become familiar with, the
notation and syntax that ASP.NET actually uses for controls in the UI HTML code, as
although the IDE abstracts you from it, most documentation will provide ASP.NET
examples in code, rather than Visual Studio.NET instructions.
However, there was in fact a second, more subtle purpose for this chapter, which
was to help you become more familiar with the Visual Basic.NET syntax. Hopefully
through the examples in this chapter, you will now be comfortable with these tasks,
amongst others: declaring variables and creating objects in VB.NET; using the If
conditional statement; using the For loop structure; accessing members of objects and
classes; performing type conversions; assigning values to variables and properties,
and other simple yet important tasks. You should also hopefully have a basic
understanding of the event model in ASP.NET, and its place in the Page Cycle.
Chapter 6
Anchors (Links)
The first and most obvious way for a user to cause them selves to be navigated from
one page to another is by using a link. The previous chapter dealt with the HyperLink
web control, but as a refresher, this control is basically an anchor tag (A) that is
programmatically accessible on the server side. The use of this as opposed to a ‘static’
link (which will be covered shortly) will be made visible when state persistence is
covered in the 2nd part of the chapter.
As an example, the following web control definition will result in a link being
created to a page called “WebForm2.aspx”.
<asp:HyperLink id="lnkNextPage" runat="server"
NavigateUrl="WebForm2.aspx">Next Page</asp:HyperLink>
However, since ASP.NET processes the web control, it is possible to change the
properties of the HyperLink control at “run time”, resulting in a different anchor tag
being generated. For example, if in the Page Load event handler, the following line
was included:
lnkNextPage.NavigateUrl = "WebForm3.aspx"
Then the link would point to WebForm3.aspx. This becomes particularly useful
when transferring state from page to page.
Of course, if you’d like to include a link to another page and don’t need to change
it programmatically, then simply inserting an HTML anchor tag straight into your
page will suffice. In the Design view, you can select either an image or text and click
Insert-Hyperlink. In HTML view, you must manually enter the anchor link. Note that
in both the design and HTML views, the Properties window can be used to modify the
properties of the tag.
HTTP Redirection
This method of the user moving from one page to another doesn’t actually involve
any action on their part, but it does occur due to the behaviour of the web browser that
they’re using. HTTP Redirection is a fairly common method of refreshing a page
automatically, or automatically sending the user to another page after a specified time.
The most common example of HTTP Redirection is probably its use in pages that
inform the visitor that “This site has moved. You will be sent to the new address in 5
seconds.” This effect is achieved using the META tag in HTML. You will probably
have noticed that Visual Studio.NET automatically inserts four META tags into all
Web Forms. These store information about the Web Form for use by Visual
Studio.NET. The HTML standard provides META tags for the purpose of supplying
additional information about an HTML, and a META tag normally takes the
following format:
<meta name=”keyname” content=”content”>
META tags can be used to store any information. For example, a META tag could
be used to store the author of a document. In this case, the META tag might look like
this:
<meta name=”Author” content=”Peter McMahon”>
Search engines use META tags to help better index pages in their engines. More
specifically, they look for the Keywords and Description META tags and use these
values.
However, HTTP Redirection uses a slightly different form of the META tag, and
doesn’t include the name attribute. Instead the attribute “http-equiv” is supplied, with
the value “Refresh”. The content attribute then stores the amount of time in seconds
before the refresh will occur, and if applicable, the document that must be called,
separated by a semi-colon and “url=”. If you wanted to automatically redirect a user
from WebForm1.aspx to WebForm2.aspx after they’ve viewed WebForm1.aspx for
10 seconds, you’d insert the following META tag into the HEAD section of
WebForm1.aspx’s HTML code:
<meta http-equiv=”Refresh” content=”10;url=WebForm2.aspx”>
After 10 seconds, the browser will then request WebForm2.aspx, as if it had been
a link that the user had clicked on.
HTML Forms
ASP.NET goes a long way towards changing the idea of HTML forms as they have
been traditionally known, and making HTML forms operate more like traditional
Windows forms do, but HTML forms are still a very common and powerful
mechanism for moving between web forms and particularly in persisting state. In
chapter 3, it was explained that HTML forms contain an “action” attribute that allows
the file where the contents of the form will be sent. For example, a form on one page
could send its contents to another page that then processes the form. In ASP.NET the
most common occurrence is the form data being sent back to the same Web Form that
processes it before it displays the form again. Although for many developers this was
common practice in ASP, many did not use it, but ASP.NET strongly encourages this
style of development and makes it easier by providing a programming model for the
Web Controls, rather than having to constantly deal with Request.Form variables.
This makes Web Forms development become significantly more like Windows
development where forms are really self-contained units, but sometimes it is useful to
revert back to the “traditional” method of sending form data to another file.
You come across forms that “link” to other pages every day. Possibly the most
common example are search engines. When you enter a search term in Google and
click Search, the data that you entered is sent to a different page. When you search for
a book in Amazon.com using the search bar on the main page, the data isn’t sent back
to the main page to be processed, but rather to their search script.
When using this method, there are several important changes in the code that need
to occur for it to work. Firstly, the form must be a pure HTML form and not a server
control, so the form must not have a runat=”server” attribute. This is because, when
using server controls, ASP.NET forces the form to send all the data back to the same
page. In other words, it forces the action attribute of the form to revert to the name of
the current page, regardless of whether it is manually set differently or not.
Because the form is no longer a server control, and for a variety of other reasons,
the page that the form data is being sent to will not be able to use the “friendly”
method of obtaining form values, and will have to revert to the traditional ASP
method of using Request.Form() that was briefly covered in the previous chapter.
In Listings 6.1 and 6.2, an extract from two web forms is shown. In Listing 6.1, a
pure HTML form that will send its data to another page is shown, and Listing 6.2
shows the Page_Load event handler for WebForm2.aspx that assigns a value passed
from the 1st page to a label control in WebForm2.aspx.
Listing 6.1
<form action="WebForm2.aspx" method="post">
Enter your name.<br>
<input type="text" name="txtName">
<input type="submit" value="Send">
</form>
Listing 6.2
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
lblName.Text = "Your name is " & Request.Form("txtName") & "."
End Sub
When the submit button on the 1st page is clicked, the browser submits a request
to the server for WebForm2.aspx page, and includes the data from the form in the
request. When WebForm2.aspx receives the request, it accesses the posted data using
the Request.Form() property and assigns it to a label.
Client-side Script
Although not commonly used, there are several ways in which client-side script can
be used to move the user between pages. Scripting can be used on its own to redirect
the user, or it can be used in conjunction with regular HTML forms by submitting the
form, even though the user has not clicked the “submit” button.
Firstly, the Internet Explorer DOM (document object model) exposes a location
object that provides details about the current document’s location. However, assigning
a value to the href property of the location object will result in a redirect to that
document. The following code, if inserted into the Web Form’s HTML, will
automatically redirect the user to WebForm2.aspx once the page has been loaded.
<script language="javascript">
location.href = "WebForm2.aspx";
</script>
A history object is also exposed and includes a back() method. This method
allows you to specify how many items back in the history the browser must move to.
For example, inserting the following into a Web Form’s HTML will result in the user
being automatically sent back to where they’d come from.
<script language="javascript">
history.back(1);
</script>
However, it is important to note that, where possible, the browser will always
attempt to load the item from its cache when this method is used.
To automatically submit forms, the form must have a unique name, which is then
referenced using the document object, and is submitted using the submit() method.
The following is an example of a form that is automatically submitted as soon as the
page is loaded, and thus the user sent to WebForm2.aspx, along with the data in the
form:
<form name=”Form1” action=”WebForm2.aspx” method=”post”>
<!-- some form fields -->
<input type=”submit” value=”Submit”>
</form>
<script language=”javascript”>
document.Form1.submit();
</script>
[ Listing 6.3 ]
Response.Redirect
The Response.Redirect method has two overloaded versions. The most commonly
used version takes one string parameter, which is the relative or absolute URL to the
resource that the client must be redirected to. In effect, the first overloaded version of
Response.Redirect terminates the processing of the ASP.NET page and the user is
sent to another page. However, the second overloaded version takes the URL string
string parameter, followed by a Boolean specifying whether the current response
should be immediately terminated or not.
The one drawback of Response.Redirect is that in both cases it requires a
roundtrip to the client before the redirection occurs, which can have an impact on
performance, although generally this does not result in a major performance
bottleneck.
Listing 6.4 shows an event handler for a button that will redirect the user when the
button is clicked.
Listing 6.4
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Response.Redirect("WebForm2.aspx")
End Sub
For this particular example, the exact same effect could have been gained by using
the aforementioned HTML forms or client-side script techniques, but since the
Redirect() method is executed in server-side code, it has decided advantages over the
client-side methods. Possibly one of the most common occurrences of server-side
redirects on the Internet are login scripts. When you log in to a web application, the
site will normally send you from the form page to another page that checks your
credentials. If you are successfully authenticated, you will then normally be redirected
to a menu, or back to the main page. That same result would be difficult to obtain
without using server-side redirection, and would be very cumbersome. This is just one
in the many examples where server-side redirection is very useful.
By default, the endResponse parameter of Redirect() is True, and thus when
calling the first overloaded version of Redirect() that only allows one parameter, the
URL, the execution of the current ASP.NET page is immediately terminated.
However, this is sometimes inappropriate as it may be convenient for the rest of the
code to complete execution before the redirection occurs. Examples of such scenarios
would include if a file has been opened, the script should complete so that it is closed
again etc. In such cases, the second overloaded version of Redirect() should be used,
with the second parameter set to False, as demonstrated in Listing 6.5.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
'Code that opens a file
Response.Redirect("WebForm2.aspx", False)
'Code that closes the file
'This code will be executed
End Sub
[ Listing 6.5 ]
Server.Transfer
[ Listing 6.6 ]
Server.Execute
Server.Execute is a hybrid between user controls and page redirection. User controls
are covered later in the book, but in their simplest form, involve putting together a
group of HTML elements or server controls that can be easily inserted into a page as a
single entity. For ASP developers who haven’t come across Server.Execute
previously, it can perform a similar function to server-side includes in ASP.
Server.Execute is similar to Server.Transfer, in that it calls a specified page and
transfers control over to the page, but the difference between the two methods is that
when using Execute(), once the second page has completed its response, control is
returned to the original page, where the code execution starts again where it left off.
Server.Execute can have several useful applications, but generally it is difficult to
implement and the desired solution could probably be obtained much more easily
using user controls, classes and procedural programming in ASP.NET.
The Server.Execute method has two overloaded versions – the first taking only a
string parameter containing a URL, and the second both a URL parameter and an
instance of the TextWriter class. The first version dumps the response of the URL
immediately where the method is executed, but the second version places the response
into a TextWriter object, allowing the response to be placed into a label control, or the
like. Listing 6.7 demonstrates the use of this second version.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim objWriter As New System.IO.StringWriter()
Server.Execute("WebForm2.aspx", objWriter)
Label1.Text = objWriter.ToString()
End Sub
[ Listing 6.7 ]
When using Server.Execute() in the fashion described above, it is important to note
that the page will return the same response as if it was being requested by a
browser. It is therefore important to build the pages that are being called using
Server.Execute() accordingly by only including necessary code, and not including the
HTML document “template” of HTML, META, BODY and FORM tags, in much the
same way as User Controls are built.
Persisting State
It’s all fair and well moving between web forms, but there’s not much point if the
application doesn’t know what has been done on the previous forms. An example of
state information is a user ID. When you log in to Hotmail, your user ID has to be
temporarily stored so that the application knows which inbox is yours, what your
settings are for sending mail, what your signature is and a whole host of other items.
In Windows application development, persisting state information is effectively
automatic because the user is constantly ‘connected’ to the application, and so the
application can easily check what options were selected on a form that was opened 10
minutes ago, or what your name is for appending to a document that you’re writing.
However, the Internet is a stateless environment. This stems primarily from the fact
that the user is not constantly connected to the application and a whole process of an
application is not spawned when a user ‘opens’ the application. When a user requests
an ASP.NET page, the browser opens a connection to the web server and requests the
appropriate file. The server then processes the request, executes the code in the
ASP.NET page and returns the result to the client browser, and the connection is then
closed. A ‘copy’ of the application is not left running in the server’s memory, ready to
resume activity when the user connects again to request another page. The primary
reason for this is that it would simply be far too resource-intensive for web servers to
implement. This means that the programmer has to provide some other method for the
application to ‘remember’ whom the user was, and what settings they’d applied.
When the form is loaded for the first time, the link will point to
“WebForm2.aspx?name=”. Type your name into the TextBox, click the Button and
the link should point to “WebForm2.aspx?name=yourname”. When the user clicks the
link, WebForm2.aspx now “remembers” what the user entered in the previous form,
and can call upon it by referencing Request.QueryString(“name”).
Of course, this is a very simple example and the same result could have been
achieved using other means, but this technique is very useful when the application
needs to know values that were entered 2 or 3 forms back. However, as has been
mentioned, using the HtmlAnchor class is only one of the ways in which the anchor
tag can be dynamically generated.
Many Web Controls can contain other controls. Those that include a “Controls”
property allow other controls to be programmatically added and included within them.
Amongst several of these “Container” controls, is the Panel control. A dynamically
built link could be added to one of these controls, either by adding a HyperLink
control, or by adding a LiteralControl object. The use of the HyperLink control has
already been discussed so will not be elaborated on, but the LiteralControl class
provides an interesting method of adding items to a page that don’t need to be
programmatically accessed, and operates in a very similar fashion to the Literal Web
Control.
The LiteralControl class’s constructor allows a string parameter to be passed,
containing the HTML code to be inserted. Listing 6.8 shows its use, in conjunction
with a Panel, as an alternative to using a HyperLink control in the same example as
above.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Panel1.Controls.Add(New LiteralControl("<a
href=""WebForm2.aspx?name=" & TextBox1.Text & """>Next page</a>"))
End Sub
[ Listing 6.8 ]
Take note of the use of the double-inverted commas, used as an escape character
for inserted quotes, which would normally be a string delimiter. This code would have
the exact same result as the previous example, namely generating a link that passes
the text in TextBox1 as a query string variable.
Another method of generating the link and inserting it into the document is using a
Literal Web Control, which was again covered in the previous chapter, and allow
HTML code to be included in its Text property. It functions very similarly to the
LiteralControl class, but is slightly different in that it is a Web Control and can
therefore be added at design-time. After adding a Literal control, the following code
could be used in the Page_Load event to create the link with a query string:
Literal1.Text = "<a href=""WebForm2.aspx?name=" & TextBox1.Text &
""">Next page</a>"
Any Web or Html control that has a Controls property, or has a Text property, can
be used to dynamically insert anchors into a page using the previously demonstrated
methods. There are however two more completely different methods that were very
commonly used in previous versions of ASP, where page content was generally not
separated from the server-side code by a code-behind, as the ASP.NET programming
model and VS.NET encourage. This said, it is still possible to include server-side
ASP.NET code in amongst the UI code using the traditional <% and %> delimiters.
For example, inserting the following code in the UI file will result in a link being
dynamically created, as with all the previous examples:
<%
Response.Write("<a href=""WebForm2.aspx?name=" & TextBox1.Text &
""">Next page</a>")
%>
Where the link is created in the page depends on where this code is inserted. The
overloaded version of Response.Write() in this instance takes one string parameter
which it writes directly to the response being generated in the position where the code
is currently executing.
Following on from the above “inline server-side code” method, there is another
ASP.NET delimiter which allows server-side variables to be written to the response as
if done using a Response.Write(). This delimiter is <%=, and ended by %>. The value
of a variable name included between these two delimiters is written to the response.
Therefore, the below code produces the same result as well:
<a href=”WebForm2.aspx?name=<%=TextBox1.Text%>”>Next page</a>
This code would be placed where the link must appear in the page, as is the case
with the previous example of inline server-side code.
In following pages, this exact line could be repeated (assuming that the hidden
field is also included), persisting this value from the first page, through all the pages
inbetween, right through to the last.
Again, the hidden field could also be added as a LiteralControl in a Panel’s
Controls property. This could be achieved using the following code in the Page_Load
event, assuming that a Panel named Panel1 exists within the confines of the form
(between the form start and end tags).
Panel1.Controls.Add(New LiteralControl("<input type=””hidden””
name=””txtName”” value=””” & Request.Form(“txtName”) & “”>”))
Finally, the required variable could be inserted into a static hidden field using the
following code:
<input type="hidden" name="txtName"
value="<%=Request.Form("txtName")%>">
Whilst the Add() method can accept an object, it is advised that only values of basic
data types, such as strings, integers and dates, be inserted due to considerable strain
that may be put on the server, baring in mind that a new collection of session state
variables is created for every user of the website, and this collection is only deleted
after a relatively lengthy period of time.
The Remove() method accepts one string parameter containing the name of the
variable to remove from the session variables collection.
To retrieve items from the Session object, the Item() property is used. The Item
property accepts a string parameter of the name of the item, or an integer representing
the index of the item, and returns the value or object. This value will be returned in
the same type as it was inserted as. For example, if a string was added, when it is
retrieved, a string variable will be returned without having to perform any casting.
Item() is the default property of the Session object, and therefore it is also possible to
reference session variables using Session(). The Item() property is read and write, so
items can also be added with it.
To demonstrate the use of Session state, listing 6.9 shows the code behind for a
page containing a TextBox and a Button. When the button is clicked, a Session
variable is created containing the value in the TextBox. The user is then redirected to
the page with the code behind shown in Listing 6.10, where the value is then placed
into a label on the second page without passing any form and query string values.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Session.Add("name", TextBox1.Text)
Response.Redirect("WebForm2.aspx")
End Sub
[ Listing 6.9 ]
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Label1.Text = Session.Item("name")
End Sub
[ Listing 6.10 ]
Take note that no variables were passed in the Response.Redirect() call. After
trying out this example, you can go to other forms in the project, either through links,
or by typing the direct URL in the address bar of the browser, but when you return to
the 2nd web form through whatever means, the value of the label will still be loaded as
the value you originally entered on the 1st web form (assuming that the session hasn’t
timed out).
To demonstrate the different ways in which items can be added to session state,
this line could be used to replace the Add() method call in listing 6.9:
Session.Item("name") = TextBox1.Text
Because Item() is the default property, the line could also be replaced with this:
Session("name") = TextBox1.Text
Likewise, in listing 6.10, the code inside the event handler could be replaced with
this line:
Label1.Text = Session("name")
Using the overloaded Integer version of the Item property, this line could also be
replaced with this:
Label1.Text = Session(0)
When used in conjunction with the Count property of the Session object, a list of
all the set session variables can be accessed, as demonstrated by this code:
Dim i As Integer
Label1.Text = ""
For i = 0 To Session.Count - 1
Label1.Text = Label1.Text & Session.Item(i) & "<br>"
Next
This is a demonstration of the Count property, and looping through and displaying
all the session variables in the Session object should generally not be done in
applications, since data stored in session variables might not be displayable using a
label without serialization, since it is not guaranteed that only strings, integers etc
have been stored in the Session object.
Likewise, if the Add() method is called twice referencing the same variable, the
value in question will be overwritten:
Session.Add("MyVariable", "A value")
Session.Add("MyVariable", "Edited value.")
Although in most scenarios querystrings and form variables can be used to persist
state, session variables often provide a solution that is much easier to implement and
also offers several security advantages by avoiding the problem of malformed
querystrings and form variables, either through accidental corruption or modification
by malicious users. The chapter “Session State Model” discussed ASP.NET session
state is much greater detail, and provides information for advanced users, especially
for developers of web applications deployed in a web farm environment.
Persisting State using Application Variables
Application variables are in many respects very similar to session variables. They are
set and accessed almost identically, they are accessible throughout the application,
irrespective of querystrings and form variables passed and are not susceptible to the
same possible security vulnerabilities that using querystrings and form variables have,
amongst other similarities. However, the fundamental difference between Application
and Session variables is that Applications variables are accessible by all the users of
the application, whereas Session variables are unique to each user. Application
variables do not time out as session variables do, but their lifecycle begins when the
application is first accessed by any user, and ends when the application is stopped, as
opposed to session variables’ lifecycles that begin when the user first hits a page in
the application, and ends when the session for that particular user times out after a
certain period of inactivity.
While session variables are primarily used to store information about a specific
user, application variables are generally used to store information about the
application that all users should be able to access. A session variable might store the
name of a user, or include the contents of a user’s selections for a shopping cart, but
an application variable would include items that are applicable to all users, for
example, the physical path of the application on the server, or the number of users
currently viewing the site (i.e. the number of active sessions).
As has already been mentioned, setting and accessing application variables is
almost identical to the equivalent operations with session variables. The Application
property of the Page class is an instance of the HttpApplicationState class, which
provides an Item() default property for accessing application variables. The Add(),
Set() and Remove() methods are also provided for adding, updating and removing
application variables respectively. Because the Application object is simultaneously
available to all users of the application, the methods Lock() and Unlock() are provided
to prevent users from modifying the object at the same time. The use of these two
methods is the one pivotal difference between the manipulation of variables in the
Application and Session objects, as will be demonstrated shortly.
The advantages of the Application object are very apparent when used in
conjunction with the Global.asax file (which is covered in more detail in the chapter
“ASP.NET Application Configuration”). Simply put, the Global.asax file provides a
place to include event handlers that are fired when events dealing with the application
as a whole and user sessions occur. A counter of the number of concurrent users
visiting a site is quite a common occurrence (for example, “You are 1 out of 251
people currently viewing this website.”), and one of the ways of building such a
feature is by using Application variables, and the Global.asax file. To build an “active
visitors” counter sample, perform the following steps:
5. Compile the project and open the web form. The label should
report than there is 1 active visitor.
6. Open another instance of Internet Explorer, and navigate to the web
form. The label should now read that there are 2 concurrent users (if
it doesn’t, refresh the page).
Without focussing too heavily on the Global.asax file, the two event handlers
inserted fire when a new user session is started, and when a session ends. An
application variable, “CurrentUsers” is used to store an Integer value representing the
number of current users. It does this by having one added to it every time a new
session is created, and one subtracted every time a user session ends. In the
Session_OnStart event handler, the Application object is locked, so that it can only be
edited by the current instance to avoid synchronisation errors, and the value contained
in the Application variable “CurrentUsers” is then incremented. The Application
object is then unlocked, allowing the Application object to be written to by other
instances again. Similarly in the Session_OnEnd event handler, the Application object
is again locked and unlocked when modifying the object. However in this event
handler, the “CurrentUsers” variable is decremented. In the web form, the Page_Load
event handler loads the value from the “CurrentUsers” Application variable and
assigns it to the label.
After being introduced to the Session object, the use of Application variables
should look very familiar – excepting the locking and unlocking of the object before
writing to it, they are accessed and modified in exactly the same fashion as Session
variables. As with the Session object, the Application object too allows numerous
ways to add, access, edit and delete its variables.
First and foremost is the use of the Item() property, which allows variables to be
added, edited and accessed. Since Item() is the default property of the Application
object, it would be possible to replace the code in, for example, the Session_OnStart
event handler to this:
Application.Lock()
Application("CurrentUsers") = CInt(Application("CurrentUsers")) + 1
Application.UnLock()
Likewise, the web form’s Page_Load event could have accessed the variable
without including the default property name:
lblCurrentUsers.Text = "There are " & CStr(Application("CurrentUsers")) &
" users currently visiting the site."
However, unlike with the Session object, the Add() method can only be used to
add variables, but not edit them. However, the Set() method can be used for this
purpose. The following code would create, and then modify the application variable
“MyVariable”:
Application.Add("MyVariable", "Original value")
Application.Set("MyVariable", "New value")
Application state has traditionally been used primarily for caching, since an object
such as a database table can be stored once and accessed by all users of the
application. However, ASP.NET introduces the Cache object that should be used for
this purpose since it provides numerous advantages over using the Application object.
As has been shown with this section’s session counter, there are also other uses
for Application variables. A common use that still applies in ASP.NET is the use of
Application variables to store configuration strings for use in the code – these might
include items such as the connection string for a site’s database connection, or the
physical file path of the site. Any variable that needs to be globally accessible to all
users should be stored in an application variable.
Lastly, to demonstrate a fairly common practical use of application variables, a
chatroom will be built. One of the most popular methods of building an online
“chatroom” is using application variables. The method by which this works is that the
last 20 messages are stored in application variables. When the page displaying the
messages loads, it displays the 20 application variables. When a user sends a message,
all the messages stored in application variables are moved down one, and the user’s
message is placed in the first position.
The form structure is very simple – add two textboxes, a button and a Literal
control, separated by line breaks. Name them txtName, txtMessage, btnSend and
litMessages respectively. Listing 6.11 shows the code-behind event handlers, subs and
functions that will complete the functionality for the web form.
Private Function GenerateMessageList() As String
Dim strMessages As String
Dim i As Integer
For i = 1 To 20
strMessages = strMessages + CStr(Application.Item("Message" + CStr(i)))
+ "<br>" + vbCrLf
Next
Return strMessages
End Function
[ Listing 6.11 ]
When the page is loaded, the current messages are loaded into the Literal control.
If no message is entered and the form is submitted, then the messages are simply
updated, but if a message was entered, it is placed at the top of the list (application
variable “Message1”). The GenerateMessageList() function returns a string
containing all the messages by looping through the application variables from
“Message1” to “Message20”. Similarly the button’s click event handler loops through
the messages from 20 down to 2, replacing each with the next message, thus message
20 will contain message 19, and 19 will contain 18 etc. “Message1” is assigned as the
message that the user entered, preceded by the time that the message was sent, and the
name of the user.
As demonstrated, a simple chat application can be built very quickly and easily
using application variables. When used with multiple users, this is a good example of
how different users can edit application variables, and the changes are reflected for all
the users of the application.
Whilst it may sound that cookies are almost an extension of session variables, if
anything, the converse is true – session variables actually rely on a cookie that is
automatically sent with all ASP.NET responses to keep track of the user’s session id
so that the correct session variables can be matched with the correct user when
session variables are set, edited and deleted.
The cookie could also be created using another overloaded version of the
constructor, like this:
Dim objCookie As New HttpCookie("MyCookie", "MyValue")
As a slight deviation from session and application variables, the value entered for
the cookie is a string, and can only be a string – not an Object, as is the case with
session and application variables. This is because the value must be stored in an
ASCII text file by the user’s browser, and must also be transported via HTTP, and
neither have support for dealing with .NET objects.
After the cookie has been created, it must then be appended to the HTTP response.
The Response object’s Cookies property is an HttpCookieCollection class, which
includes an Add() method for adding cookie objects:
Response.Cookies.Add(objCookie)
If a cookie of the same name as one that already exists is added, the original
cookie will be overwritten with the new one, as is the case with the Add() method
when dealing with session variables. However, the HttpCookieCollection class
provides the Set() method for the specific purpose of updating cookies. This code
would update a cookie, “MyVariable”:
Dim objCookie As New HttpCookie("MyCookie", "New value")
Response.Cookies.Set(objCookie)
Cookie expiry is set through the HttpCookie class’s Expires property, which is a
DateTime value. An absolute expiration can be set (eg. All cookies expire at midnight
on 2003/10/01), or a sliding scale can be used where the cookie expires and is
removed 10 minutes from when it is created. The following code creates a cookie that
will expire on the 1st October 2003:
Dim objCookie As New HttpCookie()
objCookie.Name = "MyExpiringCookie"
objCookie.Value = "MyValue"
objCookie.Expires = DateTime.Parse("10/01/2003 12:00:00")
A sliding scale can be created using a TimeSpan class, or with only the DateTime
class. The following code creates a cookie that will expire in 10 minutes from when it
is created, using the DateTime class only:
Dim objCookie As New HttpCookie()
objCookie.Name = "MyExpiringCookie"
objCookie.Value = "MyValue"
objCookie.Expires = DateTime.Now.AddMinutes(10)
However, the TimeSpan class can also be used. The following code creates a
cookie that will expire 1 hour, 10 minutes and 5 seconds from when it is created:
Dim objCookie As New HttpCookie()
objCookie.Name = "MyExpiringCookie"
objCookie.Value = "MyValue"
Dim objTS As New TimeSpan(0, 1, 10, 5)
objCookie.Expires = DateTime.Now.Add(objTS)
After the expiry period has elapsed, the cookie will no longer be accessible and
will have to be recreated.
Sub-values
It is possible for a single cookie to contain numerous key and value pairs. The Values
property of the HttpCookie class is a NameValueCollection, which allows key and
value pairs to be added to the cookie, in addition to the cookie’s Value property value.
The NameValueCollection class provides an Add() method for adding keys, and their
respective values, and an Items() property for accessing and editing keys. This allows
a cookie to contain not one, but numerous values. This is particularly useful if a group
of settings are being stored using cookies, and they are all related. For example, a
cookie storing information about the user could contain key and value pairs storing
his/her name, shipping address and telephone number, all in one cookie.
The following code creates a cookie that includes several key and value pairs:
Dim objCookie As New HttpCookie()
objCookie.Name = "UserInfo"
objCookie.Value = "UserInfo"
objCookie.Values.Add("Name", "Peter McMahon")
objCookie.Values.Add("Telephone", "1234567")
objCookie.Values.Add("Address1", "123 Some Road")
objCookie.Values.Add("Address2", "Somecity")
objCookie.Values.Add("Address3", "123ZIP, SOMESTATE")
The Set() method can be used to modify values, as can the Item() property:
objCookie.Values.Set("Name", "P McMahon")
objCookie.Values.Item("Telephone") = "01234567"
The Item() property can also be used to access the value of a key/value pair:
Dim strName As String = objCookie.Values.Item("Name")
Retrieving Cookie Values
After a cookie has been set, unless it has expired or been manually removed by the
user, it can be accessed at any time, from any page in the website that created it. The
Request object’s Cookies property is again an instance of the HttpCookieCollection
class, and it is from here that the relevant values can be retrieved. The Item() property
can be used in the same way as with the Response.Cookies object, with the parameter
specifying either the name of the cookie, or its numerical index. An instance of the
HttpCookie class will be returned, and from there, either the Value property can be
used, or if applicable, the Values property can be used to obtain the key/value pairs
stored within the cookie.
To retrieve the value from a cookie “MyCookie” and store it to a variable, the
following code would be used:
Dim strMyCookie As String = Request.Cookies.Item("MyCookie").Value
Similarly, the values of several key/value pairs from a cookie “UserInfo” could be
extracted and stored in variables as follows:
Dim strName, strAddress1, strAddress2, strAddress3 As String
strName = Request.Cookies.Item("UserInfo").Values.Item("Name")
strAddress1 = Request.Cookies.Item("UserInfo").Values.Item("Address1")
strAddress2 = Request.Cookies.Item("UserInfo").Values.Item("Address2")
strAddress3 = Request.Cookies.Item("UserInfo").Values.Item("Address3")
Conclusion
Moving between web forms is an important aspect of any web application, but since
HTTP and the Web are inherently stateless, user and application state must be
manually maintained. Fortunately ASP.NET provides numerous methods of
maintaining state that make the job significantly easier. The chapter also dealt with
how state can be maintained using only intrinsic capabilities of HTML and HTTP,
such as querystrings and hidden form variables, along with how ASP.NET makes
using the HTTP state mechanism, cookies, easier to allow state to be persisted over
long periods of time. The session and application state functionality of ASP.NET was
also introduced.
Chapter 7
RequiredFieldValidator
Possibly the most common use for validation is to ensure that all required fields on a
form are filled out. For example, in a registration form, such fields would include
name, e-mail address and telephone number. Ensuring that such fields are filled out is
the work of the RequiredFieldValidator control. Each field that is required must have
its own RequiredFieldValidator control, which has a property that defines which
control it is to validate. As with all the validation controls, a message must be
supplied that is displayed when the validation fails – i.e. the user does not fill out a
value for the required field.
To try out the RequiredFieldValidator, perform the following steps:
1. Create a new Web Form
2. Add a label, textbox, button and RequiredFieldValidator control as
shown in figure 7.1.
RequiredFieldValidator Options
The RequiredFieldValidator control features numerous properties that allow the
control to be customised for most needs. Since the display mechanism for showing the
error message when validation fails is similar to that of a Label control, most of the
properties of a Label are present, allowing background and foreground colours, along
with font styles and sizes to be changed. However, the RequiredFieldValidator also
includes two properties that are unique to the validation controls. These are Display
and EnableClientScript.
EnableClientScript Property
Display Property
• None
• Static
• Dynamic
By default the value of the property is Static. This property defines how the error
message is displayed when the validation fails. A value of None will result in the error
message not being shown. Generally this will not be used unless a
ValidationSummary control is in place, because the user must be informed of their
error. Static and Dynamic are fairly similar, but there is one important difference
between them. Firstly, the difference between Static and Dynamic does not have
anything to do with how the client-side validation script works, or when validation
events are triggered. When Static is chosen, the error message, even when not visible,
takes up a physical portion of the screen where the message would be displayed when
it becomes visible. Dynamic however, does not use up screen space when not visible,
but when it is displayed, it shifts all the elements on the page down so that it can be
displayed.
From a more technical perspective, when Static is chosen, the ‘visibility’ CSS
property is used to hide the error message, whereas when Dynamic is chosen, the
‘display’ CSS property is used.
Page.IsValid
If the form in a page that includes validators and has “invalid” controls, is submitted,
either because scripting was not enabled in the client browser, or the validation
controls were not set to use client-side validation, then it is important that the server-
side code is aware of whether or not the submitted form is “valid”. Even if a control,
or controls, on the form are not submitted with suitable values (in the case of the
RequiredFieldValidator, any value for a validated control would be suitable, so long
as it’s not an empty string, however this is not always true for the other validation
controls, which will be introduced shortly), the event handler for the button, or other
control used to submit the form, will still be fired. Therefore a mechanism must be in
place to ensure that the code knows whether the form submission included invalid
control values or not, otherwise the entire objective of using validators would be
undermined.
For example, if a form included two textboxes, a button and a label, and this form
would add the two numbers in the textbox when the button was clicked and output the
result to the label, the button’s Click event handler needs to know that values have
been entered in the textboxes. RequiredFieldValidators are used to ensure that values
are entered, but if client-side validation is circumvented, the button event will still fire
when the form is submitted, and an exception will be thrown because an empty string
cannot be converted to an integer. To check that the submitted form does not include
invalid controls, the Page object’s IsValid property should be used. This property
returns a boolean value of True if the submitted form does not include any invalid
controls, and False if it does. An If condition should be wrapped around the code
inside the button’s Click event handler ensuring that the Page.IsValid property is
True.
Figure 7.2 shows the UI of the above example, with Listing 7.1 showing the
button’s Click event handler.
[ Figure 7.2 – Page.IsValid example form ]
Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnSubmit.Click
If Page.IsValid Then
lblAnswer.Text =
System.Convert.ToString(System.Convert.ToInt16(txtNumber1.Text) +
System.Convert.ToInt16(txtNumber2.Text))
End If
End Sub
[ Listing 7.1 ]
The TextBox controls are named txtNumber1 and txtNumber2 respectively. The
Button is btnSubmit and the Label holding the answer is lblAnswer. When btnSubmit
is clicked, the validating controls should ensure that txtNumber1 and txtNumber2 do
have values inserted. If the form is submitted when txtNumber1 or txtNumber2 do not
include a value, the Page.IsValid property will be False when the button’s event
handler is called. If it is True, the textboxes must contain values, in which case
lblAnswer’s Text property is set to the sum of the values contained in txtNumber1 and
txtNumber2.
Regardless of whether the submitted form includes invalid controls or not, the
Page.IsValid property will always be true when read in the Page_Load event handler,
as it has not yet been correctly set by the server, and should therefore not be relied
upon in event handlers for the Page’s OnLoad event.
CompareValidator
Fields on a form often need to be compared to one another, or specific values to
ensure that the user is entering acceptable data. Possibly the most commonly used
example of where a CompareValidator could be implemented is in a registration form,
where the user must enter their chosen password twice. Another common scenario
where fields must be compared to each other is entering an email address, then
entering it a second time as a confirmation. The CompareValidator also lets you
ensure that one field’s value is larger than, or less than another field’s. This ability
could potentially be used when a user is asked to enter in a range of dates, where the
start date must obviously be before (i.e. less than) the end date.
The ability of the CompareValidator to compare the value of a control to a
specific defined value also has numerous uses. It could be used to ensure that a value
is larger than 0. Perhaps the user cannot set a date before the current date. Conversely,
the user might not be allowed to enter a value after a specific date.
If the value entered by the user must be bigger than X, but smaller than Y, then
rather than using two CompareValidator controls, the RangeValidator should be
used.
However, possibly the most useful feature of the CompareValidator is its ability to
ensure that the value entered by a user is of the correct data type. For example, if an
Integer is expected, then using the CompareValidator to ensure that indeed an Integer
value is entered mitigates the need to include error checking code when converting
the control value to an Integer in the server-side code when the postback occurs.
Comparison Operators
The default behaviour of the CompareValidator control is to ensure that the values
compared are equal. However, the Operator property of the control allows a variety of
operators to be chosen. The property must be assigned a value from the
System.Web.UI.WebControls.ValidationCompareOperator enumeration. The
enumeration includes the members shown in Table 7.1.
Member Description
DataTypeCheck Ensures that the value is of a specific type (dealt with in the section
“Comparing Control Values with Data Types”)
Equal Ensures that the relevant values are equal
NotEqual Ensures that the relevant values are not equal
GreaterThan Ensures that one value is greater than the other
GreaterThanEqual Ensures that one value is greater than or equal to the other
LessThan Ensures that one value is less than the other
LessThanEqual Ensures that one value is less than or equal to the other.
[ Table 7.1 – ValidationCompareOperator Enumeration Members ]
In previous example, setting the Operator property of the valEmailConfirm
control to NotEqual will result in the error message being displayed if two equal
strings are entered into the textboxes. Since VB.NET can compare strings using the
inequality operators, setting the Operator property to GreaterThan will result in the
error being displayed is the first textbox’s value is B and the second’s is A, for
example. However, the Operator property’s values other than ‘Equal’ only really
become useful when used with data types other than strings.
HTML form controls are typeless by nature, and they therefore assume the type
“string” in .NET, since that is the data type that they most often contain. For the most
part, this is perfectly acceptable and does not pose too much of a problem. However,
with validation, the data type is often important. For example, if a CompareValidator
were used to ensure that an integer value entered into one textbox was larger than one
in a second textbox, the desirable result would be obtained if the value of the first
textbox was 9 and the value of the second was 8. However, if the value of the first
was 10, and the value of the second was still 8, the CompareValidator would
incorrectly claim that the form was invalid, because when comparing strings, the first
character is compared against the first character, and the comparison only continues to
the second character if the first characters were the same. Thus, in a string
comparison, 8 is larger than 10. This is obviously not the desired result, and the
validator needs to be informed that it must perform its comparisons as Integer
comparisons, not as string comparisons.
To this end, the CompareValidator includes the Type property, which accepts a
value from the System.Web.UI.WebControls.ValidationDataType enumeration. Table
7.2 lists the enumeration members.
Member Description
Currency Casts the values to the Currency data type before comparing.
Date Casts the values to the DateTime data type before comparing.
Double Casts the values to the Double data type before comparing.
Integer Casts the values to the Integer data type before comparing.
String Casts the values to the String data type before comparing.
[ Table 7.2 – ValidationDataType Enumeration Members ]
Figure 7.4 shows a form that allows the user to enter a start and an end date. The
CompareValidator has the following properties set:
• ControlToCompare: txtStartDate
• ControlToValidate: txtEndDate
• Operator: GreaterThan
• Type: Date
[ Figure 7.4 ]
In this case, the CompareValidator will compare the values entered as dates, and
ensure that the end date is a later date than the start date.
[ Figure 7.5 ]
The first CompareValidator had the following properties set:
• ControlToValidate: txtStartDate
• Operator: GreaterThanEqual
• Type: Date
• ValueToCompare: 2002/01/01
The form will be invalid if a date earlier than 2002/01/01 is entered, but any date
including and after 2002/01/01 will be valid.
When dealing with the Date data type in the CompareValidator, it is very important
to ensure that the correct date format is used. The correct format’s positioning of
the year, month and day elements is largely dependant on the server’s regional
settings, but whatever the YMD format, the short date should be used, and a time
should not be included. This is the reason for the previous example specifying the
string parameter “d” in the Date’s ToString() method – it ensures that the function
returns the date in short format, without a time.
This ability lends even more power and flexibility to the CompareValidator. The
ValueToCompare property can now be assigned a value from any server resource,
from an event log entry, to a node in an XML document, to a value from a SQL
Server database.
Validating Data Types
Possibly one of the greatest causes for the requirement of error checking on the
server-side has traditionally been the fact that users will often enter data in an
incorrect format. For example, when an Integer is required, they might enter a
floating-point number, which, if unchecked, will result in an exception being raised
when the conversion from the string from the control to an Integer occurs. Likewise
spaces or commas might be used to separate groups of numbers, which would again
cause errors when the type conversions take place in the server-side code.
The CompareValidator’s Operator property can be assigned one member of the
ValidationCompareOperator enumeration that is the odd one out, so to speak – the
DataTypeCheck operator. When this operator is chosen, only the ControlToValidate
property should be set – neither the ControlToCompare or ValueToCompare
properties need to be used. As the name suggests, the DataTypeCheck operator
ensures that the data in the ControlToValidate control’s value is entered in a suitable
format, such that it can be successfully converted to a specific data type. The data
type that this “mode” of the CompareValidator checks against is specified in the Type
property, which accepts a value from the ValidationDataType enumeration – “String”,
“Integer”, “Date”, “Currency” or “Double”.
For example, to ensure that the value entered into a textbox, txtValue, is a date, a
CompareValidator should be inserted onto the form, and have the following properties
set:
• ControlToValidate: txtValue
• Operator: DataTypeCheck
• Type: Date
This would ensure that a value that can be cast from string to DateTime is inserted
into the txtValue control.
RangeValidator
The RangeValidator is, in many respects, quite similar to the CompareValidator when
it uses the ValueToCompare property. The RangeValidator allows a specific range of
values in a control to be enforced. A simple example would be a field that requires a
value from 1 to 10 to be entered. A RangeValidator control could be used to ensure
that only Integers values from 1 to 10 are allowed. As with the CompareValidator, the
RangeValidator can compare and validate values using the String, Integer, Double,
Date and Currency data types. The RangeValidator could therefore also prove useful
for entering in amounts of money (nothing less than 1 unit, but no more than 1000), or
for a booking sheet, where dates before the current cannot be chosen, but the booking
cannot be more than 4 weeks in advance.
To demonstrate the capabilities of the RangeValidator, perform the following
steps:
1. Create a new web form.
2. Add a label, textbox, button and RangeValidator as shown in figure
7.6.
3. Name the controls lblBooking, txtBooking, valBooking and
btnSubmit respectively.
4. Set the ErrorMessage property of valBooking to “Please enter a date
between 2002/01/01 and 2002/02/01.”
5. Set the ControlToValidate property of valBooking to “txtBooking”.
6. Set the Type property of valBooking to “Date”.
7. Set the MinimumValue property of valBooking to “2002/01/01” and
the MaximumValue property to “2002/02/01”.
8. Compile the project and load the Web Form.
9. Enter in a date – any date between the 1st of January 2002 and the 1st
of February will be allowed, but any date outside that range will
result in the validator displaying an error message.
As with the CompareValidator, and the remaining validator controls, if no value is
entered the control will pass validation – to prevent this, a RequiredFieldValidator
must again be used. The RangeValidator’s Type property allows multiple different
data types to be compared, and as such the RangeValidator should provide suitable
functionality for most situations where a value must be bound within a specific range.
The MinimumValue and MaximumValue properties can be set at runtime in the
Page_Load event handler, making the RangeValidator more flexible. These values
could be read from a database, a text file, XML, or even a Web Service.
Figure 7.7 shows a form with a label, textbox, RangeValidator and button added,
named lblNumber, txtNumber, valNumber and btnSubmit respectively. The validator
is set to validate txtNumber.
[ Figure 7.7 ]
Listing 7.2 shows the Page_Load event handler for the form, which requires the
user to enter a number in a randomly chosen range, which is displayed in the label.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim intMin, intMax As Integer
Dim objRandom As New System.Random()
intMin = objRandom.Next(1, 10)
intMax = objRandom.Next(11, 20)
valNumber.MinimumValue = intMin.ToString()
valNumber.MaximumValue = intMax.ToString()
valNumber.ErrorMessage = "Please enter a number between " &
intMin.ToString() & " and " & intMax.ToString() & "."
lblNumber.Text = "Number between " & intMin.ToString() & " and " &
intMax.ToString() & ":"
End Sub
[ Listing 7.2 ]
The System.Random class provides the Next() method to generate random
numbers. The Next() method has several overloaded versions, one of which allows
two Integer parameters to be passed specifying the range that the random number
must fall between. intMin is therefore assigned a value between 1 and 10, and intMax
is assigned a value between 11 and 20. The validator’s MinimumValue and
MaximumValue properties are then set using these randomly generated numbers.
Since these properties are string properties, the Integers must first be converted using
the ToString() method. The validator’s ErrorMessage is then set appropriately, and
finally the label is set to inform the user of the range that they must enter a value
between.
Figure 7.8 shows what the output may look like:
[ Figure 7.8 ]
The ability to programmatically modify the RangeValidator’s properties lend an
even greater flexibility to the control, especially when combined with database access,
and other functionality offered by the .NET Framework.
RegularExpressionValidator
Of all the validating controls, the RegularExpressionValidator is probably the most
powerful and most flexible, with the exception of the CustomValidator. However, the
power of the RegularExpression validator is in essence not owed to itself, but rather to
regular expressions, as the name suggests. Basically the RegularExpressionValidator
forces a control’s value to conform to a regular expression. This allows the validator
to ensure that almost any string is entered in the correct format, from telephone
numbers, to email addresses to SSN’s. To continue, a rudimentary knowledge of what
regular expressions are, and how they work is required.
Regular Expressions Primer
Regular Expressions is an entire topic in itself, and entire books have been written
solely on the topic, so this primer is far from being a complete guide to the subject
matter, but should prove sufficient to leave you at least knowing the basics of string
matching using regular expressions.
Regular Expressions can be thought of as a highly specialised programming
language for dealing with strings. The two functions that regular expressions can
perform are ensuring that a string matches a particular format, or extracting a
substring from a string that matches a particular format. In a regular expression, you
specify the format that you want a string to match. This is basically done by providing
a set of rules that a string must comply with in order for it to “match”. For the
purposes of this primer, only string matching will be demonstrated.
The regular expression’s rules are laid out in a string called a pattern. The most
basic pattern is one that matches a string, character for character. For example, the
pattern “test” will match the string “test”, and no other string. However, there is a list
of so-called “metacharacters” that don’t match themselves. These characters provide
special functionality to the regular expression and generally affect the way that other
parts of the regular expression function. The metacharacters are:
.^$*+?{[]\|()
Firstly, the square braces (‘[‘ and ‘]’) - these are used to denote a character set, or
a list or range of characters that can be matched. For example, the pattern “[abc]” will
match with any of the strings “a”, “b” or “c”. The – character can be used to specify a
range. Thus the pattern “[a-c]” is equivalent to the pattern “[abc]”. The regular
expression that matches with any lowercase alphabetic character is “[a-z]”.
The metacharacters do not apply their special functionality when used within
classes. For example, the pattern “[xyz?]” will match with the strings “x”, “y”, “z”
and “?”. The question mark is a metacharacter, but since it is included in a set, it’s
special meaning does not apply.
The caret (‘^’) can be used with in a set to specify that the pattern must match any
character(s) except those specified in the set. The caret must be the first character in
the set – if it is included elsewhere it will simply be matched like any other character.
The pattern “[^abc]” will match with any one character string that is not “a”, “b” or
“c”.
The backslash (‘\’) acts as an escape character, and allows metacharacters that
would normally perform a specific function to be matched as if they were normal
characters. The backslash must simply proceed the metacharacter that must be
escaped. The pattern “\[“ will therefore match the string “[“, as will “\\” match “\”.
The backslash is also used to specify pre-defined sets. These are commonly used
sets that can be specified as shown in table 7.1.
Pattern syntax Description Equivalent Set
\d Matches any decimal digit [0-9]
\D Matches any non-digit character [^0-9]
\s Matches any whitespace character [ \t\n\r\f\v]
\S Matches any non-whitespace character [^ \t\n\r\f\v]
\w Matches any alphanumeric character [a-zA-Z0-9_]
\W Matches any non-alphanumeric character [^a-zA-Z0-9_]
[ Table 7.1 – Regular expression character set shortcuts ]
These escapes can also be used within sets. “[\w ]” accepts any alphanumeric
character, or a space. “[\w\s]” accepts any alphanumeric character, or any whitespace
character. Likewise, these escapes can also be used outside of sets. “\w\s” matches
with any string where its first character is an alphanumeric character, and its second is
a whitespace character.
The period, or fullstop (‘.’) metacharacter is used to specify any character, except
a newline. Therefore the pattern “a.b” will match with any 3-character string starting
with “a” and ending with “b”, so long as the 2nd character isn’t a newline character.
Thus “a1b” will match, as will “a b”, as will “a|b”. However, “a c” won’t match (the
3rd character is “c”, not “b”), and neither will “c b”.
Some of the most important rules that can be set in regular expression patterns are
“repeating rules”. Regular expressions can be created that allow a string to repeat a
character, or set of characters, a specific number of times. This is where regular
expressions really set themselves apart from simply string manipulation functions.
The asterisk (‘*’) metacharacter is used to specify that the character, or character
from a character set, before it can be used any number of times. Thus “ab*c” will
match with “ac” (no “b”’s), “abc” (1 “b”), “abbc” (2 “b”’s) etc. This functionality also
applies to character sets, so the pattern “a[xy]*c” will match with “ac”, “axc”, “axyc”,
“axxc”, “axxyc” etc, but not “axy”, because it doesn’t end with “c”, and not “abc”,
because “b” is not in the specified set. The pattern “.*” should match with any string,
as it allows any character to be specified any number of times. This can also be
compounded with regular characters, and for example the pattern
“.*@microsoft.com” should allow any string ending with “@microsoft.com” to be
specified.
The plus (‘+’) metacharacter is very similar to the asterisk, but has one subtle
difference. It allows a character, or a character from a character set to be specified one
or more times. The asterisk allows the character not to be entered at all, but the plus
metacharacter requires at least one instance of the character to appear. Therefore the
pattern “ab+c” will match with “abc” and “abbc”, but not “ac”. Again, the plus
metacharacter can also apply to sets, and can also be used in conjunction with other
characters or metacharacters in a complex pattern.
The question mark (‘?’) metacharacter allows a character to be specified either
once, or not at all. It effectively marks a character or set as optional. Therefore the
pattern “Micro-?soft” will match with both “Microsoft” and “Micro-soft”.
The curly braces (‘{‘ and ‘}’) allow a specific range of repetitions of a specific
character or character set to take place. The minimum and maximum number of
repetitions is specified within the braces, separated by a comma. The pattern “.{3,6}”
matches with any string (provided that it doesn’t include any newline characters)
between 3 and 6 characters long. The string must not be less than 3, or more than 6
characters long. This notation can also be used to specify only a minimum or
maximum number of repetitions by omitting one of the values. Omitting the minimum
will result in a minimum of 0 being assumed, and omitting the maximum will result in
a maximum of infinity being assumed. Therefore the pattern “.{3,}” will match with
any string 3 characters or longer, whereas the pattern “.{,3}” will match with any
string less than or equal to 3 characters in length. The last function that the curly
braces can perform is to specify a specific number of repetitions for a character or
character set. This is done by only supplying one value, with no comma. The pattern
“\d{5}” will therefore match with any 5-digit decimal number. The string must only
contain digits, and must be exactly 5 characters in length.
The brackets (‘(‘ and ‘)’) are used for grouping characters and sets together. When
used together with other metacharacters, this can be used to create rules that would
otherwise not be possible, or more difficult to implement. For example, the pattern
“(do*g)?s” specifies that the “do*g” rule can be omitted, since it is grouped using
brackets and modified using a question mark. However, if the user decides to include
the string inside the brackets, it must be correctly formatted. In either case, the trailing
“s” is obligatory. Therefore “dogs” will match the regular expression, as will “dgs” or
“doogs”. “s” will also match (it is omitting the rule in the brackets), but “ds” will not
(it is missing the required “g”), and neither will “oogs”. “dog” will also fail to match.
However, if the regular expression omitted the brackets and was simply “do*g?s”, the
“d” would be required, as would the “s”, and only the “g” would be optional.
The vertical bar (‘|’) is used to specify an “or” condition - either one part of the
expression can be completed, or another part can be completed. The vertical bar is
normally used with brackets to ensure that the “or” is applying to the correct parts of a
pattern and for the purpose of clarity for the programmer. For example, the pattern
“Hello, \w*|Hi, \w*” specifies that the string must either begin with “Hello, “ or “Hi,
“, and can then contain any number of alphanumeric characters, but to clarify its
meaning, it would be better written as “(Hello, \w*)|(Hi, \w*)”. For that pattern
“Hello, Lionel” would match, as would “Hi, Lionel”, but “Hello Lionel” would not,
and neither would “Hi Lionel” (in both cases, the comma is missing).
Armed with this knowledge of regular expression fundamentals, you should now be
able to both dissect and dismantle almost any regular expression, and build your own
regular expressions to match almost any string format imaginable. At first glance,
regular expressions can seem incredibly complex and cryptic, but by breaking them
up into smaller parts and individually identifying what each part of a regular
expression does by applying the rules, it becomes fairly trivial to interpret the
functionality of any given pattern. Likewise when building regular expressions, the
task should be broken down into small steps, which makes the whole process
significantly easier.
[ Figure 7.9 ]
3. Name the controls lblTelephone, txtTelephone, valTelephone and
btnSubmit respectively.
4. Set the ErrorMessage property of valTelephone to “Please enter a
telephone number in the format (000) 123-4567, 000-123-4567 or
123-4567.”
5. Set the ControlToValidate property of valTelephone to
“txtTelephone”.
6. Click on the button in the properties window when the
ValidationExpression property is selected. The “Regular Expression
Editor” dialogue should appear, as shown in Figure 7.10.
This is one of the many pre-defined regular expressions that are available for
common formatting tasks, such as e-mail addresses, postal codes, and in this case,
phone numbers. However, the defaults may not be suitable, so it is possible to define a
custom regular expression for your needs. This can either be based on one of the pre-
defined patterns, or started afresh. To continue our example of requiring that a US
phone number is entered we could extend the regular expression so that it requires
that US’s International code also be entered (+1). “\+1 ” can be added to the
expression to achieve this, so the final pattern is:
\+1 ((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}
Conclusion
The RegularExpressionValidator is a very powerful tool that can be used to ensure
that a string conforms with almost any format imaginable, from standard phone
number formats, to email addresses to ISBNs. The use of the
RegularExpressionValidator can be very useful in ensuring that data captured from
users is all in the same format, which makes analysing and manipulating it later on
significantly easier.
CustomValidator
Although the functionality provided by the previous validator classes is very
comprehensive, situations do exist where a validation is required that cannot be
achieved by any of the previous controls. For such situations, the CustomValidator
control is provided, which allows you to build your own client- and server-side
validation methods for a specific control. It essentially lays the foundations for a
validator, and lets you specify exactly what criteria the control to be validated must
conform to.
An example of a scenario where a regular validator would not provide sufficient
functionality to correctly validate a control is if a leap year must be entered, and the
validator must ensure that a year that is a leap year is entered. To build a form that
does this, perform the following steps:
1. Create a new web form
2. Add a label, textbox, CustomValidator and button, as shown in figure
7.11.
[ Figure 7.11 ]
3. Name the controls lblLeapYear, txtLeapYear, valLeapYear and
btnSubmit respectively.
4. Set the ErrorMessage property of valLeapYear to “Please enter a leap
year.”
5. Set the ControlToValidate property of valLeapYear to txtLeapYear.
6. Add the event handler in Listing 7.3 to the web form’s code behind
class:
Private Sub valLeapYear_ServerValidate(ByVal source As Object, ByVal args
As System.Web.UI.WebControls.ServerValidateEventArgs) Handles
valLeapYear.ServerValidate
Try
Dim i As Integer = Convert.ToInt16(args.Value)
If (i Mod 4) = 0 Then
'It is a leap year
args.IsValid = True
Else
args.IsValid = False
End If
Catch e As Exception
args.IsValid = False
End Try
End Sub
[ Listing 7.3 ]
7. Compile the project and load the web form.
8. Enter in a year that is not a leap year (such as 2002), and click
“Submit”. The form will submit, but the postback will result in the
validation error message being displayed.
As with all the other validation controls, if an empty string is entered, the control
passed validation. This validator also affects the whole form’s validity, so if the
control is not successfully validated, the Page.IsValid property is set to false.
The CustomValidator’s ServerValidate event is raised when validation is
performed on the server. The event handler for the event receives two parameters, an
object, and an instance of the System.Web.UI.WebControls.ServerValidateEventArgs
class. This object’s Value property passes the value of the control to validate, and the
IsValid property, which specifies whether the control is valid or not, must be set
inside the event handler.
To check whether the entered year is a leap year, the year is divided by 4, and if
there is no remainder, the year is a leap year. The IsValid property is set accordingly,
and the whole block is encased in a Try block to catch any exceptions that may occur
when converting the passed value to an Integer.
There is however one noticeable omission from this control thus far, namely,
client-side validation – the control is only validated after the form is submitted.
However, the CustomValidator does provide for this functionality through the
ClientValidationFunction property. This property specifies a client-side function that
can be used to validate the ControlToValidate on the client side. The function that is
specified must accept two parameters, similar to its server-side counterpart – the
sender object, and an arguments object. Listing 7.4 shows the client-side script block
that defines a function for validating a leap year.
<script language="javascript">
function LeapYearValidate(sender, args)
{
if ((args.Value % 4) == 0)
{
args.IsValid = true;
}
else
{
args.IsValid = false;
}
}
</script>
[ Listing 7.4 ]
Insert this code into the web form’s UI code, and set the valLeapYear control’s
ClientValidationFunction property to “LeapYearValidate”. The Web Form will now
validate the txtLeapYear control on the client-side if possible, but it will still always
validate the control on the server-side, using the ServerValidate event handler code
inserted earlier.
Conclusion
The CustomValidator is available to provide a method of validation almost any
conceivable situation, and its ability it effectively only limited by the creativity of the
programmer. It provides a solution when all the other validation controls are unable to
offer suitable validation for controls in specific non-standard applications.
ValidationSummary
The ValidationSummary control isn’t actually a validator. Rather, it provides a
mechanism that groups together all the incorrectly filled out controls on a form that
didn’t successfully validate and lists all the errors that need to be corrected. This can
be useful for placing at the top or bottom of a long form, so that users know exactly
which controls need to be corrected, rather than having to scroll through the entire
form checking to see which items haven’t been validated.
The use of the ValidationSummary is very simple indeed – in a form that includes
validation controls, simply add a ValidationSummary control. It will automatically
recognise the validators on the page, and display the validation summary accordingly
at run time. The following steps demonstrate the use of the ValidationSummary
control:
1. Create a new web form.
2. Add the controls and set the appropriate properties such that the form
looks like Figure 7.12, and acts as expected.
[ Figure 7.12 ]
3. The final control on the form is a ValidationSummary control. At
design time, its text will normally always be displayed as it is here.
However, at runtime it will automatically recognise the validators on
the form. The ValidationSummary control will operate without
changing any properties, so all the properties can be left at their
defaults.
4. Compile the project and load the web form.
5. Click “Place Booking”. All the RequiredFieldValidators should
display their error messages, but the ValidationSummary should also
provide a summary of all the errors that occurred at the bottom of the
page.
6. Fill in the name field, and try to submit the form again. The
ValidationSummary should change to reflect that the name field has
now been correctly filled in.
The three properties unique to the ValidationSummary are DisplayMode,
ShowSummary and ShowMessageBox. These three all deal with how the summary is
displayed. The DisplayMode property takes a value from the
ValidationSummaryDisplayMode enumeration. The members of this enumeration are
shown in Table 7.2.
Member Description
BulletList Summary displayed in a bulleted list.
List Summary displayed in a list.
SingleParagraph Summary displayed in a single paragraph.
[ Table 7.2 – ValidationSummaryDisplayMode enumeration members ]
The BulletList member is the default value for the DisplayMode property.
The ShowSummary property is a boolean that specifies whether or not the
summary should be displayed on the page at all. This would normally be used in
conjunction with the ShowMessageBox property, otherwise it would negate the entire
purpose of the ValidationSummary control by not displaying any summary.
The boolean ShowMessageBox property, which is by default false, specifies
whether or not the validation errors should be displayed in a MessageBox (a
JavaScript window.alert() dialogue). This can be useful for grabbing the attention of
the user, and can be used on its own, by setting the ShowSummary property to False,
or in conjunction with an on-page summary.
Conclusion
The ASP.NET validation controls provide a very wide range of validating
functionality that is both easy-to-use, very effective and extremely flexible. By
automatically combing client- and server-side validation capabilities, the controls
ensure that the user obtains the quickest and best experience by using client-side
validation when available, whilst still ensuring that fields are always entered correctly
by using a mandatory server-side validation. The Page.IsValid property provides the
necessary means to programmatically check whether a page has been submitted with
valid controls or not, and the ValidationSummary control provides an easy solution
for displaying a summary of the errors that the user needs to correct in a lengthy form.
Chapter 8
ADO.NET Architecture
All the ADO.NET classes are found inside the System.Data namespace. The classes
contained within it can, for our purposes, be broken up into two groups – the classes
that are used for manipulating disconnected data from any data source, and the classes
for communicating with external data sources.
Many figures have been quoted as to exactly how much better the SQL Manager
Provider performs than the OLE DB Managed Provider. I conducted tests to settle
the matter in my own mind, and although I certainly wouldn’t class my testing
methodology as exhaustive and foolproof, I found that when a fair amount of data
was being accessed, there was a 50% increase in performance of the SQL Server
provider over the OLE DB provider, which is quite significant.
Although the only two Managed Providers that ship with .NET provide support
for OLE DB and SQL Server data sources, Microsoft hopes that other major database
vendors, such as Oracle, will develop Managed Providers for their own database
products, thus allowing more efficient access than if the regular OLE DB provider is
used.
As was mentioned in the previous section, Microsoft doesn’t include direct support for
ODBC in .NET – that is, an ODBC Managed Provider isn’t shipped with the .NET
Framework. However, if you absolutely have to use ODBC, you can do one of two things.
Firstly, you could download the additional ODBC Managed Provider from the MSDN website
and use that, or alternatively you could use an OLE DB workaround. OLE DB allows you to
access an ODBC source through it by using the “Microsoft OLE DB Provider for ODBC
Drivers”, as apposed to the SQL Server or Jet OLE DB providers. Since using this method
means that you won’t have to distribute the separate ODBC Managed Provider .NET
Assembly, this workaround does have some advantages.
However, once data has been retrieved from a database using the appropriate
Managed Provider, it will normally be transferred into the control of the database-
agnostic “data manipulation classes”. I say, “normally,” because you don’t have to
use the disconnected model, and can work in a connected fashion if it would be more
appropriate in a given situation. Once this data has been transferred into the generic
data-holding classes, the connection to the data source can be closed, and you are free
to manipulate the data in whatever way you see fit. At the moment this doesn’t sound
particularly useful or innovative, and you might even say, “But I can already do that
with ADO,” but the real power lies not so much in the principal (which was indeed
possible in ADO), but by the breadth of functionality implemented by the data-
holding classes. The diagram in figure 8.1 shows the role that the Managed Providers
play in ADO.NET.
Figure 8.1 – Managed Providers in ADO.NET
As you can see, the .NET Managed Providers communicate with the data source
(be it a SQL Server database, an Access database or any other data store that can be
accessed using OLE DB or one that additional Managed Providers can communicate
with). The Managed Provider do that actual interfacing with the database, and
(assuming you’re going to use the disconnected model) send the data to the data
storage classes. It’s actually not a fluke that the bidirectional arrow is included inside
the “.NET Managed Providers” block. This is because each Managed Provider
contains a class that will fill the data storage classes and do updates on the data store
when the data in the storage classes changes, which obviously involves reading data
from, and writing data to the data source.
Up until now, we’ve only looked at the concepts of ADO.NET, and not at any of
the classes that you’ll actually be working with. Now that you’ve got a decent
understanding of what a Managed Provider is, I’m going to show you the parts that
constitute one by using the SQL Server Managed Provider as an example. The OLE
DB provider has almost identical classes (just with different names), so you needn’t
worry if you don’t use SQL Server. First up, all the classes that constitute the SQL
Server provider are located in the System.Data.SqlClient namespace (the OLE DB
ones are in System.Data.OleDb, and all start with OleDb instead of Sql). These are the
most important ones:
* SqlCommand
* SqlConnection
* SqlDataAdapter
* SqlDataReader
* SqlError
* SqlException
* SqlParameter
* SqlTransaction
If you’ve used ADO before, some of these classes should sound vaguely familiar,
such as SqlCommand, SqlConnection and SqlParameter. These three obviously map
fairly closely to their ADO equivalents. However, there are also several new-looking
classes, whose purpose isn’t immediately clear from their names, such as the
SqlDataAdapter and SqlDataReader. These two don’t have direct ADO equivalents,
and serve to fill newly created roles in the ADO.NET architecture. Figure 8.2 shows
how the main classes in the SQL Server Managed Provider connect to communicate
with the database.
Figure 8.2 – SQL Server Managed Provider Architecture
The SqlConnection Class
The SqlConnection class is the one responsible for communicating with the database.
It creates the connection (and closes it), and allows the SqlCommand class to execute
commands on the SQL Server. A SqlCommand object cannot operate without having
an instance of a SqlConnection. The SqlConnection class works very similarly to its
ADO counterpart, and allows you to specify a connection string (which is a string that
normally specifies the IP/name of the SQL Server to connect to, the database to be
used, and the authorization credentials, amongst other things). The connection string
is normally specified as an argument in the class’ constructor, and the connection can
then be opened using the Open method, or closed using the Close method.
The SqlCommand is the object that is used to execute SQL queries on the SQL
Server. It can execute text queries or stored procedures, hence the reason why
SqlParameter objects are contained within it. A SqlParameter object represents
(surprise, surprise), a parameter in either a text query or a stored procedure, and can
represent either an input, output or return value. The SQL query text can either be
assigned in the constructor, or using the CommandText property.
The SqlCommand object provides numerous methods for executing queries,
depending on what kind of result you’re expecting, and what you’d like to do with it.
The four main methods are:
* ExecuteScalar
* ExecuteXmlReader
* ExecuteReader
* ExecuteNonQuery
The SqlDataAdapter class has no real ADO equivalent, but it’s actually very easy to
understand, just so long as you don’t try and relate it to anything from ADO.
Essentially a DataAdapter is responsible for both retrieving (i.e. SELECT statements)
and manipulating (INSERT, UPDATE and DELETE statements) data in the
disconnected data classes. It is the “link” if you will, between the Managed Provider
and the disconnected data. The SqlDataAdapter has 4 properties that define the
SqlCommand objects that control these 4 operations – SelectCommand,
InsertCommand, UpdateCommand and DeleteCommand. As their names suggest,
they each represent a SqlCommand object that contains a query for retrieving records,
as well as inserting, updating and deleting records respectively. The DataAdapter
controls when and how these SqlCommand objects are executed, so once you’ve
assigned them, you don’t need to do anything more with them. Although it may seem
fairly complex, the DataAdapter is very easy to use, and beyond setting the
appropriate SqlCommand objects (and if you don’t plan on manipulating the data, you
only need to set a SqlCommand), you don’t have to do much for it to work. The only
two methods you need to know are Fill and Update. Fill, as its name suggests, fills a
disconnected data storage object (passed as an argument) with the data obtained by
executing the SelectCommand SqlCommand. Update, also as its name suggests,
executes the necessary INSERT, UPDATE and DELETE queries through the
respective SqlCommand objects to update the data source such that it is the same as
the data in the disconnected storage object. It does this by working out which rows
have changed in the storage object (which incidentally is also passed as an argument),
and then executing the appropriate SqlCommand depending on whether a row was
updated, inserted or deleted. Figure 8.3 shows the structure of the SqlDataAdapter
class.
Figure 8.3 – The SqlDataAdapter Architecture
In essence, the purpose of the DataAdapter is to automate the process of filling the
disconnected storage and updating the data store when the contents of the storage
change. It reduces a large amount of “plumbing” that would have to be done if it
didn’t exist, because you’d have to fill the storage classes manually, manually iterate
through them checking for changed rows and manually execute the appropriate SQL
query for each changed row.
We’ve now covered the basic functionality offered by the ‘essential’ Managed
Provider classes, so it’s time to move on to the generic, database-agnostic data storage
classes.
Many .NET trainers and authors have compared the .NET DataSet (you’ll discover
what a DataSet is in a moment) to a RecordSet, saying that it is the closest thing in
.NET to a RecordSet, but I disagree. To me, the SqlDataReader seems must closer
to a RecordSet than any other .NET class, simply because it performs the same
function as a RecordSet (or at least part of the function of a RecordSet), and it
operates in a very similar way. I’ll leave the final judgment up to you.
The DataSet Class
Since the DataSet doesn’t apply specifically to any one Managed Provider, it is
simply included in the System.Data namespace. The DataSet is the central class for
storing disconnected data – it is the “glue” that holds everything to do with the data
together, and contains a set of collections of other objects, which hold the data and
information about it. Figure 8.4 shows how the DataSet is structured.
Figure 8.4 – The DataSet Class
In the diagram, the property names are shown followed by the their type in
parentheses. There are only two properties of the DataSet that you need to know at
this point – Tables and Relations. As you can see, the Table property stores a
collection of DataTable objects, and the Relations property stores a collection of
DataRelation objects. The DataSet, unlike any object in ADO, can contain multiple
tables of data, and thereby multiple sets of results from a database. Although this in
itself is convenient, it is made even more powerful by the set of relations that a
DataSet stores – these can be used to define how tables in a DataSet relate to each
other, and is designed specifically for parent-child relationships, such as a Customers
table and an Orders table. However, we’ll begin with the class that stores the data in a
DataSet, the DataTable.
The DataTable class, as the name suggests represents a single table of data in
memory. However, this table need not map directly to a table in a database, since the
DataSet is separated from the data source, and can therefore store fields from multiple
different tables in a data source in a single DataTable if need be. Typically the
DataAdapter will be used to fill a DataTable object inside a DataSet, and this can be
manipulated later through the Tables property. However, a DataTable doesn’t have to
be created by the DataAdapter, and you can also create your own DataTable objects
manually and add them to a DataSet.
The DataTable has several important properties that are essential for manipulating
the data contained within. The TableName property, which is a string, is used to
uniquely identify a table within a DataSet, and the DataSet’s Tables index property
allows either the index of a table, or a table’s name to be passed, and it will return a
reference to the respective DataTable object.
The Columns property is a reference to a DataColumnsCollection object, which is
a collection of DataColumn objects. It allows columns to be added, edited or removed
through its Add and Remove methods, and its Item property, which allows a specific
DataColumn object to be manipulated. You will not normally use the Columns
property a great deal if you are loading data from a DataAdapter, as it creates the
appropriate DataColumn objects automatically, but if you are creating a DataTable
manually you will have to add columns to the table.
The DataAdapter will also automatically detect the data type and other
information about the columns mentioned in the SQL query and will set the
appropriate values in each DataColumn object.
The Rows property, like the Columns property of the DataTable, is a collection of
objects, namely a DataRowsCollection. This contains a collection of DataRow
objects, and the DataRow is a class that you will use continually when dealing with
data access. It is possible to enumerate through all the DataRow items in the
collection, or you can use the DataRowsCollection Find method, which will return a
single DataRow where the primary key value of the row matches the one passed as an
Object argument in the Find method. The Item index property will return the single
DataRow at the specified index position in the collection.
The DataRow represents a single row of data in a DataTable, and exposes an Item
index property that allows the values from each of the cells (columns) that make up
the row to be accessed. The values can be retrieved, as well as edited through the Item
property, which is of type Object, and can have either the index of the column or the
name of the column (as defined by the corresponding DataColumn object in the
DataTable’s Columns collection) passed to it to identify which cell should be
accessed.
The DataRow contains another interesting property, RowState, which you will
rarely access directly but is useful to know about nonetheless. The RowState property
is of type DataRowState, an enumeration that describes the row’s state in the
DataTable, such as ‘Unchanged’, ‘Modified’ or ‘Added’, for example. It is a read-
only property and is set automatically by the system. When the DataAdapter is
attempting to determine if and how a row has changed to see what SQL query should
be executed when its Update method is called, it uses each DataRow object’s
RowState property. When the DataAdapter fills a DataTable, all the DataRows’
RowState properties are set to the Unchanged enumeration variable, but if you modify
a row, its RowState flag with change. Similarly, if you add a row, it will be marked as
Added, and if you remove a row, it will be marked as ‘Deleted’. You can actually
reset the RowState to Unchanged for all the rows in a DataTable by calling the
DataTable’s AcceptChanges method, although if you call a DataAdapter Update, this
is done automatically.
When displaying data, you will probably be binding the data from a DataTable (in a
DataSet) to a data-displaying Web Control, for example, a DropDownList. However,
you may wish to display the data in the DataTable in a different way, or you may only
want to display a small portion of it. This is where the DataView class fits in.
Essentially all a DataView provides is a “front” for a DataTable, representing the data
in it to data-bindable controls. The DataView allows the data to be both sorted and
filtered using its Sort and RowFilter properties, which both accept string values.
These provide the power of SQL, but without the need to reconnect to the SQL Server
to obtain the desired results.
The DataTable’s DefaultView property is a reference to a DataView object, which
by default simply provides a view of all the data’s rows and columns without any
addition filtering or sorting. Since data binding is so common, this property is
provided more as a convenience than a necessity, so you don’t have to create a
separate DataView object every time you want to bind data to controls.
The DataSet allows parent-child relationships between its tables to be defined through
the use of the DataSet Relations property. This property is a reference to the
DataRelationCollection class, which is a collection of DataRelation objects. A
DataRelation is very easy to create – the class constructor accepts the name of the
relation as a string, followed by two DataColumn parameters – the first being the
primary key column from the parent table, and the second being the foreign key
column from the child table. Once the DataRelation has been added to the DataSet’s
Relations, it is immediately usable. When accessing a DataRow from the parent
DataTable, you can call the DataRow’s GetChildRows method, passing the name of
the applicable relation as a string, and the method will return an array of DataRow
objects from the child table.
Typed DataSets
Summary
The architecture of ADO.NET is quite a significant departure from that of ADO. The
introduction of the new DataSet class, which allows multiple result sets to be stored,
and then related to each other using parent-child relationships, provides a very
powerful framework for working with disconnected data. Even though the
connections to databases may be created and maintained by different Managed Data
Provider classes, all reading and manipulation of data can be done using the generic
DataSet class in a disconnected fashion. If however you need to simply read and
display data, you can avoid the overhead of a DataSet by opting to use the DataReader
class, which provides a fast, read-only, forward-only way of reading data from a
database. Since all Managed Providers must implement certain interfaces, they will all
be very similar to use, and still provide much greater performance than in previous
versions of ADO by optimizing connections for the specific data sources that they
connect to and communicate with.
Before you can access and modify a database, you need to create a connection to it.
This is a fairly simple process, which you start off by right clicking the “Data
Connections” node and choosing “Add Connection…” The “Data Link Properties”
dialog should appear, and will be showing the second tab. Switch to the first tab,
“Provider”, and the dialog should look like figure 8.6.
Figure 8.6 – Choosing an OLE DB Provider for a new database connection
By default the OLE DB provider for SQL Server databases is selected, but as you
can see there are numerous other options for other database types. The other most
notable ones are the Jet provider, which allows access to Jet data sources such as
Microsoft Access, the Oracle provider for access to Oracle databases and the provider
for ODBC compatible databases. This allows for access to all ODBC data sources that
don’t have OLE DB drivers. Of course, if your installation doesn’t include OLE DB
drivers for a particular type of database that you need to use, you should be able to
download them from the database vendor – even the open source database MySQL
has ODBC and OLE DB drivers available.
Once you’ve selected the appropriate Provider, you can click the “Next >>”
button. The options displayed in the “Connection” tab, which should now be shown,
will vary depending on which OLE DB provider you’ve chosen. For a Jet source,
you’re simply asked for a filename and login details, but the options for a SQL Server
database, as shown in figure 8.7, are slightly more involved.
Figure 8.7 – Connection options for connecting to a SQL Server through OLE DB
The first step is to enter in the server name you want to connect to. Next, you
choose which authentication method you’d like to use to connect to the server, and if
applicable, enter your login details. The final step is to choose which database on the
server you will be working on. You can then ensure that the connection is working by
clicking the “Test Connection” button, which should display a confirmation message
if the test succeeded, in which case you can click “Ok” to add the new Data
Connection. The extra node should appear in the Server Explorer, and depending on
the functionality offered by the database and VS.NET, you’ll be able to modify the
following database components.
Database Diagrams
The Database Diagrams node expands (if the database contains any diagrams) to show
the name of each diagram, which can then be viewed, edited or deleted through the
right-click context menu. Additionally, right-clicking the node and choosing the “New
Diagram” option can be used to create new database diagrams. The diagram editor is
fully integrated into the IDE, and displays as another tab in the main editing
environment. It looks and acts identically to the diagram editor in SQL Server’s
Enterprise Manager.
Tables
The Tables node displays a list of the tables contained in the database, and each
column in a table is displayed as a child node of the table. Properties about the
columns, such as maximum length and data type can be obtained by selecting the
column node and looking at the Properties Window.
VS.NET provides extensive functionality for managing tables. You can retrieve
and edit data in a table, as well as modify the design of a table from within the IDE. It
is also possible to add triggers, as well as entire new tables. Rounding off the
functionality is the ability to export table data, and drop (remove) tables.
To access the data editor for a table, right click the table node and choose
“Retrieve Data from Table” from the context menu. The editor should be displayed in
the IDE’s editing area, similar to figure 8.8.
Figure 8.8– The VS.NET Table Data Editor
This functionality is available for most databases, and is very useful when you
quickly need to check whether you code has indeed added, modified or removed table
entries as it was supposed to.
The IDE also includes an integrated table designer, so you can both modify
existing table designs or create new tables. The designer is identical to the one in the
Enterprise Manager, so there’s no longer any need to leave the VS.NET IDE if you
want to add or edit tables. Both these options are accessible via the right-click context
menu of a table node, as well as an option to drop the table (which is quite useful if
your code creates temporary tables, but crashes before it gets to the cleanup code
where the tables are dropped).
The final significant options in the table node context menu are those to add a
trigger to the table; generate the SQL code that creates the table and export the table
data to a file. Adding a trigger results in the VS.NET IDE SQL code editor being
displayed, which provides a great environment for writing SQL code. We’ll look at it
in more detail in the ‘Stored Procedures’ section. The other two options are fairly self-
explanatory, and might be useful when you’re deploying the application.
Views
Views provide the same options as for tables, except for extracting the data – namely
the ability to review the data in an editing window, as well as design the views and
add triggers. All the columns involved in a View are shown as child nodes of the view
node, and can be inspected using the Properties Window.
Stored Procedures
Because stored procedures in SQL Server are compiled and thereby offer greater
performance than executing simply SQL text queries, you should try to use stored
procedures wherever possible if the query is going to be used in a high-load page.
Stored procedures also alleviate many problems associated with thwarting “SQL
insertion” security breaches. Because of this, you’ll probably spend a fair amount of
time building and revising stored procedures, which incidentally are also a great place
to store business logic. Visual Studio.NET provides a very useful stored procedure
editor, which I would venture to say is much better than the Enterprise Manager’s
offering. To edit a stored procedure, right click it and choose “Edit Stored Procedure”
from the context menu, or you can add a stored procedure by choosing the appropriate
option from the menu. The editor includes the obligatory code syntax highlighting
functionality, but the most useful feature is the “Query Builder”. Any INSERT,
UPDATE, DELETE or SELECT SQL block is outlined with a blue border. This
indicates that the Query Builder can be used to help construct the statement by right
clicking anywhere inside it, and choosing the “Design SQL Block” option. Figure 8.9
shows the stored procedure editor with a SQL statement that can be edited using the
Query Builder.
Figure 8.9 – The Stored Procedure Editor
The Query Builder is very similar to its equivalent in Enterprise Manager, except
it is now available when editing stored procedures, which it isn’t in SQL Server’s
default administration tool. This really is a time-saver, especially when you’re
building complex queries involving multiple table joins, for example. The Query
Builder has four main panes, as shown in Figure 8.10. The first is the “Tables” pane,
which shows the tables that the query involves, how they relate to each other (foreign
key relationships etc) and what columns are available in each. Extra tables can be
added to the query by right clicking in the blank space in the pane and choosing “Add
Table”. Once you have the tables you need, you select the columns from each table
using the checkboxes. These selections will be shown in the second pane, which
allows you to specify whether the column will be outputted (in the case of a SELECT
statement), whether it should be the basis for sorting the results and whether it should
form part of the criteria as to which rows are affected (the WHERE clause). The third
plane shows the generated SQL code, which you can modify and the changes will be
reflected accordingly in the previous two panes, and the final pane shows the results
of the query if you run it by right clicking anywhere in the Query Builder and
choosing “Run”.
Figure 8.10 – The Query Builder
Besides being able to create and edit stored procedures, the node in the Server
Explorer also allows you to execute a procedure, but more importantly, “step into” a
procedure to debug it, step by step. Both of these options are available through the
context menu. Finally, the child nodes of each procedure display the parameters that
the procedure receives and outputs, as well as the column names that it will output if
it contains a SELECT statement. You can see more about each node by selecting it
and viewing it in the Properties Window, which will show details such as data type
and length.
Functions
This node displays the list of user-defined functions, and allows you to create, edit or
remove them from the database. The same SQL code editor is used when editing a
function as for triggers and stored procedures, along with the Query Builder.
Summary
The data tools in Visual Studio.NET help to make VS.NET the only application you
need running when building web-based applications. If you’re using a SQL Server
backend, the functionality provided by the data tools in the IDE is for the most part all
you need. There are a few pieces of Enterprise Manager “missing” in the Server
Explorer, but all the most common tasks are catered for, and the stored procedure
editor is even better than the one included in Enterprise Manager. However, the SQL
Query Analyzer and SQL Profiler tools are largely unimplemented in VS.NET, so
you’ll still need to use them separately.
Data binding was introduced to ASP.NET to help reduce the number of lines of code
it requires to display data on a form. Without data binding, it would be necessary to
manually write the code to assign values to controls (normally DropDownList,
CheckBoxList, RadioButtonList or other more advanced data display controls, such as
the DataList and DataGrid) from the database. For example, if you wanted to display
the name of each customer in the Customers table in the Northwind sample database,
you would have to manually loop through a result set of entries from the Customers
table, and write the code to add the entries for each iteration. However, with data
binding, you can simply “bind” the results of a query (either in a DataSet or in a
DataReader) to a control, and ASP.NET will do whatever is necessary to display the
data in your chosen control. This can be a great timesaver, especially when the
DataList and DataGrid controls are used, which will be introduced later.
Unfortunately ASP.NET only supports one-way binding – in other words,
ASP.NET can set the values of controls according to data from a data source,
however, it cannot automatically update the data source when the user changes these
controls. Data Binding also has other significant differences to what Visual Basic
users will be used to, although the core concept of binding data from a database to a
control property still remains. If you’re a VB programmer, the first thing you think of
when “data binding” is mentioned is probably a form with textboxes and a data
control on it, where the user can scroll through the entries one-by-one using the
forward and backward buttons on the data control. This is not how data binding works
in ASP.NET, and there is no “data control” for you to use, and the TextBox is not a
data bindable control – most controls do not provide support for data binding binding,
and the TextBox control is not one of them. Table 8.1 shows the list of controls that
data can be bound to.
Table 8.1 – Data Bindable Controls
The first step when building a page that is going to access data from a database is to
decide whether to use Visual Studio .NET’s ADO.NET support and create the
Connection and other required objects in the IDE, or to code everything manually.
There are no major inherent disadvantages of using VS.NET to help speed up
development – after all, all VS.NET is doing is writing the same code for you that you
would have to write if you didn’t use it. In this section, we’ll build a page that shows
off simple data binding, as well as the basics of working with DataSets to retrieve
data. We’ll start off by using the VS.NET controls to provide a large part of the
required functionality, but I’ll also show you how to build the page from scratch,
without any help from the VS.NET IDE for the data access functionality.
So, to begin with, open up a new blank Web Form and then switch to the “Data”
tab in the Toolbox, as shown in figure 8.11.
Figure 8.11 – The Data “Controls” in the Toolbox
Of course, none of these are “controls” in the conventional sense – they aren’t like
a TextBox, or a DropDownList. They are “non-visual” controls, because the user
doesn’t see them on the form as they don’t have a UI, for obvious reasons. This means
that when you insert any of these controls, no changes are made to the UI file (the
HTML). Typically what will happen is that a new object declaration will be added to
the code-behind Page class (similar to what happens for regular controls), and any
initialization that might be required will be added to the “Web Form Designer
Generated Code” region, also in the code-behind file. However, to enable you to
modify properties on the ADO.NET objects in the designer, VS.NET will display a
separated region at the bottom of the page designer, where all the non-visual controls
are represented and can be modified or removed.
The first ADO.NET object that you need to insert is a Connection object. As with
the previous section, the examples will all use the SQL Server provider, but if you
don’t have access to a SQL Server machine, you can use the OLE DB provider and
connect to a Microsoft Access version of the Northwind database, which is almost
identical, except for functionality where stored procedures are required.
Add a SqlConnection (or OleDbConnection, if you need to connect to an Access
database) object to the Web Form by dragging and dropping the item from the
Toolbox onto the page, just as you would for a TextBox, or any other control. The
Designer should create the new section for non-visual controls, and add the
SqlConnection object to it, as shown in figure 8.12.
Figure 8.12 – Using Non-Visual Controls in the Designer
Once you’ve added the Connection, you need to provide a connection string in the
ConnectionString property of the SqlConnection object, which defines what data
source is going to be used. The connection string comprises of several details,
including the location of the data source (in the case of a SQL Server, this would
normally be the IP address of the database server, and for an Access database it would
be a file path), as well as authentication details and any other details pertinent to the
type of database that is being connected to. However, the OLE DB provider requires
that one extra parameter be included in the query string, the “Provider”, which defines
what type of OLE DB provider to use – the SQL Server-specific Connection never
requires this, since it will (and can only) always connect to a SQL Server database.
You don’t have to manually create the connection string, as VS.NET will do it for
you. Simply select the new Connection object and go to the ConnectionString
property in the Properties window. If you click on the dropdown arrow (as shown in
figure 8.12), a popup will appear showing all the available connections (this list is
derived from the Data Connections node in the Server Explorer). You should already
have the appropriate connection to the Northwind database created, in which case you
can select it, or if not, you can choose the “New Connection” option to set up a new
connection to it. After selecting a connection, the ConnectionString property should
display text similar to this:
data source=Peter\NetSDK;initial catalog=Northwind;persist security
info=False;user id=sa;workstation id=PETER;packet size=4096
As you can see, the different “segments” of the connection string are separated
using semi-colons, and the string is constructed using a series of name-value pairs.
The three most important pairs in this connection string are “data source”, “initial
catalog” and “user id”. The “data source” specifies the IP address or name of the SQL
Server machine, “initial catalog” defines the default database to work with on the
database server and “user id” defines the user id to connect to the server with. In this
case the server doesn’t require a password for SQL Server authentication (although of
course a production machine should), but if it did, an additional “password” pair
would also be included in the connection string.
Once a connection is available, you need a DataAdapter to retrieve data from the
database and insert it into a DataSet. Insert a SqlDataAdapter object by dragging and
dropping it into the page, and the “Data Adapter Configuration Wizard” should
appear, as shown in figure 8.13.
Figure 8.13 – The Data Adapter Configuration Wizard
Click “Ok” the close the Query Builder, and return to the wizard. The query
should now be displayed in the text box in the wizard. In this example, we’re not
going to allow the user to modify the CustomerID or CompanyName fields – we’re
simply going to display a list of customers in a DropDownList by binding the data to
it, so there’s no need for the wizard to generate the rest of the SQL statements in this
case, because they won’t be used. To prevent the wizard from doing this, you need to
open the “Advanced Options” dialog (shown in figure 8.14), by clicking the button at
the bottom left.
Figure 8.14 – Advanced Options in the DataAdapter Configuaration Wizard
The first option, “Generate Insert, Update and Delete statements” speaks for itself,
really. If it’s checked, the wizard will automatically create these statements for you,
based on the SELECT statement you provided, and if not, then it doesn’t. Uncheck
this option, because this example only needs to retrieve data. The other two options
should be grayed out.
Even though you’re not going to use them now, I’ll explain the other two options
as well. “User optimistic concurrency” makes the wizard generate the SQL statements
in such a way that they help to avoid concurrency conflicts by ensuring that the record
the user is trying to modify or delete hasn’t been changed by another user since the
DataSet was last loaded (since the DataSet is disconnected, it is highly possible that
its contents will differ from the live data in certain scenarios). This is a fairly useful
option, and provides a relatively good generic solution for solving the problem of
concurrency, although it obviously won’t be perfect for every situation, and you’ll
also have to manually decide what to do when a query is not executed because the
database row in question has been changed.
The third option, “Refresh the DataSet”, when checked, makes insertion and
update queries always refresh the DataSet by calling the SELECT statement after they
are executed. This is particularly important when records are added, since
“autonumber” or “identity” fields, which are calculated by the database server, will
probably be needed as the primary key field of a table, and any columns that weren’t
explicitly set in the SQL statement will revert to the database defaults, which might
also need to be used later on.
With the advanced options set, click “Ok” to close the dialog, and then “Next” in
the wizard to move to the final confirmation screen, which should display the
message, “The data adapter ‘SqlDataAdapter1’ was configured successfully.” You
can click “Finish” to close the wizard.
If you ever need to re-run the DataAdapter Configuration Wizard, you can do so by
right-clicking the DataAdapter object in the non-visual controls section of the
designer, and choosing the option, “Configure Data Adapter…”
Inserting a DataSet
The final “control” we need to add from the Data palette in the Toolbar is a DataSet,
which will of course be used to store the data from the database, and will be what the
visual controls actually on the page will bind to. As you know, the DataSet is data
server-agnostic – it uses XML as its backend, and doesn’t rely on any specific
database server or provider, so drag a DataSet from the Toolbar onto the page. A
dialog will appear asking whether you want a “Typed dataset” or an “Untyped
dataset”. An untyped DataSet is perfectly adequate for now (and in fact, using a typed
dataset in this scenario wouldn’t be a major advantage and would only waste
resources), so select that option and click “Ok”. A generic DataSet object will be
added.
Before you move on to building the actual page, you may wish to rename the three
ADO.NET objects to more “friendly” names than their defaults, “SqlConnection1”,
“SqlDataAdapter1” and “DataSet1”, by selecting each one and modifying its Name
property. However, for the sake of simplicity in this example, we’ll move on without
using different names.
The page will perform two functions – firstly, it will display a list of the customers
in the Northwind database, and secondly, it will display the name of the contact and
his/her title for the selected company. To implement this, you need three controls – a
DropDownList, a Button and a Label (all of them Web Server Controls). Add the
DropDownList and the Button on the same line, next to each other, and add the Label
on the line below them. Name them lstCustomers, btnViewContactDetails and
lblContactDetails respectively.
Setting the DropDownList up so that it displays the customer list is fairly easy.
Firstly, move down to the DataSource property in the Properties window. When you
click on the dropdown arrow, a list of the available data sources that the control can
bind to will be shown. This will generally include a list of all the DataSet and
DataView objects on the page. Select “DataSet1”. Since the DataSet can hold multiple
tables, it is also necessary to specify the specific table that must be used. To do this,
modify the DataSource property to read, DataSet1.Tables("Customers"). If you were
using a typed DataSet, the available tables would be shown in the dropdown, but with
a generic DataSet you have to type the appropriate value in.
The next step is to set the DataTextField and DataValueField properties. These
define the column that the DropDownList will bind to in the chosen DataSource –
DataTextField is the column that will be displayed (i.e. for each row in the DataSet,
this column value will be set as the Text property of each DropDownList item), and
DataValueField is the column that will be stored in each ListItem’s Value property.
Set DataTextField to “CompanyName” and DataValueField to “CustomerID”. You
will normally bind the Value to a primary key (in this case, CustomerID), so that each
DataItem in the DropDownList can be uniquely identified.
With the DropDownList set up, all that remains is to provide that code that will
bind the data from the database to the DropDownList. To do this, switch to the code
view and add in the code so that the Page_Load event handler reads like listing 8.1.
Listing 8.1
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles MyBase.Load
SqlDataAdapter1.Fill(DataSet1, "Customers")
If Not Page.IsPostBack Then
lstCustomers.DataBind()
End If
End Sub
Before you can bind the data, you must first fill the DataSet, otherwise there will
be no data to bind to. The SqlDataAdapter’s Fill method has numerous overloaded
versions, but the one used here accepts two parameters – the first is the DataSet that
will be filled, and second is the name of the table inside the DataSet that must be
filled. The Fill method executes the DataAdapter’s SELECT statement and fills the
specified table with the results from the query. If the table does not already exist, it is
created. Since the DataSet object is obviously destroyed between postbacks, it must
be reloaded every time the page loads, which is why it does not appear in the
following If block.
Since the lstCustomers control has already had its data binding properties
configured at design time, all that remains to be done is call its DataBind method,
which binds the data from its DataSource property to the control. However, the
control only needs to be bound once (when the page is first loaded), because
ViewState will persist the items in the list through postbacks. The If condition ensures
that the DataBind method is only called the first time the page is loaded. If this is not
done, then the DropDownList will be reloaded on every postback, and will therefore
lose state information, such as the currently selected item.
Although the remaining functionality hasn’t yet been implemented, you can load
the page, which will show the list of customers in the Northwind database as shown in
figure 8.15 – and all it took was 4 lines of code.
Figure 8.15 – Data binding to a DropDownList
Reading data directly from the DataSet
After implementing the binding functionality, the second feature that needs to be built
is the code such that when the Button control is clicked, the Label will display the
contact person’s name and title for the selected customer. There’s no way to do this
using data binding, so we have to manually find the row (record) in the DataSet that
corresponds to the selected customer in the DropDownList, and then assign the values
of the ContactName and ContactTitle columns in that row to the Label control.
Fortunately, because the DataSet provides powerful manipulation features, this is
actually fairly simple to do.
There are three steps that need to be performed to complete the task. Firstly, you
need to find the index of the selected item in the DropDownList. Secondly, you need
to relate the index of the selected item to the indices of the rows held in the DataSet
and obtain a reference to the corresponding DataRow object, and thirdly, you need to
retrieve the ContactName and ContactTitle column values from that DataRow.
All three of the steps require a line of code each or less. To set up the page
appropriately, set the Button’s Text property to “View Contact Details”, and set the
Label’s Text to an empty string. You can then double click on the Button control to
create a Click event handler for it, and insert the code in listing 8.2.
Listing 8.2
Private Sub btnViewContactDetails_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnViewContactDetails.Click
Dim strContactName As String = _
DataSet1.Tables("Customers").Rows(lstCustomers.SelectedIndex _
).Item("ContactName")
Dim strContactTitle As String =
DataSet1.Tables("Customers").Rows(lstCustomers.SelectedIndex _
).Item("ContactTitle")
lblContactDetails.Text = strContactName & ", " & strContactTitle
End Sub
These three lines are all that is required. Obtaining the index of the selected item
in the DropDownList is very easy, since that control exposes a SelectedIndex
property, which returns an Integer value. The DataSet also allows rows to be
referenced using indexed values, and since the entire Customers table is bound to the
DropDownList, indices from the DropDownList map directly to indices of rows in the
DataSet (i.e. if the index of an item in the DropDownList is 5, then the index of the
corresponding row in the DataSet will be 5 as well).
As you learned in the ADO.NET theory section, the DataSet stores a reference to
a collection of DataTable objects in its Tables property, which is a reference to a
DataTableCollection object. This object’s Item property is used to obtain a reference
to a specific DataTable object inside the collection by passing either the index of the
table (which would be 0 in this case, since no tables were added before the Customers
table in the current example), or by passing the name of the table as a string.
However, the Item property is the default property, and therefore does not have to be
explicitly used, which it isn’t in this code listing. Therefore the following segment of
the first line obtains a reference to the “Customers” DataTable object:
DataSet1.Tables("Customers")
With this reference to the appropriate DataRow object, you can obtain the value
from any of the row’s columns through the Item property (which is also the default
property, but is not used implicitly in the example). The Item property accepts either
the index of the column as an integer or the column name as a string, as a parameter,
and returns the value of the column. The first line declares a string, strContactName,
and using an initializer, sets it to the value of the ContactName column in the selected
company’s row. The second line does almost exactly the same, except it declares the
variable strContactTitle, which is set to the contents of the ContactTitle column.
With the two values stores in string variables, all that is left to do is display them.
The third line simply concatenates these two strings and assigns them to the Text
property of the Label so that they are displayed. Figure 8.16 shows the result when the
page is loaded.
Figure 8.16 – Retrieving individual values from a DataSet
As was mentioned at the beginning of this section, the Visual Studio.NET IDE isn’t
performing any black magic to data-enable your pages – all it’s doing is writing a fair
amount of the ADO.NET code for you that you would otherwise have to write
manually. To see this code, open up the “Web Form Designer Generated Code”
region in the code-behind file and look at the InitializeComponent method, which is
executed before the Page.Load event is fired. It should look similar to listing 8.3.
Listing 8.3
Private Sub InitializeComponent()
Me.SqlConnection1 = New System.Data.SqlClient.SqlConnection()
Me.SqlDataAdapter1 = New System.Data.SqlClient.SqlDataAdapter()
Me.DataSet1 = New System.Data.DataSet()
Me.SqlSelectCommand1 = New System.Data.SqlClient.SqlCommand()
CType(Me.DataSet1,
System.ComponentModel.ISupportInitialize).BeginInit()
'
'SqlConnection1
'
Me.SqlConnection1.ConnectionString = "data source=Peter\NetSDK;" _
& "initial catalog=Northwind;persist security info=False;user " _
& "id=sa;workstation id=PETER;packet size=4096"
'
'SqlDataAdapter1
'
Me.SqlDataAdapter1.SelectCommand = Me.SqlSelectCommand1
Me.SqlDataAdapter1.TableMappings.AddRange(New _
System.Data.Common.DataTableMapping() {New _
System.Data.Common.DataTableMapping("Table", "Customers", New _
System.Data.Common.DataColumnMapping() {New _
System.Data.Common.DataColumnMapping("CustomerID", "CustomerID"),
New _
System.Data.Common.DataColumnMapping("CompanyName",
"CompanyName"), New _
System.Data.Common.DataColumnMapping("ContactName",
"ContactName"), New _
System.Data.Common.DataColumnMapping("ContactTitle",
"ContactTitle")})})
'
'DataSet1
'
Me.DataSet1.DataSetName = "NewDataSet"
Me.DataSet1.Locale = New System.Globalization.CultureInfo("en-ZA")
'
'SqlSelectCommand1
'
Me.SqlSelectCommand1.CommandText = "SELECT CustomerID,
CompanyName, " _
& "ContactName, ContactTitle FROM Customers"
Me.SqlSelectCommand1.Connection = Me.SqlConnection1
CType(Me.DataSet1,
System.ComponentModel.ISupportInitialize).EndInit()
End Sub
Summary
Now is probably a good time for an overview of what happens in this page, from start
to finish. First off, when the page is loaded, the required ADO.NET objects (a
SqlConnection, a SqlDataAdapter, a SqlCommand and a DataSet) are all declared and
initialized. The SqlConnection is set to use the appropriate SQL Server and database.
The SqlCommand is set up to execute a SELECT statement to retrieve a list of
customers from the Customers table. The SqlDataAdapter is set to use the created
SqlCommand when it needs to retrieve data from the database and fill a DataSet with
in. Finally, a DataSet is also needed, so that the SqlDataAdapter can fill it and the rest
of the page can retrieve data from it.
With the ADO.NET objects all initialized, the Page_Load event handler is called,
where the SqlDataAdapter’s Fill method is called. When this happens, the
SqlConnection’s Open method is implicitly called, and the SqlCommand is executed.
The results of the query are placed into a new DataTable in the DataSet, and the
SqlConnection’s Close method is implicitly called, so that the connection to the
database server does not remain open. The remaining code in the Page_Load event
handler binds the data in the DataSet to the DropDownList control on the page if the
page is being loaded for the first time.
When the Button control is clicked, a postback is triggered, and when the page is
loaded, all the ADO.NET objects are again created and initialized, and the DataSet is
filled. However, the DropDownList maintains its state, and therefore does not need to
be re-bound to the DataSet. The Button control’s Click event handler is called, which
finds the record in the DataSet that corresponds to the selected customer in the
DropDownList, and the ContactName and ContactTitle column values are retrieved
and assigned to the Label’s Text property. This process is repeated each time the
button is clicked.
Introducing XML
Entire books have been written on XML, so this brief primer certainly doesn’t
cover this important topic in any great depth, but it should be sufficient to give you a
basic understanding of what XML is all about. XML is an acronym for eXtensible
Markup Language. At the most basic level, XML is a format for describing data.
However, unlike other database systems such as Access and SQL Server, XML stores
data in plain text format. Additionally, XML stores data in a hierarchical format,
rather than relational, as used by the more traditional database system, such as SQL
Server. The major benefit of using XML over traditional data stores stems from its
inherent portability – because XML data is stored in plain text, it is very easy to
transport it across platforms, which is why it has been chosen as the format for web
services (which are dealt with in Chapter 14, but basically deal with transferring
commands and information across websites, irrespective of platform or development
framework). However, it is vital to understand that XML is not a replacement for
database systems like SQL Server and Oracle, but rather a complementary
technology.
A Practical Introduction
XML uses “tags” to describe data, in very much the same way as HTML does.
However, although XML data may look like HTML in many respects, the two are
completely separate technologies. XML is a generic format for storing (and not
displaying) data, whereas HTML is a format that allows data to be presented in a web
browser, and uses tags to control the format and layout. In XML, the tags are simply
used to delimit that data and provide additional information.
Many aspects of .NET revolve around XML, and it should come as no surprise
that Visual Studio.NET also provides support for XML. To create an XML file in a
new project, right click on the project node in the Solution Explorer and click Add-
Add New Item. In the “Add New Item” dialog, select “XML File”, type in a suitable
name, and click Open.
In this section, we’re going to create an XML file that will store a list of
employees and related information to demonstrate the basic concepts of XML and
establish the meaning of some “XML lingo”, such as XSL, XML Schemas, nodes,
attributes and so forth. To start off with, create a new XML file named
“employees.xml”. The file will currently only contain one line:
<?xml version="1.0" encoding="utf-8" ?>
In XML, you define your own markup. In other words, you choose which tags
you’d like to describe your data, and use them accordingly. Other than for the XML
processing instructions, there are no set “tags” that must be used – you invent your
own to suite the situation. However, just as HTML documents must conform to
certain rules to be considered valid, so too must XML documents. However, web
browsers are not very strict in their enforcement of HTML rules, and even invalid
HTML documents are displayed as best as the browser can. XML processors are
much more stringent in their enforcement of the basic XML structure rules, so it is
very important that you make sure that your XML conforms to the basic rules.
One such rule is that all XML documents must contain one, and one only, root
“element” or “node”. In XML, an element or node loosely maps to a “tag”, so there is
one “employees” element/node and three “employee” elements/nodes in the above
example. The root element is the element that contains the rest of the XML data. In
this case, the “employees” element is the root element.
Take careful note that for every element, there is a closing tag (i.e. the identical
tag, prefixed by a forward-slash). Another XML rule is that all tags must be properly
closed. Closing a tag can either take the form of a closing tag, or a trailing forward-
slash in the original tag, which will be demonstrated shortly.
Each tag, or element, can contain both text data as well as child nodes. In this
example, the root “employees” element contains no text itself, but contains three child
nodes, which in turn contain text, but no further child nodes.
In this example, each employee element contains three child nodes storing details
about each employee – name, job title and salary. While this method is perfectly
acceptable, it would probably be better to include such information in element
attributes. XML attributes are identical to their HTML counterparts – they are
“properties” that are specified inside the actual tags. The following shows how the
above example could be implemented using attributes rather than child nodes:
<?xml version="1.0" encoding="utf-8"?>
<employees>
<employee name="Peter McMahon" title="Developer" salary="60000" />
<employee name="Leon Cilliers" title="Graphic Designer" salary="45000"
/>
<employee name="Karl Heydenrych" title="Project Manager"
salary="75000" />
</employees>
In this example, the trailing forward-slash method of closing each tag is used. This
code could also have been written like this, using the closing tag method:
<?xml version="1.0" encoding="utf-8"?>
<employees>
<employee name="Peter McMahon" title="Developer" salary="60000">
</employee>
<employee name="Leon Cilliers" title="Graphic Designer"
salary="45000">
</employee>
<employee name="Karl Heydenrych" title="Project Manager"
salary="75000">
</employee>
</employees>
The use of attributes is preferable to child nodes when there is a finite number of
data that may be applicable to a particular element, whereas child nodes are more
appropriate where there is no specific limit to the amount of data that may be related
to an element.
[ Figure 9.1 ]
This shows the XML in a “table” format. The “data view” mode of the XML
editor in Visual Studio.NET can be a very useful tool when creating and editing XML
data stores that structure their data that is in a form that can be represented as a table,
although some XML documents can be well-formed, but are still not able to be
viewed in the “data view” mode. A good example of this is the web.config application
configuration file. However, for data such as our employees information, the “data
view” displays the data into a much more readable and usable format.
[ Listing 9.1 ]
If this is now viewed in the “data view” mode, it is possible to “drill down” into
each employee node by clicking on the + next to the item, as shown in Figure 9.2.
[ Figure 9.2 ]
Clicking on the “employee_task” item will result in a display similar to Figure
9.3, which shows all the task nodes beneath the selected employee node.
[ Figure 9.3 ]
Take note that the display not only shows the task nodes, but also information
about the selected employee node (in the grey bar directly above the tasks “table”),
and in the blue bar above that, clicking the arrow will return you to the table
displaying the employee nodes. Take note that the data view has created two separate
“tables” for display – “employee” and “task”. Even though XML represents data in a
hierarchical format, this demonstrates that it is still possible to manipulate it such that
it is displayed in a more relational way.
[ Listing 9.2 ]
Inspecting the file in the data view will show that the task “table” entries now
have two columns – task_Text and priority.
XML Schemas
In a RDBMS such as SQL Server, you design a database by adding tables, and for
each table defining column names, and the data type for each column. Once this
design has been set, all entries must conform to it, and there’s no way (other than
modifying the actual design) of adding or modifying entries so that they don’t
conform to the design, because the RDBMS simply disallows the request. Since XML
is doesn’t natively have any rules of what elements can be added to a document, and
what attributes and child nodes that element can have, XML can be very unstructured
unless an XML schema is used. An XML schema can be thought of as the “design
rules” for an XML file; it is almost analogous to the database design in a regular
relational database. If an XML file uses a particular schema, then it should conform to
the rules specified in the schema.
Visual Studio.NET provides sterling support for XML schemas, and even
provides the functionality to automatically generate them from an existing XML
document. To generate a schema for our employees.xml file, switch to the data view,
right-click in the “tables” list, and choose “Create Schema” from the context menu. A
new file, employees.xsd will be added to the project, and this is reflected in the
Solution Explorer. Open the file by double-clicking on the node. Figure 9.4 shows the
XML schema editor open with the schema for the employees.xml file:
[ Figure 9.4 ]
XML schema files end with the extension .xsd, as they are written in the “XML
Schema Definition Language”, which is in itself a subset of XML. Fortunately Visual
Studio.NET provides a graphical editor for creating and editing schemas, so it is not
necessary to be able to manually create schemas. The editor is very comprehensive,
and allows for full manipulation of schemas according to the W3C specification. The
ability to graphically create schemas is obviously a major productivity advantage,
especially when working with complex schemas. Even our very simple
employees.xml files requires quite a long schema. Clicking on the “XML” toggle
button at the bottom left of the editor will reveal the actual XML schema code, which
will look similar to listing 9.3.
<?xml version="1.0"?>
<xs:schema id="employees"
targetNamespace="http://tempuri.org/employees.xsd"
xmlns:mstns="http://tempuri.org/employees.xsd"
xmlns="http://tempuri.org/employees.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:element name="employees" msdata:IsDataSet="true"
msdata:Locale="en-ZA" msdata:EnforceConstraints="False">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="employee">
<xs:complexType>
<xs:sequence>
<xs:element name="task" nillable="true" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent msdata:ColumnName="task_Text"
msdata:Ordinal="1">
<xs:extension base="xs:string">
<xs:attribute name="priority" form="unqualified" type="xs:string"
/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="name" form="unqualified" type="xs:string" />
<xs:attribute name="title" form="unqualified" type="xs:string" />
<xs:attribute name="salary" form="unqualified" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
The graphical editor certainly takes a lot less effort to understand what the schema
actually requires an XML document to conform to. In the case of the employees.xsd
schema, the XML document is required to be made up of employee elements. These
elements can optionally contain any of the three attributes (marked with an “A” in the
editor), “name”, “title” and “salary”, which all contain data of type “string”. Each
employee element may also contain a child element (marked with an “E”) of type
“task”. The task element is defined in the second block, and also allows the optional
inclusion of an attribute, this time called “priority”, of type “string”. The line between
the two blocks indicates the parent-child relationship between the two “tables”.
Try out editing the schema by changing the data type of the “salary” attribute in
the employee node block, and the “priority” attribute in the task node block to
“integer” by choosing the appropriate item from the dropdowns that appear in the
fields when they are clicked.
The xmlns attribute is used to specify which schema should be used to validate the
document. The value “http://tempuri.org/employees.xsd” is actually an XML
namespace (XML namespaces are another part of XML, and were created by the
W3C to prevent overlapping XML markups from clashing, but that topic is far beyond
the scope of this introduction). You may be wondering where the “tempuri.org” came
from, as your site has nothing to do with that domain, but tempuri.org is the default
XML namespace, and the one that was automatically chosen and used in the
employees.xsd XML schema (you can change this by changing the namespace in both
the schema and employees.xml).
With the schema now “attached” to the XML document, Visual Studio is able to
now offer intelligent Autocomplete functionality. Figure 9.5 shows the 4 scenarios
where Autocomplete displays options – when a tag is opened within the <employees>
element (1), when an attribute is expected in the <employee> tag (2), when a tag is
opened within an <employee> tag (3), and finally when an attribute is expected in a
<task> element (4).
[ Figure 9.5 ]
Another really useful feature that is included in the XML editor when a schema is
being used is that the editor will automatically underline elements or attributes that
don’t comply with the schema with a red squiggle, in the same way the invalid VB
code is underlined. This automatic validation of the document against the schema is
very useful for quickly spotting errors in the XML.
Conclusion
Armed with enough XML knowledge to be dangerous, you should now be able to
understand and identify the uses and purpose of the examples give in this chapter of
how to manipulate XML data using the .NET BCL. However, as has already been
mentioned, XML is a much bigger topic than this chapter can cover, and not only is
this introduction to XML very brief, and only intended as a primer, but the support of
XML in the .NET framework also extends way beyond what will be covered in this
chapters. XML is deeply rooted in the remoting classes, web services framework, and
ADO.NET, amongst other things, and this chapter only offers a brief look at basic
XML manipulation in .NET.
Using XML in .NET
Reading XML Data
The System.Xml namespace includes numerous classes for dealing with XML
data, but the simplest way of reading XML data from an XML file is by using the
System.Xml.XmlTextReader class. This class provides functionality for XML files
that is not dissimilar to what the System.Data.DataReader class does for relational
data – it provides a forward-only, read-only method of reading data from an XML
file. It contains takes a no-frills approach, and doesn’t automatically valid the XML
against the schema or any other fancy extras, but provides the basic functionality for
quickly and easily reading XML data.
To demonstrate how to perform simple XML manipulation, this section will be
using the XML file outlined in Listing 9.2 that contains a small XML employees
database.
Create a new web form and add two controls – a DropDownList and a Button,
named “lstEmployees” and “btnView” respectively. In the code-behind, create a new
sub-routine, with the code listed in Listing 9.4.
Private Sub LoadEmployees()
LstEmployees.Items.Clear()
Dim objXML As New XmlTextReader(Server.MapPath("employees.xml"))
While objXML.Read()
If objXML.NodeType = XmlNodeType.Element Then
If objXML.Name = "employee" Then
lstEmployees.Items.Add(objXML.GetAttribute("name"))
End If
End If
End While
objXML.Close()
End Sub
[ Listing 9.4 ]
This procedure will load the employees.xml file and run through it, adding each
employee into the lstEmployees dropdown. The XmlTextReader constructor allows
the filename to be passed. Server.MapPath() is used to determine the full physical
path of the employees.xml file, which is then passed to the constructor. Alternatively
it would be possible to pass no arguments to the constructor and then call the Load()
method directly after the declaration like this:
objXML.Load(Server.MapPath("employees.xml"))
This will ensure that the items are only added to the dropdown the first time the
page is loaded – from there onwards, the ASP.NET viewstate functionality will take
care of maintaining the state of the dropdown. The following shows how the
dropdown should look when the page is loaded in a browser:
Reading Individual Items
Once the user has selected an employee from the dropdown, s/he should be able to
view the details of the employee when s/he clicks the “View” button. These details
include the name, position and salary of the employee, as well as a list of the
employee’s responsibilities. To do this, add three text fields, named txtName, txtTitle
and txtSalary, and a ListBox, named lstTasks to the form, as shown in figure 9.6.
[ Figure 9.6 ]
Using the XmlTextReader class to obtain only the values from one specific
element is slightly more involved that just reading the entire XML file. To begin with,
we’ll only read the values stored in the employee element’s attributes, namely the
employee’s name, title and salary. Listing 9.5 shows the code for the Click event
handler of btnView:
Private Sub btnView_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnView.Click
Dim objXML As New XmlTextReader(Server.MapPath("employees.xml"))
While objXML.Read()
If objXML.NodeType = XmlNodeType.Element Then
If (objXML.Name = "employee") And (objXML.GetAttribute("name") =
lstEmployees.SelectedItem.Text) Then
txtName.Text = objXML.GetAttribute("name")
txtTitle.Text = objXML.GetAttribute("title")
txtSalary.Text = objXML.GetAttribute("salary")
Exit While
End If
End If
End While
objXML.Close()
End Sub
[ Listing 9.5 ]
The XmlTextReader object is created, and the employees.xml file is automatically
loaded by passing its path as an argument to the constructor. A loop is again
constructed to iterate through each node in the XML file. The first condition checks to
see if the current node is an element. If so, a second if statement checks if the name of
the element is “employee” (the element name is analogous to the “tag” name) and that
the element’s name attribute is the same as the selected item’s text display. If this
condition is satisfied, then the TextBox controls are assigned values according to the
attributes of the element. Since it is no longer necessary to continue running through
the nodes checking for the correct element, since it has already been found, the “Exit
While” statement is called to stop the loop. Finally, the XML file is closed. Figure 9.7
shows the output in a browser after one of the employees has been selected:
[ Figure 9.7 ]
Reading the items into the list of tasks is slightly more tricky. When reading the
name, title and salary, all that was required was a simple call to the GetAttribute()
method. However, the tasks are not stored as attributes of the employee element –
they are child elements, and the XmlTextReader does not expose a specific method
for obtaining the text and attribute values from such elements (i.e. there is no method
such as GetChildElementValue). Instead, they must themselves be filtered out in the
main loop. Since only the tasks for the selected employee are wanted, this entails
more than simply checking to see if the node’s Name is “task” – the procedure must
keep track of whether or not the loop is currently “inside” the correct employee
element, and therefore must add the current task element to the list. Listing 9.6 shows
the code to achieve this:
Private Sub btnView_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnView.Click
lstTasks.Items.Clear()
Dim objXML As New XmlTextReader(Server.MapPath("employees.xml"))
Dim bInsideSelectedEmployeeNode As Boolean = False
While objXML.Read()
If objXML.NodeType = XmlNodeType.Element Then
If (objXML.Name = "employee") And (objXML.GetAttribute("name") =
lstEmployees.SelectedItem.Text) Then
txtName.Text = objXML.GetAttribute("name")
txtTitle.Text = objXML.GetAttribute("title")
txtSalary.Text = objXML.GetAttribute("salary")
bInsideSelectedEmployeeNode = True
ElseIf (objXML.Name = "employee") And
(bInsideSelectedEmployeeNode) Then
Exit While
ElseIf (objXML.Name = "task") And (bInsideSelectedEmployeeNode =
True) Then
Dim strPriority As String = objXML.GetAttribute("priority")
lstTasks.Items.Add(objXML.ReadString() + " (" + strPriority + ")")
End If
End If
End While
objXML.Close()
End Sub
[ Listing 9.6 ]
The XML file is loaded into the XmlTextReader, and again, a While loop iterates
through each node in the file, and checks to see if the current node is an element using
an if condition. However, the change comes in that there are now three possible
scenarios where code may be executed. The first is if the current node is an
“employee” element and its name attribute is equal to the selected name in the
DropDownList. Just as before, the three TextBox controls have their Text property
assigned to the appropriate attribute value using the GetAttribute() method. However,
the addition to this code block is the flagging of the bInsideSelectedEmployeeNode
variable to true. The purpose of this is to notify further iterations of the loop that the
nodes they are dealing with are children of the correct employee node, and as such
should be handled.
The first ElseIf block checks to see if the current node is an “employee” element,
and that the bInsideSelectedEmployeeNode variable is true. If this is the case, then it
signals that the loop has already reached the selected employee’s element (which is
when bInsideSelectedEmployeeNode is set to true), and that it has finished processing
the children of that node, because the next “employee” element has been reached (and
employee nodes do not contain other employee nodes), so it is therefore appropriate to
exit the loop at this stage, so the “Exit While” statement is executed.
The second ElseIf block deals with the “task” elements that are within the selected
“employee” element. The condition checks that the node is a “task” element, and also
checks to see if the “task” element is within the selected “employee”, which is
signalled by the bInsideSelectedEmployeeNode variable. The ListBox must contain
both the task description, and the priority. The priority is stored in an attribute, so it is
accessed using the GetAttribute(). However, an element’s text is considered as a
separate node, so is not directly accessible, and the XmlTextReader must progress one
node in order to access the text. The XmlTextReader class provides a method for
exactly this type of situation – ReadString(). This method progresses the object to the
next node and returns the node’s Value property as a string.
Therefore the second line in the block adds the “task” element’s text, along with
the priority in brackets. Figure 9.8 shows the final result.
[ Figure 9.8 ]
[ Listing 9.7 ]
The XmlDocument’s constructor does not allow a filename to be passed, so no
arguments are passed, and the following line loads the XML from the employees.xml
file into the object. A variable of type XmlNode is then declared, and used in a For
Each loop that iterates through all the child nodes of the DocumentElement element.
The DocumentElement is a reference to the root node, or the element that contains the
document, which incidentally in the case of employees.xml is the “employees”
element. The ChildNodes property returns an object of type XmlNodeList, which can
be looped through using the For Each construct. In each iteration of the loop, the
objXMLNode variable is instantiated as a new object of type XmlNode, representing
the current node.
The XmlNode object (and consequently objXMLNode) exposes an Attributes
property, which is a reference to an XmlAttributeCollection. The ItemOf() property of
that class allows an attributed to be selected by name, and it’s value returned using the
Value property of the resulting XmlAttribute. The first condition in the loop uses this
to check that the “name” attribute of the current element node to ensure that it is the
same as the selected name before continuing. If it is, then the “selected” node has
been reached, and so the TextBox controls are filled accordingly. However, whereas
with the XmlTextReader it was not possibly to simply create another loop to iterate
through the selected “employee” element’s child nodes (i.e. its “task” elements), the
XmlDocument has that ability, because nodes are exposed as XmlNode objects.
Another XmlNode is declared, and another For Each loop is constructed to iterate
through the ChildNodes collection of the selected “employee” element. The XmlNode
also exposes the InnerText property, which returns the text contained in between the
element’s opening and closing tags. The priority is extracted from the appropriate
attribute, and both the text and attribute values are added to the list. Finally “Exit For”
is called to stop looping through the elements, as the correct one has already been
found.
The result is identical to that using the XmlTextReader, and although the code
isn’t significantly shorter (2 lines) it is definitely much clearer and more elegant, and
shows that for tasks where it is necessary to do more than simply loop through all the
nodes in an XML file, the XmlDocument class is a much better option, and this holds
true even more when more complex tasks are required.
Modifying XML Data
Where the XmlTextReader’s abilities end at being able to read XML data, for the
XmlDocument object, that’s only the start. In addition to being able to load and read
XML data, it can also modify, add and delete XML data, and save the changes to file
using the same object model that is used for reading the data.
To start off with, we’re going to add the ability to edit the name, title and salary of
employees in the employees.xml file. Add a button, btnEditEmployee to the form
(preferably in a new row below the “salary” row), and create a new Click event
handler for it. Listing 9.8 shows the code that should be used to perform the
operation:
Private Sub btnEditEmployee_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles btnEditEmployee.Click
Dim objXMLDoc As New XmlDocument()
objXMLDoc.Load(Server.MapPath("employees.xml"))
Dim objXMLNode As XmlNode
For Each objXMLNode In objXMLDoc.DocumentElement.ChildNodes
If objXMLNode.Attributes.ItemOf("name").Value =
lstEmployees.SelectedItem.Text Then
objXMLNode.Attributes.ItemOf("name").Value = txtName.Text
objXMLNode.Attributes.ItemOf("title").Value = txtTitle.Text
objXMLNode.Attributes.ItemOf("salary").Value = txtSalary.Text
Exit For
End If
Next
objXMLDoc.Save(Server.MapPath("employees.xml"))
LoadEmployees()
End Sub
[ Listing 9.8 ]
This code is fairly similar to Listing 9.7 (which read items from the XML using
the XmlDocument class) for the first part, where it loads the employees.xml file, starts
a loop to iterate through all the child nodes of the root node, and checks each iteration
to see if the current node is the one that is selected in the ListBox. It is in this code
block that the changes occur. Instead of reading from the XmlAttribute objects
obtained using the Atributes.ItemOf() property of the objXMLNode object, the code
instead assigns the respective values from the TextBox controls into the appropriate
attributes. The job tasks are not being edited, so a second loop is not included, and the
“Exit For” statement is executed. The next change comes after the loop, where the
Save() method of the XmlDocument class is called. This method has several
overloaded versions, of which one allows a filename to be passed, which is the one
used in the example. Finally the LoadEmployees() method, which was created in the
previous section, “Reading XML Data”, is called to refresh the lstEmployees
DropDownList, as the user may have changed the name of the selected employee.
Editing the Tasks
Since there is a list of tasks for each employee, editing a specific task posts more
of a challenge than simply editing the name, position or salary of an employee. To
make editing intuitive, the figure 9.9 shows the form interface with the three additions
to enable editing – a TextBox, txtTaskDescription, for holding the task’s description;
a second TextBox, txtTaskPriority, for holding the task’s priority and a Button,
btnEditTask, for performing the update.
[ Figure 9.9 ]
The basic procedure for editing is as follows: the user chooses an item from the
task ListBox control. When the user clicks an item, the page will post back and
display the description and priority of the selected item in the two TextBox controls
beneath the ListBox. The user then makes the modifications to the description and/or
priority and clicks the “Edit Task” button, which performs the actual update to the
XML file.
Firstly, we need to implement the event handler for the lstTasks ListBox’s
SelectedItemChanged event. To start off with, the AutoPostBack property of the
ListBox must be set to True (so that when the user selects a different item, a postback
occurs and the two TextBox controls can be filled). Since the ListBox contains both
the description and the priority, there is no need to load those two values from the
XML file, although there is still some work to be done here, as both value need to be
extracted from one string. Regular expressions and the Regex class provide a great
solution for this.
Listing 9.9 shows the event handler for the ListBox, as well as the two supporting
functions for extracting the description and priority from the selected item.
Private Function ExtractTaskDescription() As String
Dim objRegEx As New
System.Text.RegularExpressions.Regex("(?<desc>[\w\s-]+) \(?[\d]+\)")
Dim objMatch As System.Text.RegularExpressions.Match =
objRegEx.Match(lstTasks.SelectedItem.Text)
If objMatch.Success Then
Return objMatch.Groups("desc").Value
Else
Return ""
End If
End Function
[ Listing 9.9 ]
The two functions, ExtractTaskDescription() and ExtractTaskPriority() pull the
respective strings from the selected item in the ListBox. Both work in essentially the
same way, just using different regular expressions. A new Regex object is created,
with the regular expression being passed into the constructor as a string. Next, a
Match object is created using the Regex object’s Match() method. If the match was
successful (i.e. the Regex object managed to successfully parse the string according to
the specified regular expression), then the appropriate value from the Match object is
returned.
The actual event handler simply calls the two extraction functions and assigns
their results to the two respective TextBox controls.
The second part of implementing this editing functionality is to build the event
handler for the Button’s Click event. This is the code that will actually make the
changes in the XML file. Because it is possible that the user changes the description
of the task, another procedure is also created to refresh the task list. Listing 9.10
shows both this procedure, and the event handler for btnEditTask.
Private Sub LoadTasks()
lstTasks.Items.Clear()
Dim objXMLDoc As New XmlDocument()
objXMLDoc.Load(Server.MapPath("employees.xml"))
Dim objXMLNode As XmlNode
For Each objXMLNode In objXMLDoc.DocumentElement.ChildNodes
If objXMLNode.Attributes.ItemOf("name").Value =
lstEmployees.SelectedItem.Text Then
Dim objXMLTaskNode As XmlNode
For Each objXMLTaskNode In objXMLNode.ChildNodes
lstTasks.Items.Add(objXMLTaskNode.InnerText + " (" +
objXMLTaskNode.Attributes.ItemOf("priority").Value + ")")
Next
Exit For
End If
Next
End Sub
[ Listing 9.10 ]
The btnEditTask Click event handler is identical it the Click handler for
btnEditEmployee up until the first If statement in the outer loop. It basically creates a
new instance of the XmlDocument class, loads the employees.xml file, starts a loop to
iterate through all the “employee” nodes in the XML, and the If condition checks to
see if the current “employee” element is the selected on in the lstEmployees ListBox.
It is in this code block that the changes occur. Firstly, an XmlNode variable is
declared, which will be used in another For Each loop. strDescription is declared as a
string and is initialised by calling the ExtractTaskDescription() function. A second
loop is started, this time looping through all the “task” nodes beneath the current
“employee” element. The If condition checks to see if the current “task” node’s
description (stored in between the node’s opening and closing tags as text) is the same
as the selected item’s description, and if so, the current node’s InnerText and
“priority” attribute are assigned values accordingly. Since the correct node has already
been modified, the inner loop is exited using the “Exit For” statement, as is the outer
loop, using the same method.
Finally the XML file is saved, using the XmlDocument’s Save() method, and the
LoadTasks() procedure is called. This procedure is fairly straightforward. It starts off
by clearing the list, and then proceeds to load the employees.xml file and create a loop
to iterate through all the “employee” elements. The If condition checks to see if the
current node is the selected employee from the ListBox, and if so, proceeds to set up
another loop that iterates through all the “task” items, adding each one to the lstTasks
ListBox.
[ Listing 9.11 ]
The code starts off by opening the employees.xml file as usual, however, for the
most part, this code is entirely different from the editing, or other routines, mainly due
to the fact that it is not necessary to find any particular element before a new
employee can be added. It is simply added directly onto the “employees” root
element. The third line creates a new “employee” element to be stored in a variable of
type XmlElement. Take note that this does not actually add the element to the XML
file – it simply creates an XML element in memory, ready for manipulation. All
employee elements have three attributes: “name”, “title” and “salary”. Since the
employee is being added using a separate TextBox for entering the name, and there
aren’t fields for adding the other two, it is only possible to assign a value for “name”.
However, all three attributes must still be created, because the editing facility expects
them to exist. Creating attributes is done in much the same way as elements – declare
a variable of type XmlAttribute, and initialise it by calling the CreateAttribute()
method of the XmlDocument, and it will return the appropriate object. In this
example, only the Value property is used, which sets the value that the attribute will
hold. Following this, the Attributes.Append() method of the XmlElement variable is
called, passing the XmlAttribute variable as a parameter. This is repeated three times,
for the three attributes. Once all the attributes have been added to the in-memory
element, the element can then be added to the actual document. We want to add it to
the “employees” node, which is the document element (or root node), so the
DocumentElement.AppendChild() method of the XmlDocument object is called,
where the XmlElement is passed as a variable. All that is left to do is save the changes
to file, and to update the employees DropDown by calling the LoadEmployees()
routine. Figure 9.11 shows the form loaded in a browser after another entry has been
added:
[ Figure 9.11 ]
The event handler for the “Add Task” button is slightly more complicated – it first
needs to locate the “employee” element that it must add the task under, and then it can
add the “task” element. Listing 9.12 shows the code:
Private Sub btnAddTask_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnAddTask.Click
Dim objXMLDoc As New XmlDocument()
objXMLDoc.Load(Server.MapPath("employees.xml"))
Dim objXMLNode As XmlNode
For Each objXMLNode In objXMLDoc.DocumentElement.ChildNodes
If objXMLNode.Attributes.ItemOf("name").Value =
lstEmployees.SelectedItem.Text Then
Dim objXMLTaskElement As XmlElement =
objXMLDoc.CreateElement("task")
Dim objXMLAttribute As XmlAttribute =
objXMLDoc.CreateAttribute("priority")
objXMLAttribute.Value = txtTaskPriority.Text
objXMLTaskElement.Attributes.Append(objXMLAttribute)
objXMLTaskElement.InnerText = txtTaskDescription.Text
objXMLNode.AppendChild(objXMLTaskElement)
Exit For
End If
Next
objXMLDoc.Save(Server.MapPath("employees.xml"))
LoadTasks()
End Sub
[ Listing 9.12 ]
The code again starts off by loading the XML file, and continues the same way as
the “Edit Task” code by creating a For loop to iterate through the element, and having
an If condition to see if the current node is the selected “employee” element. Inside
the If block is where the changes occur. Firstly, a new “task” XmlElement is created
using the CreateElement() method from the XmlDocument. Next, a new XmlAttribute
is created to represent the “priority” attribute of the “task” element. Its Value property
is set to the Text in the txtTaskPriority TextBox. The attribute is then added to the
element by using the Attributes.Append() method, again passing the XmlAttribute
object as a parameter. The “task” element stores the task priority in an attribute, but
the description is included between the “task” tags. To insert this text, the InnerText
property is assigned the value from the txtTaskDescription TextBox. Finally, the new
XmlElement (objXMLTaskElement) is added to objXMLNode, which is an
XmlElement representing the current node in the loop (i.e. the selected employee
node). The loop is exited, the file is saved and the tasks are reloaded. Figure 9.12
shows the page is a browser after a couple of tasks have been added to a new
employee (Patrick Collins):
[ Figure 9.12 ]
[ Listing 9.13 ]
As per normal, the code starts off by opening the XML and starts a loop iterating
through all the “employee” elements, searching for the selected one using the If
statement. When the If statement evaluates True and the loop has reached the selected
employee element, then the following line is executed:
objXMLDoc.DocumentElement.RemoveChild(objXMLNode)
All the “employee” elements are stored as direct children of the “employees”
document element (root element). The objXMLNode object is the XmlNode object
representing the current node, which is the selected “employee”. The XmlDocument’s
DocumentElement.RemoveChild() method is called, passing the objXMLNode
XmlNode object as a parameter, resulting in that node being removed from the
“employees” element, along with all the “employee” node’s children (“task” nodes).
The loops are exited, the file saved and the employees list reloaded.
Listing 9.14 shows the code for the btnDeleteTask event handler:
Private Sub btnDeleteTask_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnDeleteTask.Click
Dim objXMLDoc As New XmlDocument()
objXMLDoc.Load(Server.MapPath("employees.xml"))
Dim objXMLNode As XmlNode
For Each objXMLNode In objXMLDoc.DocumentElement.ChildNodes
If objXMLNode.Attributes.ItemOf("name").Value =
lstEmployees.SelectedItem.Text Then
Dim objXMLTaskNode As XmlNode
Dim strDescription = ExtractTaskDescription()
For Each objXMLTaskNode In objXMLNode.ChildNodes
If objXMLTaskNode.InnerText = strDescription Then
objXMLNode.RemoveChild(objXMLTaskNode)
Exit For
End If
Next
Exit For
End If
Next
objXMLDoc.Save(Server.MapPath("employees.xml"))
LoadTasks()
End Sub
[ Listing 9.14 ]
This event handler again follows the regular approach for dealing with “task”
nodes – it opens the XML file, then sets up a loop iterating through all the
“employee” elements. A second loop is set up to iterate through all the “task”
elements of the selected employee (which is found using the first If condition). In this
loop, the program is searching for the selected task’s element. The If statement
compares the current “task” node’s text (which contains the description) to the
description in the tasks ListBox. Inside this If code block, the current “employee”
element (represented by the objXMLNode object) call’s its RemoveChild() method,
and passes the current “task” element, objXMLTaskNode. The loops are then exited,
the file saved, and the tasks list reloaded.
In all these examples, the code for loading the XML document and finding specific
elements has been repeated to avoid unnecessary complexity when learning the
basics of the XmlDocument and XmlTextReader classes. However, in a real
application it would be advisable to reuse code, such as the loading and saving of
documents, as well as searching for elements by building custom functions that
return XmlElement objects, ready for manipulation.
Whether XML is loaded from a file using the Load() method, or LoadXml() is
used, the functionality of the XmlDocument object remains the same.
Obviously manipulating the XmlDocument is useless if it’s not possible to obtain
the final XML when you’re finished. For this purpose, the InnerXml property of the
XmlDocument class is available, which returns the XML as a string. This can then be
assigned to a string variable, like this:
Dim strXML As String = objXMLDoc.InnerXml
XSL Transformations
XSL (or eXtensible Stylesheet Language) often plays a crucial role in the display
of XML data. Chapter 12, “Using the Advanced User Controls” details the usage of
the Xml Web Control, which makes doing XSL transformations extremely simple.
That chapter also includes a brief primer on XSL, as well as information on using the
XslTransform class to perform transforms, along with the Xml Web Control.
Conclusion
XML is an extremely large, and extremely important topic. The .NET Framework
provides extensive support for XML, and a solid working knowledge of how to use
XML in ASP.NET is important. This chapter has provided a primer on XML to get
you to speed on the fundamentals, and guided you through the basics of manipulating
XML data using the XmlDocument class. This should certainly not be the end of your
learning – many XML and XML-related topics haven’t even been mentioned or have
been given very brisk overviews, such as XPath, XSL, XML schemas and
namespaces. ADO.NET provides very deep intrinsic support for XML, as do the
serialization classes, used for converting .NET to XML, and then back again. Most of
the .NET configuration files use XML (for example, Web.config), so using the .NET
XML parsing capabilities, it is possible to build tools to manipulate the configuration
files from an ASP.NET applications, which is potentially very powerful. With XML,
the opportunities for expansion and usage are very diverse, and hopefully this chapter
has prepared you to experiment with how XML can help you build better, more
elegant solutions to problems.
Chapter 10
File IO
In today’s world of development, there are many ways to store data and information,
ranging from advanced relational data systems such as SQL Server, to the hierarchical
data text-based XML format. However, sometimes when designing an application it
makes more sense to use one of the oldest methods of storing computer data – the file
system. The Windows filesystem provides a very easy-to-use method of storing data
in a hierarchical structure. Using the filesystem also provides several inherent
advantages from the point of view of resource access control and security because of
the Windows security model and NTFS.
The .NET Framework provides a comprehensive set of classes in the System.IO
namespace for manipulating files and folders in the filesystem. Files and folders can
easily be created, deleted, copied, moved and created. The relevant classes also
encapsulate intrinsic NTFS permissions for both files and folders, along with file and
folder attributes, and other useful information.
Obviously such code could appear in the Page_Load event, or any event caused by
a user’s action, such as the clicking of a button. This code could even be executed in
the Global.asax file in an event handler, such as the session’s Start event. When
combined with other File class methods, the Move method could be used for creating
a powerful web storage administration front end. However, the important aspect that
needs to be acknowledged is that the second parameter specifies a file path, not just a
directory, as might be expected. If a directory is specified instead of a full file path, an
exception will occur. Another important point to consider is that the Move method
does not automatically create the specified file path except for the actual file, and if a
file path that does not exist is entered, an exception will also be raised. For example,
if the second parameter is “c:\testdir\test.txt”, and c:\testdir does not exist, the method
will fail.
The Directory class’s Move method operates similarly to the File.Move() method.
It too accepts 2 parameters, although in this case the parameters specify the folder to
be moved, and where the folder must be moved to respectively. As with the
File.Move() method, the 2nd parameter must specify the full path that should be
created, not just the path to the folder that must be the parent of the moved folder. The
following code will move the folder c:\testdir to c:\windows\testdir.
System.IO.Directory("c:\testdir", "c:\windows\testdir")
Again, as with the File.Move() method, the path specified in the 2nd parameter
must already exist, and the method will not automatically create the path specified.
When a directory is copied, all its child files and folders are maintained in their
current structural form and copied along with the folder, maintaining their positions in
the folder’s structure.
The File.Copy() method operates almost identically to the File.Move() method,
except that it does not move the file in question, but rather creates a copy of it. It too
requires a full existing file path to be entered as its second parameter. The following
would copy the file c:\test.txt to the c:\windows directory:
System.IO.File.Copy("c:\test.txt", "c:\windows\test.txt")
The code could also easily be modified to check if the file c:\test.txt exists:
If System.IO.File.Exists("c:\test.txt") = True Then
'Code to execute
End If
Creating Folders
The Directory class exposes the CreateDirectory() method for creating new
folders. The method accepts one string parameter specifying the path of the directory
to be created. An instance of the DirectoryInfo() class is returned, which includes
details about the newly created directory.
The CreateDirectory() method allows multiple levels of directories to be created
using one call i.e. the immediate parent of the created directory does not need to exist
– the method will create the entire path automatically. For example, if you call the
method to create c:\testdir\anotherdir, if c:\testdir doesn’t already exist it will be
created automatically. If the method is called to create a directory that already exists,
the DirectoryInfo class that is returned simply returns the values for the specified
directory, and no exception is raised.
To simply create the directory c:\testdir, the following call would be used:
System.IO.Directory.CreateDirectory("c:\testdir")
This could be used in conjunction with the Move() or Copy() methods to ensure
that a path exists:
If System.IO.Directory.Exists("c:\testdir") = False Then
System.IO.Directory.CreateDirectory("c:\testdir")
End If
System.IO.File.Move("c:\test.txt", "c:\testdir\test.txt")
This code ensures that the c:\testdir directory exists before moving the file
c:\test.txt to it. However, because the CreateDirectory() method does not raise an
exception if the specified folder already exists, it would also be possible to rewrite the
above code example as follows:
System.IO.Directory.CreateDirectory("c:\testdir")
System.IO.File.Move("c:\test.txt", "c:\testdir\test.txt")
The DirectoryInfo class that is returned provides properties and methods for
handling the newly created directory. The section “Folder and File Information” deals
specifically with manipulating the DirectoryInfo class, but the following code will
store an instance of the DirectoryInfo class for the newly-created folder c:\testdir:
Dim objDirInfo As System.IO.DirectoryInfo =
System.IO.Directory.CreateDirectory("c:\testdir")
Likewise the following code could be used to create a new DirectoryInfo object
referring to the folder c:\testdir:
Dim objDirInfo As New System.IO.DirectoryInfo("c:\testdir")
Creation Time
To access the creation time of a file or folder using the File and Directory classes,
the GetCreationTime() method should be used. This method takes one string
parameter with the path of the file or folder, and returns a DateTime value.
The following code would output the creation time of the file c:\test.txt to a label:
Label1.Text = System.IO.File.GetCreationTime("c:\test.txt").ToString("g")
This code places the last accessed time of the file c:\test.txt into a label and sets
the last modified time of the c:\testdir folder to the current time.
Again, as with the creation time functionality, the FileInfo and DirectoryInfo
classes provide properties for accessing and modifying the last accessed and last
modified values of files and folders. The LastAccessTime and LastWriteTime
properties are of type DateTime, and are used in the same way as with the
CreationTime property:
Dim objDirInfo As New System.IO.DirectoryInfo("c:\testdir")
Label1.Text = objDirInfo.LastAccessTime.ToString("g")
objDirInfo.LastWriteTime = DateTime.Now
Since a regular array is returned, a For Each loop could also be used to iterate
through the files:
Dim strFiles As String() = System.IO.Directory.GetFiles("c:\testdir")
Dim strFile As String
For Each strFile In strFiles
lstFiles.Items.Add(strFile)
Next
The GetDirectories() method could be used fairly similarly to fill the list
lstFolders with the list of subdirectories in c:\testdir:
Dim strDirs As String() = System.IO.Directory.GetDirectories("c:\testdir")
Dim strDir As String
For Each strDir In strDirs
lstDirs.Items.Add(strDir)
Next
All three of these methods provide a second overloaded version that allows 2
string parameters to be passed – the first still the path of the folder, but the second
specifies criteria for limiting the items returned in the array based on file or folder
name. The criteria, or filter, that is passed uses the same format as the command line
in Windows – ‘*.*’ means all files; ‘*.aspx*’ means all ASP.NET UI and codebehind
files; ‘m*’ means all files beginning with ‘m’, and so forth.
The following code populates a listbox with ASP.NET UI files in the c:\testdir
directory:
Dim strFiles As String() = System.IO.Directory.GetFiles("c:\testdir",
"*.aspx")
Dim strFile As String
For Each strFile In strFiles
lstFiles.Items.Add(strFile)
Next
As has been mentioned, this second parameter can be used with the
GetDirectories() and GetFileSystemEntries() methods as well.
The DirectoryInfo class’s implementation of the GetFiles(), GetDirectories() and
GetFileSystemInfos() methods differ from those of the equivalent methods in the
Directory class, but can also easily be used to populate a listbox. The following code
fills a listbox with the names of the files within the c:\testdir folder using the
DirectoryInfo class:
Dim objDirInfo As New System.IO.DirectoryInfo("c:\testdir")
Dim objFile As System.IO.FileInfo
For Each objFile In objDirInfo.GetFiles()
lstFolders.Items.Add(objFile.FullName)
Next
This implementation lists the files including their full paths. The FileInfo class
provides the Name property, as opposed to the FullName property, which returns only
the file’s name and not its path (so Name will return ‘test.txt’ rather than
‘c:\testdir\test.txt’). The GetFiles() method has a second overloaded version which
accepts a string parameter containing a filter for the listed files, so it is possible to call
the method and have it return only .aspx files etc. The following code fills an array of
FileInfo objects with all the ASP.NET UI files in a directory:
Dim objFileInfo As System.IO.FileInfo() = objDirInfo.GetFiles("*.aspx")
To alleviate the problem of the Directory class not exposing a method for
obtaining the size of a directory, it is possible to create your own function that does so
using the FileInfo class. The following subroutine is a recursive function that allows
the folder path to be specified, along with a variable to store the total size of the folder
in:
Private Sub GetFolderSize(ByVal folder As String, ByRef size As Integer)
Dim strDirs As String() = System.IO.Directory.GetDirectories(folder)
Dim i As Integer
For i = 0 To strDirs.Length - 1
GetFolderSize(strDirs(i), size)
Next
Dim strFiles As String() = System.IO.Directory.GetFiles(folder)
For i = 0 To strFiles.Length - 1
size += New System.IO.FileInfo(strFiles(i)).Length
Next
End Sub
This subroutine would be called as follows to obtain the total size of the c:\testdir
directory:
Dim size As Integer
GetFolderSize("c:\testdir", size)
The variable ‘size’ would contain the total size of the c:\testdir folder in bytes
after the code is executed. The GetFolderSize() sub calls itself for every folder within
the specified “root” folder, allowing the sub to accurately return the size of any
folder’s contents, no matter how many directory levels it has. For every file in any
particular folder, the ‘size’ variable is incremented by the size of the file.
Attributes
Every file or folder in the Windows filesystem has attributes associated with it.
These attributes may store information such as whether a file or folder is read only,
hidden, compressed or encrypted, amongst others. The File, FileInfo and
DirectoryInfo classes allow you to access and manipulate this information. The File
class provides the GetAttributes() method which returns a value from the
System.IO.FileAttributes enumeration. This is a bit flag enumeration, meaning that
each value in the enumeration is specifically assigned an Integer value so that more
than one value can be selected (because a file can be marked as both hidden and
archive, for example). The File class’s SetAttributes() method allows for attributes to
be set for a particular file. The FileInfo and DirectoryInfo classes provide an
“Attributes” property, which allows read and write access to the associated
FileAttributes enumeration.
Since the FileAttributes enumeration is bit flagged, it is slightly more involved to
deal with when requesting specific items, but to output a basic list of the attributes set
for a specific file to a label is fairly simple:
Label1.Text = System.IO.File.GetAttributes("c:\test.txt").ToString()
This will return a comma-separated string list of the attributes applicable to the
file c:\test.txt. The possible options from the FileAttributes enumeration are listed in
Table 10.1.
Member: Description:
Archive Is the file marked for archival purposes?
Compressed Is the file compressed?
Device
Directory Is the file a directory?
Encrypted Is the file encrypted?
Hidden Is the file hidden?
Normal Set if no other attributes apply
NotContentIndexed Is the file marked as not available for indexing?
Offline Is the file offline?
Readonly Is the file readonly?
ReparsePoint Does the file contain a reparse point?
SparseFile Is the file sparse (typically large; mainly zeros)
System Is the file marked as a system file?
Temporary Is the file a temporary file?
[ Table 10.1 – System.IO.FileAttributes enumeration members ]
To receive a boolean of whether one of the enumeration members applies to a
specific file (such as ‘Is a file hidden?’), the following code can be used:
Dim objFileAttributes As System.IO.FileAttributes =
System.IO.File.GetAttributes("c:\test.txt")
Dim bHidden As Boolean = ((objFileAttributes And
System.IO.FileAttributes.Hidden) <> 0)
The boolean bHidden will contain a value of True if the file c:\test.txt is hidden,
and False is it isn’t. This method can obviously be applied to any value in the
FileAttributes enumeration.
To set the attributes of a file using the File’s SetAttributes() method a string
containing the path of the file and values from the FileAttributes enumeration must be
passed. The following will mark the file c:\test.txt as read-only:
System.IO.File.SetAttributes("c:\test.txt",
System.IO.FileAttributes.ReadOnly)
Calling SetAttributes() will automatically remove all attributes from the file and
apply only the attributes specified. In the above case, the file c:\test.txt will only be
flagged as read-only, regardless of what other flags it had previously. To specify
numerous attributes, the bitwise Or operator should separate the specific values.
Below shows code to set the attributes of the c:\test.txt to both hidden and read-only:
System.IO.File.SetAttributes("c:\test.txt",
System.IO.FileAttributes.ReadOnly Or System.IO.FileAttributes.Hidden)
To avoid all the previous attributes of a file being reset it is possible to specify that
you want to apply the current attributes and set another one:
System.IO.File.SetAttributes("c:\test.txt",
System.IO.File.GetAttributes("c:\test.txt") Or
System.IO.FileAttributes.ReadOnly)
This will ensure that the file c:\test.txt is read-only, whilst maintaining its current
attributes. The Xor operator can be used to toggle whether a specific attribute is
applied or not. For example, the following code will set the file c:\test.txt to read-only
if it isn’t read-only, but will set it to be non-read-only if it is read-only:
System.IO.File.SetAttributes("c:\test.txt",
System.IO.File.GetAttributes("c:\test.txt") Xor
System.IO.FileAttributes.ReadOnly)
The FileInfo and DirectoryInfo classes provide the Attributes property for
accessing and setting attributes. All the methods of accessing and setting attributes for
the GetAttributes() and SetAttributes() methods of the File class still apply, so to
obtain a boolean of whether a specific attribute is applied to a file or directory is done
as follows:
Dim objFileInfo As New System.IO.FileInfo("c:\test.txt")
Dim bHidden As Boolean = ((objFileInfo.Attributes And
System.IO.FileAttributes.Hidden) <> 0)
The above code will hide the c:\testdir folder. Again, as with the SetAttributes()
method, it is possible to set multiple attributes by separating each value with an Or
operator:
Dim objDirInfo As New System.IO.DirectoryInfo("c:\testdir")
objDirInfo.Attributes = System.IO.FileAttributes.Hidden Or
System.IO.FileAttributes.ReadOnly
Maintaining the original attributes whilst applying new ones is also possible in the
same fashion as with the SetAttributes() method:
Dim objDirInfo As New System.IO.DirectoryInfo("c:\testdir")
objDirInfo.Attributes = objDirInfo.Attributes Or
System.IO.FileAttributes.ReadOnly
The above code maintains the original attributes of the c:\testdir folder, but also
sets it to be read-only.
The Peek() method reads the next character in the file, and returns -1 if it is the
end of the file. The While loop ensures that all the lines in the file are read. It is also
possible to read through the file, character by character using a similar technique:
Dim objSR As System.IO.StreamReader =
System.IO.File.OpenText("c:\test.txt")
Dim strFile As String
While objSR.Peek > -1
strFile = strFile + Chr(objSR.Read())
End While
objSR.Close()
The Read() method returns the next character in the file as an Integer, so the Chr()
function is used to convert the integer value to a character.
Writing To Files
The FileStream and StreamWriter classes are used when writing to text files. An
instance of the FileStream class is required before a StreamWriter object can be
created. The FileStream class can be created using its constructor, or by calling the
System.IO.File.Open() method. Both methods require the name and path of the file
and the file mode as a minimum to be specified. The file mode is a value from the
System.IO.FileMode enumeration, listed in table 10.2.
Member Description
Append Appends any data written to the end of the file, or creates a new file if the
file does not already exist.
Create Creates a new file. If the file already exists, it is overwritten.
CreateNew Creates a new file. If the file already exists, an exception is thrown.
Open Opens a file.
OpenOrCreate Opens a file, or creates a new file if the file does not already exist.
Truncate Opens a file and immediately erases its content.
[ Table 10.2 – System.IO.FileMode Enumeration Members ]
The System.IO.File.OpenWrite() method also provides a shortcut for creating a
FileStream that doesn’t require a FileMode to be entered.
Before delving into actually writing to a file, let’s first introduce the several
methods of creating a StreamWriter class. The simplest is using the OpenWrite()
method from the File class:
Dim objFS As System.IO.FileStream =
System.IO.File.OpenWrite("c:\test.txt")
Dim objSW As New System.IO.StreamWriter(objFS)
This code will create a FileStream object that can both be read and written to, and
the specified file will be created if it doesn’t already exist. The OpenWrite() method
accepts that name of the file to open as a string and returns a FileStream object.
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.OpenOrCreate)
Dim objSW As New System.IO.StreamWriter(objFS)
The File class’s Open() method has numerous overloaded versions, this being the
simplest of them – the filename is passed as a string, and the mode to open the file in
is specified. This is one of the values from the FileMode enumeration, and in this case
is OpenOrCreate. Again, the method returns a FileStream object.
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.Append, System.IO.FileAccess.Write)
Dim objSW As New System.IO.StreamWriter(objFS)
This overloaded version of Open() accepts three parameters – the name of the file,
the FileMode and a member from the FileAccess enumeration. The FileAccess
enumeration has three members: Read, ReadWrite and Write. This specifies what
operations can be performed on the resulting FileStream. The final overloaded version
allows these three, and a FileShare enumeration member to be specified:
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.Append, System.IO.FileAccess.Write,
System.IO.FileShare.None)
Dim objSW As New System.IO.StreamWriter(objFS)
The FileShare enumeration has four members: None, Read, ReadWrite and Write.
This specifies what level of control other streams have on the specified file after is has
been opened. For example, if the FileShare.Read member is set, then other subsequent
attempts to open and read the file whilst the original FileStream is still open will be
allowed, but any attempts to write to the file by this second FileStream will fail.
The FileStream class can also be instantiated by itself, and does not have to be
created by the File class’s Open() method. Its constructor has numerous overloaded
versions, of which the simplest can be used as follows:
Dim objFS As New System.IO.FileStream("c:\test.txt",
System.IO.FileMode.OpenCreate)
Dim objSW As New System.IO.StreamWriter(objFS)
The constructor allows the file to be passed as a string and the mode to open the
file in as a member for the FileMode enumeration. Another version can be used to
specify the FileAccess level as well:
Dim objFS As New System.IO.FileStream("c:\test.txt",
System.IO.FileMode.OpenCreate, System.IO.FileAccess.Write)
Dim objSW As New System.IO.StreamWriter(objFS)
In this case it will only be possible to write to the file, and not read from it.
The last version introduced here allows a FileShare value to be specified as well:
Dim objFS As New System.IO.FileStream("c:\test.txt",
System.IO.FileMode.OpenCreate, System.IO.FileAccess.Write,
System.IO.FileShare.Read)
Dim objSW As New System.IO.StreamWriter(objFS)
Please take note that only a few versions of the FileShare constructor have been
shown, and that the FileShare class contains much for functionality than this
introduction to it demonstrates, so be sure to take a look at the .NET Framework
documentation if you’d like to find out about some of the more advanced features
of the FileShare class.
Once a StreamWriter object has been created, writing is a very simple affair. The
two methods shown here involve writing to the file without automatically inserting
line feeds, or writing to the file, one line at a time, where the CRLFs are inserted
automatically. Both the Write() and WriteLine() methods of the StreamWriter feature
numerous overloaded versions for writing most primitive data types to the file – from
single characters (Char), to arrays of Chars, to strings, to Integers.
The following example opens a file, or creates new one if it doesn’t already exist,
and writes in the line “Hello World!” before closing it. The original content of the file
is erased.
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.OpenOrCreate)
Dim objSW As New System.IO.StreamWriter(objFS)
objSW.WriteLine("Hello World!")
objSW.Close()
objFS.Close()
The same result could be achieved using the Write() method, although the CRLF
has to be inserted manually:
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.OpenOrCreate)
Dim objSW As New System.IO.StreamWriter(objFS)
objSW.Write("Hello World!" & vbCrLf)
objSW.Close()
objFS.Close()
The following example will add a line to the file, without reading the previous
contents and rewriting it, by using the Append FileMode:
Dim objFS As System.IO.FileStream = System.IO.File.Open("c:\test.txt",
System.IO.FileMode.Append, System.IO.FileAccess.Write)
Dim objSW As New System.IO.StreamWriter(objFS)
objSW.WriteLine("This is another line.")
objSW.Close()
objFS.Close()
Take note that in this example the FileAccess.Write member was explicitly
specified when creating the FileStream. This is because the Append FileMode will
only work when the file is opened is such a fashion. If it is not, an exception is raised.
Conclusion
This chapter has given a brief introduction to some of the functionality offered by
the System.IO namespace, which provides a very powerful framework for
manipulating most aspects of the Windows filesystem. The filesystem can be a very
useful method of storing data in certain scenarios, despite the power of modern
RDBMS systems, and the System.IO namespace exposes numerous classes for
making the task of manipulating directories, files and their contents fairly simple.
Chapter 11
All the parameters are Strings. However, as I’ve mentioned earlier, to test this out,
IIS’s SMTP server needs to be running on the same machine as the application. To
check this, and rectify it if possible, perform the following:
1. Click Start-Settings-Control Panel
2. Open “Administrative Tools”
3. Open “Internet Services Manager”
4. The MMC should show the following:
then you need to start your SMTP server. Do this by right-clicking the
node and clicking “Start”. If an error occurs and the server does not start,
the most probable cause is that you already have another application
bound to port 25 (the port that SMTP servers run on by default). If this is
the case, shut down your other server application, and try start IIS’s
SMTP server again.
Depending on the situation, the SMTP server may require further configuration. If
you are already on a network and know that your SMTP service is functioning
correctly, then you may skip these steps. However, if you are either on a network that
uses another machine as the SMTP server, or are not on a network at all, then you will
need to configure your server to send all mail through another SMTP server,
otherwise your machine will likely not be able to send mail to users on other systems
and networks. In this case, perform the following steps:
1. Click Start-Settings-Control Panel
2. Open “Administrative Tools”
3. Open “Internet Services Manager”
4. Right-click the “SMTP Virtual Server” node and choose
“Properties”.
5. Click on the “Delivery” tab, then on the “Advanced” button. A dialog
similar to Figure 11.4 should appear.
End Sub
#End Region
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
End Sub
[ Listing 11.1 ]
Only one line is used to actually send the email in this listing. It calls the shared
Send method of the SmtpMail class, and passes 4 string parameters, each the contents
of the appropriate TextBox web controls. Once the method has been called, the table
containing the fields is hidden, and a message informing the user that the form has
been sent is displayed. It would also be equally easy to omit a field, and have it filled
in automatically in the code. The following line will send the message, but a “From”
address set to “peter@mydomain.com” is automatically inserted, thus the txtFrom
control can be deleted:
System.Web.Mail.SmtpMail.Send("peter@mydomain.com", txtTo.Text,
txtSubject.Text, txtMessage.Text)
It is possible to modify which SMTP server messages are sent via using the shared
SmtpServer property of the SmtpMail class. This is a string property, which accepts
the name of the machine that must be used as the SMTP Server. The local machine
is assumed unless the property is explicitly set, which is why the above example
does not set it.
End Sub
#End Region
[ Listing 11.2 ]
The CC and BCC are simply two additional text fields, but the Priority is
implemented as a DropDownList web control in the user interface. The list has three
options, “High”, “Low” and “Normal”, with the values “2”, “1” and “0” respectively.
The first line in the btnSend_Click event handler instantiates a new MailMessage
object. The following 7 lines proceed to set the properties of the object with the
appropriate values. The Priority property expects a value from the Mail.MailPriority
enumeration, but it will accept Integer values (but not strings) that represent the
values in the enumeration. It is also important to note that for properties that accept
email addresses, such as To, CC and BCC, multiple addresses may be given by
separating them with semi-colons. The line
“System.Web.Mail.SmtpMail.Send(objMessage)” sends the message by passing the
MailMessage object that was created to the Send method.
Sending Attachments
Sending attachments is achieved through the use of the MailAttachment class, in
conjunction with the Attachments property of the MailMessage class, which is of type
IList, the class from which the likes of the Array class, and numerous collection
classes for web controls are derived. The Add method of the IList class allows
instantiated MailAttachment classes to be added to the list of attachments for a
particular message. The MailAttachment class contains properties for the filename of
the file to be attached on the server, and the encoding of the file. The constructor for
this class allows either the filename (of type string) or the filename and the
appropriate encoding to be set when the class is instantiated. Listing 11.4 shows the
code to attach a file on the server to a message and send it.
Dim objMessage As New System.Web.Mail.MailMessage()
Dim objAttachment As New
System.Web.Mail.MailAttachment("c:\file_to_attach.txt")
objMessage.Attachments.Add(objAttachment)
objMessage.To = "peter@mydomain.com"
objMessage.From = "peter@mydomain.com"
objMessage.Subject = "This message has an attachment!"
objMessage.Body = "This message has an attachment."
System.Web.Mail.SmtpMail.Send(objMessage)
[ Listing 11.4 ]
Multiple Attachments
In order for multiple attachments to be sent, multiple MailAttachment classes
must be created. In this case, it may be useful to create an array of MailAttachments,
and then loop through the array, adding the classes to the Attachments IList of the
MailMessage class. Listing 11.5 shows the code to add 3 attachments using this
method.
Dim objMessage As New System.Web.Mail.MailMessage()
Dim objAttachments(2) As System.Web.Mail.MailAttachment
Dim i As Integer
objAttachments(0) = New
System.Web.Mail.MailAttachment("c:\attachment1.txt")
objAttachments(1) = New
System.Web.Mail.MailAttachment("c:\attachment2.txt")
objAttachments(2) = New
System.Web.Mail.MailAttachment("c:\attachment3.txt")
For i = 0 To UBound(objAttachments)
objMessage.Attachments.Add(objAttachments(i))
Next
objMessage.To = "peter@mydomain.com"
objMessage.From = "peter@mydomain.com"
objMessage.Subject = "This message has several attachments!"
objMessage.Body = "This message has several attachments."
System.Web.Mail.SmtpMail.Send(objMessage)
[ Listing 11.5 ]
Since array subscripts always begin at zero, dimensioning objAttachments(2)
actually creates 3 elements. This line creates an array, from 0 to 2, of type
MailAttachment. However, although the elements are of type MailAttachment they do
not as yet contain instances of MailAttachment classes, which explains the need for
the respective New statements in the following lines. The loop is from zero to the
upper bound of the array. This effectively loops through all the elements in the given
array, adding each element (which is a MailAttachment object) to the Attachments
IList. The rest of the MailMessage is set up, and sent as per normal.
When performing HTTP file uploads, it is important that the form’s enctype
property is set to “multipart/form-data”. If this is not done, the form upload will not
execute correctly.
[ Figure 11.6 – Web Form with HTML File Upload control ]
The click event handler of the Send button will first save the uploaded file to the
server’s hard drive. It will then create a MailAttachment class with the file, and
proceed to continue with the MailMessage class normally. Listing 11.6 shows the
entire source for the codebehind for this web form.
Public Class SimpleEmail
Inherits System.Web.UI.Page
Protected WithEvents tblSimpleEmail As
System.Web.UI.HtmlControls.HtmlTable
Protected WithEvents lblSend As System.Web.UI.WebControls.Label
Protected WithEvents txtTo As System.Web.UI.WebControls.TextBox
Protected WithEvents txtFrom As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSubject As System.Web.UI.WebControls.TextBox
Protected WithEvents txtMessage As
System.Web.UI.WebControls.TextBox
Protected WithEvents filAttachment As
System.Web.UI.HtmlControls.HtmlInputFile
Protected WithEvents btnSend As System.Web.UI.WebControls.Button
End Sub
#End Region
[ Listing 11.6 ]
The call of the HttpPostedFile class’s SaveAs method requires particular attention.
The FileName property of the HtmlPostedFile class (the type of the PostedFile
property of the HtmlInputFile class) contains the full physical path of the uploaded
file on the client. Thus, if the client uploaded the file from “C:\My
Documents\Messages\Attachment.doc”, the value of this FileName property would be
that entire path. It is obviously unsuitable to have the entire path used when storing
the file on the server, in which case the server would have files liberally scattered
throughout its hard drive. However, it would be desirable to keep the original name of
the file for descriptive purposes when attaching it to the message. The GetFileName
function receives a full physical path as a string parameter and returns only the
filename and extension. It does this by searching for the last backslash, and copying
the remainder of the full path after it. Thus, the SaveAs call saves the uploaded file to
“C:\Temp\<filename>.<ext>”. In the following line, where the MailAttachment is
created, this path is then used again. The rest of the code remains the same as for a
normal mail message with a normal attachment.
Mass Mailing
There are two primary methods of mass mailing, each with its respective
advantages and disadvantages. The first is sending one message, with all the
recipients in the blind carbon copy field. This message has the advantage of only
requiring one message to be sent (through your server, at least), thus reducing
bandwidth wastage, but carries the disadvantage that the message cannot be
customised for each recipient. The second method is to create a new mail message for
each recipient. This has the disadvantage of generally creating more work for your
SMTP server, but has the advantage of being able to tailor the message for each
recipient.
[ Listing 11.7 ]
The string array strRecipients is used to hold the email addresses of 5 recipients.
Although the addresses are hard-coded in this example, they could just have easily
been retrieved from a mailing database. The BCC property of the MailMessage class
is set to a semi-colon separated list of email addresses. The Join function takes an
array as its first parameter, and a string delimiter as its second. This function returns
one string that contains each element of the array, separated by the specified
delimiter, which is in this case a semi-colon.
Individual Message Mass Mailing
Listing 11.8 shows an alternative method of mass mailing that sends an individual
message to each recipient by looping the creation and sending of multiple messages.
Dim objMessage As System.Web.Mail.MailMessage
Dim strRecipients(4) As String
Dim i As Integer
strRecipients(0) = "peter@mcmahon.com"
strRecipients(1) = "leon@cilliers.com"
strRecipients(2) = "leonard@fienberg.com"
strRecipients(3) = "lionel@guimaraes.com"
strRecipients(4) = "craig@macintosh.com"
For i = 0 To UBound(strRecipients)
objMessage = New System.Web.Mail.MailMessage()
objMessage.To = strRecipients(i)
objMessage.From = "webmaster@mysite.com"
objMessage.Subject = "This is a mass mailing!"
objMessage.Body = "Hi " + strRecipients(i) + ", this message was generated
especially for you!"
System.Web.Mail.SmtpMail.Send(objMessage)
Next
Conclusion
This chapter has demonstrated some of the most important features of the
System.Web.Mail namespace in ASP.NET, which allows you to perform most
mailing operations simply and easily. The chapter covered setting up the SMTP server
is IIS, sending an email in one line only, HTML messages, attachments, user upload
attachments in addition to 2 commonly used techniques for mass mailing.
Chapter 13
Getting Started
There are essentially two methods of creating user controls – one where the
control’s user interface is specified in an .ascx file, with its logic implemented in a
code-behind file, and the second where a class manually creates the control through
code. This chapter will discuss and delve into the advantages and disadvantages of
both methods, but the former is certainly the simplest, and the one that the VS.NET
environment offers support for.
To begin with, create a new web project. Once the project has been created, right
click on the project node in the Solution Explorer and select Add-Add Web User
Control, as shown in Figure 13.1, to add a new user control, “Footer.ascx”.
[ Figure 13.1 – Adding a new user control ]
Once this has been done, a new page should appear in the IDE. This will look
almost identical to a new web form, except the filename in the tabs will indicate that
the extension is .ascx, and not .aspx, denoting the file as a web user control. This
control will simply be used to append the current date to pages, so add a label to the
“page”, just as you would for a web form and name it, “lblDate”. Switch to the “Code
View”, either by right-clicking the “page” and choosing the appropriate option from
the context menu, or by doing so in the Solution Explorer, as you would for a normal
web form. Listing 13.1 shows what the code should look like.
Public MustInherit Class Footer
Inherits System.Web.UI.UserControl
Protected WithEvents lblDate As System.Web.UI.WebControls.Label
End Sub
#End Region
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
End Sub
End Class
[ Listing 13.1 ]
First up, it’s clear that the control is implemented as a class that inherits from the
System.Web.UI.UserControl class. This class provides the basic functionality for a
user control. As with ASP.NET pages, the next section in the code-behind file is the
list of user-added members. In this case, the only control that has been added is the
label. The two subs in the “Web Form Designer Generated Code” region deal with
initializing components that may have been added to the form at design time. Finally,
the Page_Load event handler is available as a place to put custom initialization code.
Put the following line into this sub:
lblDate.Text = Now.ToLongDateString()
This will set the Text property of the label to the current date in the long date
format when the user control is loaded.
[ Figure 13.2 – A blank web form with the footer control added ]
When this page is viewed in the browser, the code in the user control will be
executed, resulting in the date being displayed, as shown in figure 13.3.
[ Figure 13.3 ]
User Controls can, however, consist of complex arrangements of web controls, not
just a single label as with the above example. It is also possible to dynamically add
web controls to a user control at runtime, so that user controls can be used for
displaying data-driven menus, for example. However, no matter how complex or
simple a user control is, using it in a web form is always as simple as dragging and
dropping the control from the Solution Explorer.
When a user control is added to a form, several lines of code are added to the user
interface file. These lines tell the ASP.NET compiler where it must load the control
from (i.e. either the location and name of the .ascx file, or the name of the control’s
assembly and namespace), and then where the control must be inserted in the page.
Inspecting the HTML of the web form in the above example will reveal the code in
listing 13.2.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="UserControls.aspx.vb"
Inherits="UserControls.UserControls"%>
<%@ Register TagPrefix="uc1" TagName="Footer" Src="Footer.ascx" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title>UserControls</title>
<meta content="Microsoft Visual Studio.NET 7.0"
name=GENERATOR>
<meta content="Visual Basic 7.0" name=CODE_LANGUAGE>
<meta content=JavaScript name=vs_defaultClientScript>
<meta content=http://schemas.microsoft.com/intellisense/ie5
name=vs_targetSchema>
</HEAD>
<body>
<form id=Form1 method=post runat="server">
<uc1:Footer id=Footer1 runat="server"></uc1:Footer>
</form>
</body>
</HTML>
[ Listing 13.2 ]
The @ Register directive is used to define the location of a user control that can
be used in a page, as well as the tags that are used to identify the instances of the
control. It has two forms – one that allows the user control filename to be specified,
and another allowing the assembly and namespace to be specified. Visual Studio
inserts the former:
<%@ Register TagPrefix="uc1" TagName="Footer" Src="Footer.ascx" %>
The TagPrefix and TagName attributes specify what the tags that will be used to
insert the control will look like. When inserting a regular Web Control, the “tag
prefix” is always “asp”, and the “tag name” depends on the control – for example,
“TextBox”. The Src attribute specifies the user control file.
This directive only defines how “Footer” controls can be added, but does not
actually add any itself. This is done using tags similar to those for Web Controls, but
using the parameters specified in the @ Register directive. In this case, the tag for
inserting the “Footer” control is <uc1:Footer>, because the tag prefix is “uc1” and the
tag name is “Footer”. As with all Web Controls, user controls must also include a
runat attribute, and an id attribute is normally given so that the control can be
programmed against. The final tag to insert the control in the example is:
<uc1:Footer id=Footer1 runat="server"></uc1:Footer>
The generic tag prefixes that Visual Studio creates are not very descriptive, so it
may be useful to change them. To do so, simply edit the TagPrefix attribute of the @
Register directive, and suitable modify the tags of all the user controls on the form.
[ Listing 13.3 ]
The top row’s cell has a dark blue background color, and contains large white text
with the name of the site, “XYZ Inc.” The second row’s cell does not have a
background color specified, as this will be set by a user control property. There is no
text in this cell yet, but a Label Web Control is present, which will later contain the
section name.
The control’s server-side code is where the property definitions occur. For this
control, two private variables will be used to store the row color and the section name
internally. These will be declared in the user control class as follows:
Private colBarColor As Color
Private strSection As String
The properties will have a public scope, as they must be available to the web form
class. Both will simply set their respective private variable when they are assigned a
value, and return the private variable value when requested, so the definitions for the
two properties are:
Public Property BarColor() As Color
Get
Return colBarColor
End Get
Set(ByVal Value As Color)
colBarColor = Value
End Set
End Property
Since the property get and set accessors are doing nothing other than assigning and
returning the value of a private variable, it would be possible to expose public
variables as “properties” of the user control instead of real properties in this
instance, so both Property blocks and the private variables could be scrapped, and
replaced by two public variables, BarColor and Section, of type Color and String
respectively.
All public properties (and variables) can be set as properties in the user control’s
tag on the web form using the name of the property as the attribute in the tag. For this
header control, setting the BarColor and Section properties could be done as follows:
<XYZInc:Header BarColor="propertyvalue" Section="propertyvalue"
runat="server">
It is important to keep this in mind when choosing the names of properties, so that
the user does not have to specify attributes with names such as “strSection”.
The UserControl class exposes a Render() method that is called when the control
must be displayed. In order to use the BarColor and Section properties that have been
specified by the user, this method should be overridden, so that the appropriate
properties of the table and label can be set before they are rendered:
Protected Overrides Sub Render(ByVal output As HtmlTextWriter)
tblHeader.Rows(1).Cells(0).BgColor =
ColorTranslator.ToHtml(BarColor)
lblSection.Text = Section
RenderChildren(output)
End Sub
Since the BgColor property of the HtmlTableCell class accepts a String, the
BarColor property (which is of type System.Drawing.Color) must first be converted.
The ColorTranslator’s ToHtml method accepts a Color value and returns the HTML
equivalent. The Section property, which is a String, is directly assigned to the label’s
Text property, without any manipulation. Finally the RenderChildren() method is
called, which writes the actual Html for the controls contained in the user control.
To use the control, it must first be added to a web form. The easiest method of
doing this is by dragging-and-dropping the .ascx file from the Solution Explorer onto
the form. Once this has been done, switch to the HTML view and location the User
Control tag. It will look something like this:
<uc1:Header id="Header1" runat="server"></uc1:Header>
The control expects two properties to be specified – BarColor and Section. Both
are specified using attributes of the user control tag, as follows:
<uc1:Header id="Header1" BarColor="LightBlue" Section="Home"
runat="server"></uc1:Header>
When designing user controls, it is important to provide for situations where the
user does not specify values for properties. This is normally best done assigning
default values for all the properties using initializers on the private variables storing
their values, so that even if the user does not specify a value for any given property,
the control will still function correctly.
Once these attributes have been assigned values, the rest of the page can be built.
Figure 13.4 shows a web form loaded in the browser using the header control.
[ Figure 13.4 ]
The ArrayList class allows a list of Objects to be stored, and accessed through an
Items property, using an index. The intSelectedItem variable is initialized to –1 so that
by default no items are selected. The default colors for selected and unselected items
are gray and white respectively.
The following are the actual property definitions:
Public ReadOnly Property Item(ByVal index As Integer) As String
Get
Return CStr(Items.Item(index))
End Get
End Property
The Item() property is read-only, so only the Get accessor is included. The
property is of type String, as even though the ArrayList can store objects of all types,
only Strings will be stored in this user control, thus the value is cast as a string using
CStr() before being returned. The Count property is simply a wrapper for the
ArrayList’s Count property, since the user doesn’t have direct access to the Items
object. The SelectedItem Set accessor uses an If condition to ensure that the
SelectedItem is never larger than the total number of items in the list (although it can
be negative, indicating that nothing is selected). The remaining two properties simply
access or store values to their respective private variables.
The user control will expose two methods – one for adding items to the list, and
one for removing items. The AddItem() method will accept a string as a parameter,
which will be added to the Items ArrayList object. The RemoveItem() method will
accept an Integer representing the index of the item to remove from the Items
ArrayList. It performs some simple bounds checking to ensure that the parameter
passed to it is within range before attempting to remove the item. The implementation
for these two methods follows:
Public Sub AddItem(ByVal item As String)
Items.Add(item)
End Sub
Theses methods and properties will be able to control all aspects of the control.
However, before the code to render the control is written, the following UI code must
be placed in the control’s user interface file:
<table border="0" id="tblSelectionTable" runat="server">
</table>
This table will be used to display the items in the ArrayList. It is important to note
that this code could have been replaced by creating an HtmlTable control in the
server-side code.
When the ASP.NET engine creates an instance of a user control, several methods
are called within the control. The Control class’s CreateChildControls() method is
called when the control must create its child controls and add them to the control tree
prior to rendering. The method must be overridden so that our table list control can
create the actual list in HTML from the ArrayList object. Listing 13.4 shows the
overridden method.
Protected Overrides Sub CreateChildControls()
Dim i As Integer
For i = 0 To Items.Count - 1
Dim tr As New HtmlTableRow()
Dim tc As New HtmlTableCell()
tc.InnerHtml = CStr(Items.Item(i))
If SelectedItem = i Then
tc.Attributes.Add("bgcolor", ColorTranslator.ToHtml(SelectedColor))
Else
tc.Attributes.Add("bgcolor",
ColorTranslator.ToHtml(UnselectedColor))
End If
tr.Cells.Add(tc)
tblSelectionTable.Rows.Add(tr)
Next
End Sub
[ Listing 13.4 ]
The objective of this method is to create a single row for each item in the Items
ArrayList. If the item is “selected”, then that background color for the cell must be the
SelectedColor property, and if not, the UnselectedColor property must be used.
The first two lines declare a counter variable and start a For loop through all the
items in the ArrayList. Next, a table row and table cell are declared. The table cell is
then set to contain the text from the corresponding item in the Items ArrayList. An If
condition checks to see whether the current item is “selected”. If so, the cell’s bgcolor
attribute is set to the HTML color value for the SelectedColor property, and if not, the
UnselectedColor property is used. Finally, the cell is added to the row, and the row is
added to the table.
The declaration for the control must be made in the web form’s code behind file.
Assuming that the control is named “SelectionTable1”, the declaration should be
written as follows:
Protected WithEvents SelectionTable1 As SelectionTable
The Page_Load event handler will be used to fill the SelectionTable control with
values using the AddItems() method:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
SelectionTable1.AddItem("Leonard Fienberg")
SelectionTable1.AddItem("Daniel Bense")
SelectionTable1.AddItem("Patrick Collins")
SelectionTable1.AddItem("Len Leisegang")
SelectionTable1.AddItem("Jono Hotz")
SelectionTable1.RemoveItem(2)
SelectionTable1.SelectedItem = 3
End Sub
[ Figure 13.5 ]
To demonstrate the use of the SelectionTable’s Item() property, place a label on
the form, and add the following line to the Page_Load event handler:
Label1.Text = SelectionTable1.Item(0)
When the page is loaded, the label should display, “Leonard Fienberg”.
The code is fairly simple – it uses the StateBag’s Item() property to manipulate the
items in the bag. Using this method, if the item did not previously exist, it will be
created, and if it existed previously, the item will be updated with the new value.
The subroutine to restore the state is equally simple:
Private Sub LoadControlState()
If Not IsNothing(Me.ViewState.Item("Items")) Then
Items = Me.ViewState.Item("Items")
End If
If Not IsNothing(Me.ViewState.Item("intSelectedItem")) Then
intSelectedItem = Me.ViewState.Item("intSelectedItem")
End If
If Not IsNothing(Me.ViewState.Item("colSelectedColor")) Then
colSelectedColor = Me.ViewState.Item("colSelectedColor")
End If
If Not IsNothing(Me.ViewState.Item("colUnselectedColor")) Then
colUnselectedColor = Me.ViewState.Item("colUnselectedColor")
End If
End Sub
Before each item is restored, an If condition checks to ensure that it does actually
exist in the StateBag. It would probably suffice to only check for one item in the
StateBag, as it could then be logically assumed that the other items were present, but
to be safe, all four variables are checked before the assignments are made.
These two methods should be called from the CreateChildControls() and
Page_Load() methods respectively. The CreateChildControls() method is as good a
place as any for the SaveControlState() method to be called, as it is only executed
once the control is to be rendered, so all property changes will have occurred by the
time the method is called. The method should be called without any arguments before
or after the code inside the CreateChildControls() method, as shown below:
Protected Overrides Sub CreateChildControls()
'**Code to create table rows
SaveControlState()
End Sub
Take note that the actual content creation code has been omitted and replaced by a
comment.
The Page_Load event handler will appear with the LoadControlState() call as
follows:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
LoadControlState()
End Sub
With these two methods and calls in place, the control can now manage its own
state.
Now add two buttons to the form in the designer and name them btnPrevious and
btnNext, and assign their Text properties to “<” and “>” respectively. Next add
another button, name it btnRemove and set its Text property to “Remove”. Finally
add a TextBox, txtNewItem, and another button, btnAdd, giving the button the Text,
“Add Item”. Double click the btnPrevious button to hook up and event handler and
insert the following code, so that the event handler looks like this:
Private Sub btnPrevious_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnPrevious.Click
If SelectionTable1.SelectedItem <= 0 Then
SelectionTable1.SelectedItem = SelectionTable1.Count - 1
Else
SelectionTable1.SelectedItem = SelectionTable1.SelectedItem - 1
End If
End Sub
Add the following code for the btnNext button’s Click event handler:
Private Sub btnNext_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnNext.Click
If SelectionTable1.SelectedItem >= SelectionTable1.Count - 1 Then
SelectionTable1.SelectedItem = 0
Else
SelectionTable1.SelectedItem = SelectionTable1.SelectedItem + 1
End If
End Sub
The btnAdd button’s Click event handler should feature this code:
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnAdd.Click
SelectionTable1.AddItem(txtNewItem.Text)
End Sub
And finally, the Page_Load event handler should initialize the SelectionTable:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
If Not Page.IsPostBack Then
SelectionTable1.AddItem("Patrick Collins")
SelectionTable1.AddItem("Chris Hide")
SelectionTable1.AddItem("Leon Cilliers")
SelectionTable1.AddItem("Ross Glashan")
SelectionTable1.SelectedItem = 0
End If
End Sub
The basic purpose of this web form is to demonstrate how the page can interact
with the user control across post-backs. The Previous and Next buttons allow the user
to “cycle through” the different items in the list. The Remove button will remove the
currently selected item from the list, and the Add button will add another item to the
list using the text in the TextBox. The Previous and Next button event handlers both
ensure that an item is always selected by checking to see if the currently selected item
is at the start or end of the list and acting accordingly, For example, if the selected
item is the last item of the list, and the user clicks the Next button, the event handler
will select the first item, starting another “cycle”. The Previous button behaves
similarly when the first item is selected and it is clicked.
The Remove event handler first checks to see if the currently selected item is the
last on the list, so that when it is removed, the next item can be selected. This prevents
a situation where no item is selected if the last item in the list is removed (except
where it is the only item).
The Add button’s event handler simply call’s the control’s AddItem() method and
passes the Text from the txtNewItem control. The Page_Load event handler first
checks to see if the request is a postback, and if not (i.e. the page is being loaded for
the first time), it populates the SelectionTable control with some values and selects
the first item.
Figure 13.6 shows this demonstration form loaded in the browser after having
been manipulated using the buttons available:
[ Figure 13.6 ]
The use of the StateBag class and ViewState property is very important when
maintaining state in control development, and can be a very useful tool for building
robust, usable controls. Bare in mind that in order to achieve the state persistence for
the control in the demo above, only several lines of code were required to save and
load the state from the StateBag.
The If condition checks to ensure that the HeaderTemplate property has been
assigned a value. If it hasn’t then the tmpHeaderTemplate variable will still be
“Nothing”. If a HeaderTemplate has been specified, then a new table row, th is
created, along with a cell for the row, tc. The ITemplate interface’s InstantiateIn()
method requires that a parameter of type Control is passed. The method then adds the
contents of the template to this control. In the SelectionTable control, after the new
table cell has been created and filled, it is added to the new row, and the row is added
to the table. The remaining code from the original CreateChildControls() method
follows.
Switch back to the HTML view of the UI. The code for the control will look
similar to this:
<uc1:SelectionTable id=SelectionTable1
runat="server"></uc1:SelectionTable>
Adding the HeaderTemplate to the control is fairly simple – all that needs to be
done is a HeaderTemplate “tag” must be included as a “child” of the user control, and
the code for the template must be specified inside:
<uc1:SelectionTable id=SelectionTable1 runat="server">
<HeaderTemplate>
<font color="#ff0000">Movie List</font>
</HeaderTemplate>
</uc1:SelectionTable>
This will result in the text “Movie List” being inserted as a header for the table, in
red text. However, the template could contain practically any HTML or even
ASP.NET server controls. Figure 13.7 shows the web form with the templated
SelectionTable control loaded:
[ Figure 13.7 ]
Instead of simply generating an HTML string to add to the page to create the
menu, it would be possible to build an HtmlMenuItem control, derived from the
System.Web.UI.HtmlControls.HtmlContainerControl class, which would offer an
object-oriented approach to building the menu. The class in Listing 13.5 implements
the HtmlMenuItem object, and shows both how to derive from a regular Html Control
class, and how to create a new control entirely programmatically:
Public Class HtmlMenuItem
Inherits System.Web.UI.HtmlControls.HtmlContainerControl
End Class
[ Listing 13.5 ]
The “Inherits” keyword is used to derive the control from the
HtmlContainerControl class. The next few lines declare the private variables for
storing the three property values, and the properties themselves – ItemName,
LinkURL and Vbar. ItemName contains the name that will be displayed, LinkURL
contains the URL that the menu item must link to, and the Vbar property is a boolean
specifying whether or not a vertical bar should be appended to the end of the item (the
last item in the menu should not have one, which is why this is needed).
Two constructors are available. Both simply take the arguments passed to them
and assign them to the appropriate properties. Finally, the overridden
CreateChildControls() method creates the actual HTML for the item just prior to it
being rendered. An HtmlAnchor control is used to create the link, which is added to
the control’s Controls list, and if necessary, a vertical bar is included, preceded and
followed by non-breaking spaces.
To actually use the new control, create a new class in Visual Studio.NET by right-
clicking the project node in the Solution Explorer, and click Add-Add New Item and
choose “Class” from the dialog. Finally, insert the implementation from Listing 13.5
into the class. Other pages in the project can then use the class.
To try out the HtmlMenuItem class, create a new web form, and add a label
control named, “lblMenu”. Ensure that the Text property is empty. Switch to code
view, and insert the following code into the Page_Load event:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
lblMenu.Controls.Add(New HtmlMenuItem("Home", "default.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("Members", "members.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("Articles", "articles.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("News", "news.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("Events", "events.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("Jobs", "jobs.aspx"))
lblMenu.Controls.Add(New HtmlMenuItem("About", "about.aspx", False))
End Sub
When the page is loaded, the menu will be generated and displayed. Of course for
this example, the control is fairly pointless, as the menu could easily have been
generated in the form designer using the regular controls, however, it’s usefulness
becomes more evident when the menu, or parts thereof, must be loaded from a
database, and even more so when items in the menu might be subject to change their
display name or URL, thanks to the inclusion of properties in the control.
Deriving from and extending existing controls can be extremely powerful, and
save a significant amount of time. For example, the AdRotator control is a fine base to
work from when attempting to build a custom banner control. It already contains
much of the logic for a simple banner rotation system, and could possibly be modified
for more individual needs, such as loading the banner data from a SQL Server
database rather than an XML file without nearly as much effort as it would take to re-
implement the control from scratch.
Conclusion
This chapter has provided a detailed overview of much of what is possible using
user controls in ASP.NET. Custom-built user controls are an essential part of code-
reuse in the ASP.NET programming model, and offer a solution that is significantly
more comprehensive and powerful than the existing ASP equivalent of server-side
includes. Controls should not be over-used, but for situations where code reuse is
bound to occur, such as with basic page layout items like headers, footers and menus,
controls make an obvious solution to the inevitable maintenance problems if a
reusable design element is not used. The ability to persist state across postbacks, use
the new templates functionality and inherit from other controls all add to the appeal of
user controls as a medium for encapsulation and code reuse.
Chapter 14
Web Services
Much of the hype following the dot-com fallout has been related to B2B (Business-to-
Business) Internet transactions, and in particular "Web Services". This chapter aims to
remove the marketing hype from the topic and present a clear case of exactly what
web services are, how they work, how they help you develop better applications and
how you can build (and consume) them using Microsoft's technologies.
A Different Approach
The Object Oriented Programming paradigm was a huge advance in the world of
computer programming. It advocated the principle of code-reuse. "Why rewrite a
piece of code, when you've already written it elsewhere?". The principles of OOP
were advanced upon and incorporated into an era of "component oriented
programming" where specific reusable functionality is contained in components. The
.NET framework and VS.NET fully embrace this programming paragdim, and C# was
even developed specifically with this in mind. Much of the thinking behind web
services revolves around the same principles, which are again, expanded upon. The
theory of web services is that web sites should not only provide information to
customers, but also expose certain business logic and information to business
partners. Thinking back to the hypothetical e-commerce sites that stores its courier's
rates itself, an application of a web service could be that the courier company exposes
its shipping rates as a web service, that the e-commerce site could use to obtain the
most recent rates on-the-fly whenever a quote for a customer is generated. Figure 14.1
shows the flow of information in this scenario.
[ Figure 14.1 - An example of a simple web service ]
Caching
As anyone who uses the Internet will tell you, performance is a major issue. As a
developer, everything must be done to optimize application performance as much as
possible, to reduce the strain on server resources so that more clients can be served. In
single user, low-load applications, there is no need for caching. However, as soon as
external resources are accessed, caching becomes essential, particularly in n-tiered
web application development. Previously most caching functionality had to be
programmed manually, but ASP.NET now provides two extensive caching
mechanisms which should provide enough functionality to cover any caching
requirement without any additional “plumbing” code. These two types of caching are
Output Caching and Data Caching. Output Caching is the more basic of the two, and
uses a directive to turn caching on or off for a particular page. This method is ideal for
entire pages that need to be cached for certain periods of time. Data Caching has a
more specific application. It is used for storing specific pieces of data, which can
include objects, and it programmatically accessible. As a vast oversimplification, it
can be considered as a special type of Application variable.
Output Caching
Output Caching provides an extremely simple method of caching entire pages, or
in fact, even parts of pages. Output Caching is enabled by using the @ OutputCache
directive in the pages that need to be cached. The syntax of this directive is as
follows:
<%@ OutputCache Duration="#ofseconds" Location="Any | Client |
Downstream | Server | None" VaryByControl="propertyname"
VaryByCustom="browser | customstring" VaryByHeader="headers"
VaryByParam="parametername" %>
As can be seen, the directive offers numerous parameters for modifying its
functionality. These parameters make this facility very flexible and suitable for use in
most page and control caching scenarios without any additional programming.
Duration
The most basic use of this OutputCache directive involves the Duration parameter
and VaryByParam parameter. These parameters are mandatory when using output
caching. Duration specifies the length of time (in seconds) for which the page will be
cached before it is refreshed. VaryByParam indicates for which querystring
parameters the page must be refreshed. Again, even if you don’t intend to differ the
cached versions for different values passed through querystrings, this parameter is
mandatory and omitting it will cause a compiler error. Listing 16.1 demonstrates the
simplest form of Output Caching:
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="OutputCachingDemo.aspx.vb"
Inherits="Caching.OutputCachingDemo"%>
<%@ OutputCache Duration="60" VaryByParam="None" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual
Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic
7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="frmOutputCachingDemo" method="post"
runat="server">
<!-- Data from dynamic source, such as a database -->
</form>
</body>
</HTML>
This page doesn’t actually contain anything that would make caching
advantageous, such as data pulled from a database, or information from a web service,
but only demonstrates the use of the directive. As can be seen, caching is enabled in
one line. No extra programming is required. It has a Duration of 60 seconds, meaning
that 60 seconds after the first time this page is hit, it will be refreshed. All hits during
those 60 seconds will be served from the cached page. Had information from a
database been included in the page, the database server would not have been hit, as
the entire page would’ve been loaded from the cache, including all the dynamic
content. An easy way to test that the page is in fact being cached, and subsequently
loaded from the cache is to put a timestamp on the page, which will show when the
page was last generated. You can do this by adding a Label Web Control to the form
with the name lblLastGenerated and then add this code to the Page_Load event in the
Code Behind file:
lblLastGenerated.Text = DateTime.Now.ToString(“G”)
When the page is loaded, the label’s text property is assigned the current date and
time in the format “mm/dd/yyyy hh:mm:ss”. The first time the page is loaded, the
label will display the current date and time. However, subsequent hits in the next 60
seconds will display the same date and time as the original hit because the page is
being loaded from the cache, rather than reprocessing the page. After the 60 second
time interval has elapsed, the next hit will cause the page to again to regenerated and
the current date and time will be displayed, and so forth.
VaryByParam
Even simple Web Forms would often not be able to take advantage of output
caching if the VaryByParam parameter was not available. Very often the contents of a
page will change according the various querystring parameters being passed to it. This
is not to say that the contents change for subsequent hits to the page with the same
querystring parameters however, which is why caching may be appropriate. The
VaryByParam parameter for output caching is available to cater for almost all
querystring-related scenarios. Listing 16.2 shows an ASP.NET page that uses the
VaryByParam parameter, along with Listing 16.3 – the codebehind for 16.2 – to make
effective use of caching whilst ensuring that accurate data is delivered to the user.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="OutputCachingDemo.aspx.vb"
Inherits="Caching.OutputCachingDemo"%>
<%@ OutputCache Duration="60" VaryByParam="catid" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual
Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic
7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="frmOutputCachingDemo" method="post"
runat="server">
<asp:Panel id="pnlProducts"
runat="server"></asp:Panel>
</form>
</body>
</HTML>
[ Listing 16.2 ]
Public Class OutputCachingDemo
Inherits System.Web.UI.Page
Protected WithEvents pnlProducts As System.Web.UI.WebControls.Panel
End Sub
#End Region
End Class
[ Listing 16.3 ]
The ASP.NET page includes an OutputCache directive like this:
<%@ OutputCache Duration="60" VaryByParam="catid" %>
This tells the compiler to cache the page output for 60 seconds for requests where
the querystring parameter “catid” is the same. In other words, if the page is called by a
client using OutputCachingDemo.aspx?catid=1 then the output from that page is
cached. Subsequent requests for OutputCachingDemo.aspx?catid=1 for the following
60 seconds are loaded from the cache. However, calls to the page with other values
for catid will not be loaded from the cache. For example, a call to
OutputCachingDemo.aspx?catid=2 would regenerate the page and store the result
when catid is 2 in cache. Subsequent requests to OutputCachingDemo.aspx?catid=2
in the next 60 seconds will be loaded from cache.
The VaryByParam parameter for Output Caching provides for a great amount of
flexibility, but there may be instances where more than one querystring parameter has
effect one what should be displayed on the page. VaryByParam provides for this
eventuality in one of two ways – either the required parameters can all be specified,
separated by semi-colons, or an asterix can be used, effectively specifying all the
querystring parameters. An example of the usage for the former would be as follows:
<%@ OutputCache Duration="60" VaryByParam="catid,supplierid" %>
In this case, any change to either of the catid or supplierid querystring parameters
will cause the page to be generated and not loaded from cache. For example, a call to
OutputCachingDemo.aspx?catid=1&supplierid=1, then
OutputCachingDemo.aspx?catid=1&supplierid=2 will cause two different versions of
the page to be cached. In this case, both the catid and supplierid querystring
parameters must have the same values if the page is to be loaded from cache. This
would not be possible if only one querystring parameter was specified, for example as
follows:
<%@ OutputCache Duration="60" VaryByParam="catid" %>
Calls to the page where any querystring parameter’s value is different will result
in a new version of the page being cached. Requests must be identical (in terms of the
values passed through the querystring, at least) in order for the page to be loaded from
cache. This can be very useful in some circumstances, but preferably only use it
where it makes sense to, as pages that take in many querystring parameters may strain
the server.
VaryByHeader
Every request to a file on a web server sends with it specific information. This
includes information about the browser that is requesting the page, the localization
settings on the client and other information. Table 16.1 shows the list of so-called
common HTTP “header” variables.
HTTP Header Description
Accept The types of files that the client will accept
Accept-Charset If the client can accept special-purpose character
sets, this header will be used to indicate the
capability to the server.
Accept-Encoding The content-coding values that the client will
accept
Accept-Language The language setting that the client would prefer
the result to be returned in (this is normally the
language setting of the client machine)
Connection The type of TCP/IP connection
Host The name/IP address of the server
Referer The URI of the page that caused this request to be
made.
User-Agent Information about the client browser
[ Table 16.1 – Common HTTP header variables ]
The VaryByHeader parameter can be very useful in certain circumstances,
particularly when used in applications that are localized. The VaryByHeader
parameter works very similarly to the VaryByParam parameter, in that it allows for
different versions of a page to be cached, depending on how the page was requested.
The VaryByHeader parameter will not be used very often, although it can play a very
important role in localized applications.
As has already been mentioned, all OutputCache directives must include the
VaryByParam parameter, even it its functionality is not required. In the example in
listings 16.4 and 16.5 (aspx and VB codebehind files respectively), the use of only the
VaryByHeader parameter is demonstrated for localization purposes, but the
VaryByParam parameter is notably present.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="OutputCachingDemo.aspx.vb"
Inherits="Caching.OutputCachingDemo"%>
<%@ OutputCache Duration="60" VaryByParam="None"
VaryByHeader="Accept-Language" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual
Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic
7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="frmOutputCachingDemo" method="post"
runat="server">
<asp:Label id="lblEnglishName"
runat="server"></asp:Label>
</form>
</body>
</HTML>
[ Listing 16.4 ]
Public Class OutputCachingDemo
Inherits System.Web.UI.Page
Protected WithEvents lblEnglishName As
System.Web.UI.WebControls.Label
End Sub
#End Region
End Class
[ Listing 16.5 ]
This example varies the cache according to the Accept-Language HTTP header. In
essence this means that versions of this form will be cached for requests from
different parts of the world. This isn’t entirely correct, as client systems can “lie”
about where the come from, but for the purpose of this example, this is what happens.
This page doesn’t actually do very much with the localization information that it
receives and doesn’t load the page content from a database accordingly, or anything
similar, but simply demonstrates how a page could be cached according to the origin
of the request. Let’s assume that the page received hits with the Accept-Language
header with the following values, in this sequence:
en-US
en-GB
en-GB
en-ZA
en-US
en-GB
en-US
Assuming that the hits were within a 1-minute time period, 3 versions of the
page’s output would be cached – one for en-US, one for en-GB and one for en-ZA,
with the remaining hits all be served from the cache. To test this example for different
localization settings, change the Duration to 600 (10 minutes) so that you will have
enough time to change your localization settings without your cache being
invalidated. To make sure that you are receiving cached pages when you’re supposed
to be, you make wish to add a label Web Control to the form and set it’s ‘Text’
property to the current time when the page it loaded. Load the page with your current
localization settings. i.e. Don’t change anything yet – just load the page. A version for
your localization setting will be cached. The full English name for your localization
setting should be displayed, as shown in Figure 16.1.
[ Figure 16.1 – Output Caching with localization settings ]
In order to change your localization settings, go to your Control Panel and open
the Regional Options dialog. It should look similar to Figure 16.2:
Location
The HTTP 1.1 protocol provides built-in functionality for caching, in the form of
HTTP headers. The “Location” attribute of the OutputCache directive allows for
flexibility in specifying where cached copies of the page will be stored, which may
involve the use of HTTP headers. The headers concerned are the “Cache-Control” and
“Expires” headers. The “Cache-Control” header, when used in combination with the
“Expires” header can be used to specify if and where the page in question can be
cached. This includes whether the page cannot be cached at all, whether it can be
cached on only the client or on the client and proxy servers between the client and the
server. ASP.NET alleviates the necessity to deal with the headers at all by providing
the “Location” attribute, which, in addition to using HTTP headers to define caching
policy, also makes use of the ASP.NET engine. To understand the “Location”
attribute more clearly, and the respective values that it can contain, it is important to
understand that when a client requests a page, the request, and thus response, more
that likely goes through a series of machines, and the request does not go directly to
the server (this may be the case in some Intranet scenarios, but on the Internet, this is
highly unlikely to occur). Figure 16.3 shows a highly simplified diagram of a request
and response for a page to demonstrate this concept.
[ Figure 16.3 – Following the page of an example HTTP request/response on the
Internet ]
As can be seen, Internet traffic from the client will typically go through numerous
“proxy” servers at the user’s ISP, and possibly even of the company that are hosting
the actual website. One of the functions of proxy servers is to cache the content that
goes through them such that subsequent requests for previously requested content are
delivered from the proxy server, rather than re-fetching the content from the web
server. However, before HTTP 1.1-compliant proxy servers cache content, they
ensure that the HTTP headers specify that they are allowed to do so (or more
specifically, they check to see if they are not allowed to, and if they are not, then they
do not cache the content, but all other content is cached).
The “Location” attribute has 4 possible values. These values stored in the
System.Web.UI.OutputCacheLocation enumeration. Table 16.2 shows the possible
values and their purpose.
Value Purpose
Any The cache can be located on the client, a proxy
server or on the web server
Client The cache is located on the browser
Downstream The cache is located on proxy servers between the
client and the server
None The page may not be cached
Server The cache is located on the web server
[ Table 16.2 – OutputCacheLocation enumeration values ]
The default value (the value if the “Location” attribute is omitted) is “Any”. In
most cases, it is unnecessary to modify this property. The most common instance
where this is not the case is in applications where a user may be referred back to a
page whose content is likely to have changed since their last hit. In this case, it is
undesirable for the browser to cache the page (in which case the user has to perform a
manual “refresh” to see the updated page), and therefore should be instructed to load
the file from the server for all requests (be it generated, or from the server cache) and
not the browser cache. In a scenario such as this, and OutputCache directive might be
used as follows:
<%@ OutputCache Duration=”60” VaryByParam=”stockquotesymbol”
Location=”Server” %>
There are other occasions where this functionality can prove useful. However, it is
not often that it is required, and can normally be safely left to use the default value of
“Any”, but it is useful to know in case of the two abovementioned scenarios, or other
similar instances where you need control over where cached copies of pages are
stored.
VaryByCustom
Until now, the Output Caching mechanism has provided numerous properties to
make it more flexible, but the “VaryByCustom” attribute is the definitive solution to
most possible shortcomings of the Output Caching feature in ASP.NET. The purpose
of VaryByCustom is actually twofold – firstly, it provides a mechanism to allow
different versions of pages to be cached according to the client browser that requests
them, and secondly, to allow you, the developer, to specify any criteria for caching
that has not already been covered by the other attributes.
“VaryByBrowser”
A major potential problem with output caching is that a page could be cached after
being requested by a DHTML-aware browser, such as IE5, and thus contain a large
amount of client-side code (such as validation code), and then be requested by a
downlevel browser such as IE3, or Netscape 3, which does not support DHTML, and
thus return numerous scripting errors on the client-side, or simply not run at all when
the server returns the cached version of the page. To alleviate this problem, Microsoft
has included the VaryByCustom attribute with the value of “browser”, which will
cache different versions of a page for each browser, using the browser name and
major version as its criteria. This means that a page requested by IE5.0, IE5.01 and
Netscape 4 would have 2 versions cached – one for IE5, one for Netscape 4. In other
words, VaryByCustom=”browser” only uses the major browser version as a factor,
and not minor browser versions.
As has already been mentioned, validation is one area where caching could cause
client scripting errors. However, the effects of caching should be taken into account in
any application where client-side scripting is generated on-the-fly by ASP.NET code
according to the browser version. This is a common technique, also often used to
dynamically deploy stylesheets and other browser-version specific elements.
VaryByCustom with the “browser” value enables pages that use these techniques to
take advantage of the powerful caching features of ASP.NET without interfering with
the existing browser-specific code.
Listing 16.6 and 16.7 list the code demonstrating a simple use of
VaryByCustom=”browser”, where the name of the client browser and last time
generated are displayed and cached for 60 seconds for each different browser that hits
the page.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="OutputCachingDemo.aspx.vb"
Inherits="Caching.OutputCachingDemo"%>
<%@ OutputCache Duration="60" VaryByParam="None"
VaryByCustom="browser" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual
Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic
7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="frmOutputCachingDemo" method="post"
runat="server">
<P>
<asp:Label id="lblBrowser"
runat="server"></asp:Label>
</P>
<P>
<asp:Label id="lblLastGenerated"
runat="server"></asp:Label>
</P>
</form>
</body>
</HTML>
[ Listing 16.6 ]
Public Class OutputCachingDemo
Inherits System.Web.UI.Page
Protected WithEvents lblLastGenerated As
System.Web.UI.WebControls.Label
Protected WithEvents lblBrowser As System.Web.UI.WebControls.Label
End Sub
#End Region
End Class
[ Listing 16.7 ]
Assuming that the hits occurred in the following order, listed by the client browser
used:
IE 5
IE 5
Netscape 4
Netscape 3
IE 6
Netscape 4
4 versions of the page would be cached – one for IE 5, one for IE 6, one for
Netscape 3 and one for Netscape 4. Since the caching duration is set at 60 seconds,
the hits for each respective browser would have to be within the 60-second limit
before a new version would be generated for the requesting browser type and version.
Custom Caching
End Sub
#End Region
End Class
[ Listing 16.8 ]
An ASP.NET page in the application could then include the following
OutputCache directive, and one version of the page would be cached if the requesting
client browser supported frames, and another version if a request was made by a
browser that did not.
<%@ OutputCache Duration="60" VaryByParam="None"
VaryByCustom="frames" %>
Like all the other “Vary” attributes, VaryByCustom supports multiple values,
separated by commas. This enables more that one criterion to be “required” for a
cached copy to be retrieved. Continuing with the example of caching based on
browser functionality, we’ll add a new criterion to the list in addition to “frames” –
“applets”, which will create different versions of pages in the cache according to
whether the client browser supports java applets or not. Listing 16.9 shows the
updated GetVaryByCustomString to allow for this extension.
Public Overrides Function GetVaryByCustomString(ByVal Context As
HttpContext, ByVal arg As String) As String
If arg = "frames" Then
Return "frames=" & Context.Request.Browser.Frames
ElseIf arg = "applets" Then
Return "applets=" & Context.Request.Browser.JavaApplets
Else
Return ""
End If
End Function
[ Listing 16.9 ]
If the “arg” parameter passed to the function is “frames” then the function reports
the frames support of the client browser, if the parameter is “applets” then the Java
applets support is returned, and if any other option is passed, nothing is returned,
which is the same for all requests, thus not leading to a new version of a page being
cached each time one is hit that uses a VaryByCustom with a value other than
“frames” or “applets”.
This added functionality provides for numerous options as to which pages are
cached and which are generated. Assuming that the VaryByCustom attribute has a
value of “frames,applets”, there could be, at most, 4 different versions of a page in
cache, for these scenarios:
Frames support, applets support
No frames support, no applets support
Frames support, no applets support
No frames support, applets support
The VaryByCustom attribute could also have the values “frames” or “applets”, in
which case there is a maximum of 2 cached versions for each, respectively.
There are myriads of possible uses for VaryByCustom, and since the actual
criteria is developed exactly as you want it, from all the data available for the request,
the likelihood of an output caching scenario made impossible because data would not
be able to be properly refreshed practically diminishes. From varying the cache
according to the contents of cookies sent in the HTTP request body, to varying by
client certificates, VaryByCustom is fit to handle the task.
As with all things in programming, search for the simplest solution to your problem.
As has already been stated, it is only in fairly specific circumstances when using
VaryByCustom is actually required, versus where it can be used. Often the other
attributes for the OutputCache directive will offer the functionality that is required,
without any need to use custom caching logic.
Simple Caching
The simplest form of caching using the Cache property that has a direct equivalent
to the OutputCache directive makes the page available as a cached version on all
nodes (client, downstream and server) with a timeout of 60 seconds. To do this, two
methods need to be called – one to define when the cache should expire (similar to the
Duration attribute), and another to set the availability of the cached copy. This can be
thought of as the Location attribute; however, it must be explicitly set for caching to
be enabled, unlike in using the directive, where it can be omitted.
Response.Cache.SetExpires(DateTime.Parse(“6:00:00PM”))
Response.Cache.SetCacheability(HttpCacheability.Server)
The SetExpires method takes a DateTime as its only parameter. Because of this, it
can be used to define absolute expiration times, such as demonstrated above, as well
as offer functionality similar to the Duration attribute, which defines the amount of
time in seconds that the page will be valid in the cache. The following code
demonstrates how to emulate this functionality using SetExpires.
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))
Response.Cache.SetCacheability(HttpCacheability.Server)
[ Table 16. 3 ]
The SetCacheability method and the HttpCacheability enumeration are similar to
the Location attribute, and the System.Web.UI.OutputCacheLocation enumeration,
although there are differences. The HttpCacheability enumeration deals mainly with
the HTTP headers dealing with caching, and only contains one option where the
cached copy is located on the server, whereas the OutputCacheLocation enumeration
offers slightly different functionality. See Table 16.2 for more details.
It is important to note that once this method has been invoked, it is impossible to
re-enable server caching for the current response.
Varying by HTTP Header
The SetVaryByCustom method, despite what its name may suggest, is not the
equivalent of the VaryByCustom attribute, but offers functionality more similar to
that of the VaryByHeader attribute. Specifically, the SetVaryByCustom method sets
the HTTP “Vary” header to the string value that you specify. This ensures that the
server responds with a cached copy of the page where the header value of the current
request is the same as the cached copy for the specified header.
VaryByHeader=”Accept-Charset” would have similar functionality to the following
statement.
Response.Cache.SetVaryByCustom(“Accept-Charset”)
The simplest form of caching for user controls is exactly the same as for pages –
the OutputCache directive is used, with a value in seconds for the length that the
output is to be cached for in the Duration attribute, and a value of “none” in the
VaryByParam attribute:
<%@ OutputCache Duration="60" VaryByParam="none" %>
Listing 16.10 shows an ASP.NET with a user control that has caching enabled.
Listing 16.11 is the User Control UI, and Listing 16.12 the User Control logic.
<%@ Register TagPrefix="uc1" TagName="FragmentOutputCaching"
Src="FragmentOutputCaching.ascx" %>
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="OutputCachingDemo.aspx.vb"
Inherits="Caching.OutputCachingDemo"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual
Studio.NET 7.0">
<meta name="CODE_LANGUAGE" content="Visual Basic
7.0">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="frmOutputCachingDemo" method="post"
runat="server">
<uc1:FragmentOutputCaching
id="FragmentOutputCaching1" runat="server">
</uc1:FragmentOutputCaching>
</form>
</body>
</HTML>
[ Listing 16.10 ]
<%@ Control Language="vb" AutoEventWireup="false"
Codebehind="FragmentOutputCaching.ascx.vb"
Inherits="Caching.FragmentOutputCaching" %>
<%@ OutputCache Duration="60" VaryByParam="none" %>
<asp:Label id="lblGenerated" runat="server">
</asp:Label>
[ Listing 16.11 ]
Public MustInherit Class FragmentOutputCaching
Inherits System.Web.UI.UserControl
Protected WithEvents lblGenerated As System.Web.UI.WebControls.Label
End Sub
#End Region
End Class
[ Listing 16.12 ]
The ASP.NET page contains nothing special. It has a user control added the same
way any other user control is added, as is detailed in Chapter 13. The user control
itself also contains nothing notable, with the exception of the OutputCache directive.
The User Control logic also contains nothing to suggest that the user control is
cached. Summed up, basic fragment caching is a very simple affair, and only involves
adding one directive to the user control UI file.
Unlike regular output caching, Fragment output caching creates a new version of the
cached control for every page or form that it is used on. For example, if you use a
user control, with caching enabled, called “mycontrol.ascx” and then use the control
on two pages, “page1.aspx” and “page2.aspx”, 2 different versions of
“mycontrol.ascx” will be cached.
The next, and possibly most important, method of varying the cache for different
versions of user controls is by properties set by attributes. The primary method of
setting initial user control properties is through attributes in the User Control
definition tag in the UI of the ASP.NET page. For example, when you set the text
property of a Label Web Control, the definition may look like this:
<asp:Label id="SomeLabel" runat="server" Text="Label Text" />
However, in code, you could also set the property like this:
SomeLabel.Text = “Label Text”
[ Listing 16.13 ]
<%@ OutputCache Duration="60" VaryByParam="none" %>
<%@ Control Language="vb" AutoEventWireup="false"
Codebehind="FragmentOutputCaching.ascx.vb"
Inherits="Caching.FragmentOutputCaching" %>
<P>
<asp:Label id="lblText" runat="server">
</asp:Label>
</P>
<P>
<asp:Label id="lblGenerated" runat="server">
</asp:Label>
</P>
[ Listing 16.14 ]
Private strText As String = ""
[ Listing 16.15 ]
If the ASP.NET page is loaded in a browser, there will be two paragraphs
displayed, one with the text “There is a different version of this control in cache for
each different 'Text' property value” and the second displaying the current date and
time. Notice in listing 16.14 that the OutputCache directive does not include any
special attributes. Refreshing the page in the browser will (so long as you do it within
60 seconds of loading the page originally) produce exactly the same result. Naturally
after the 60 seconds has expired, a new version will be cached, with the updated time
displayed. However, if you modify the text attribute in the ASP.NET page and then
reload, you will see that an entirely new version of the page is cached. Subsequent
requests are then loaded from the cache.
It is important to note that the Text property need not have been a property in
order for the cache to vary by it. If it was actually only a public string, the caching
engine would still use it, so long as it is still being set declaratively by the user
control tag. I.e. it would still work even if the Text property in the User Control
code-behind file was removed and replaced with this (assuming that nothing else is
changed):
VaryByControl
User controls may often contain other web controls themselves, which will of
course expose properties of their own. The values of these properties may possibly
affect the output of the user control. It is therefore important that different versions of
the user control are created for the controls whose properties may affect the output of
the control. Hence Microsoft included the VaryByControl attribute of the
OutputCache directive for user controls. VaryByControl works very similarly to
VaryByParam – it takes a semi-colon separated list of control names, and forces
different versions of the user control to be cached where the properties of any of the
specified controls are different from the cached version(s)’s controls’ properties.
Listing 16.15.1 and 16.15.2 show the UI and code-behind code for a user control that
demonstrates this.
<%@ Control Language="vb" AutoEventWireup="false"
Codebehind="FragmentOutputCaching.ascx.vb"
Inherits="Caching.FragmentOutputCaching" %>
<%@ OutputCache Duration="10" VaryByControl="cboChoice"
VaryByParam="none"%>
<P>
<asp:DropDownList id="cboChoice" runat="server">
<asp:ListItem Value="This is option 1.">
Option 1</asp:ListItem>
<asp:ListItem Value="This is option 2.">
Option 2</asp:ListItem>
<asp:ListItem Value="This is option 3.">
Option 3</asp:ListItem>
</asp:DropDownList>
<asp:Button id="btnGo" runat="server" Text="Go">
</asp:Button>
</P>
<P>
<asp:Label id="lblSelected" runat="server">
</asp:Label>
</P>
[ Listing 16.15.1 ]
Public MustInherit Class FragmentOutputCaching
Inherits System.Web.UI.UserControl
Protected WithEvents cboChoice As
System.Web.UI.WebControls.DropDownList
Protected WithEvents btnGo As System.Web.UI.WebControls.Button
Protected WithEvents lblSelected As System.Web.UI.WebControls.Label
#End Region
End Class
[ Listing 16.15.2 ]
To test this sample, insert this user control into a new web form and load it. A
listbox should be shown with 3 options. Choose an option and click “Go”. The page
will reload, and some text below the listbox should appear saying, “This is option X.
This was generated on DATE TIME.” If you choose another option, the date and time
will change, but after returning to the original option (if you do so within 10 seconds),
the date and time will have remained the same.
The code in Listing 16.15.1 specifies that the control should be cached for 10
seconds, and the output should be varied by the control “cboChoice”. When you
choose a new value for the listbox, the properties of it are changed (specifically, the
SelectedItem-type properties). This causes a new version of the control to be cached.
However, when you return to an option that you have already chosen, the cached
version of the control can be retrieved.
Data Caching
Output and fragment caching are a great way to greatly increase the performance
of your web applications by holding entire pre-generated versions of pages to be
served to users. However, there are many cases where a greater level of detail and
control of the cache is required. There are also situations where it would be very
advantageous to be able to access the cached information programmatically. Enter
Data Caching. ASP.NET Data Caching gives you access to a Cache object that can be
used to store and retrieve pieces of information that you’d like to cache and access
programmatically. This object is an instance of the System.Web.Caching.Cache class,
and is a property of the Page object. This Cache class is extremely powerful and
contains more functionality than any 3rd party component for ASP 3 that I am aware
of. In its simplest form, you can use the object to store and retrieve values, much like
you would using the Session object. However, also included are the facilities for
adding time expirations for items in the cache, dependencies, item priority and a
delegate callback when items in the cache expire.
[ Listing 16.16 ]
Public Class DataCachingDemo
Inherits System.Web.UI.Page
Protected WithEvents lblCachedValue As
System.Web.UI.WebControls.Label
End Sub
#End Region
End Class
[ Listing 16.7 ]
If you load the page, it should display “My Value!”. Firstly, the page checks to see
if the Cache(“MyKey”) object contains anything, and if not, sets the value to “My
Value!”. Next, the page sets the text property of the label to Cache(“MyKey”), which
will retrieve the value stored under “MyKey” in the data cache.
There are two important pieces of information that I haven’t yet told you. Firstly,
as could be logically expected, the Cache object persists over multiple users. In other
words, if you store a value in the cache once, all other sessions will be able to access
that variable. Therefore, in that regard, the Cache object is very similar to using
Application variables. Secondly, unlike with Session variables, it is perfectly
acceptable to store objects in the cache. With session variables, storing objects could
possibly cause excessive strain on the server, since a new set of session variables is
created for every single user. However, with the Cache object, as with the Application
object, there’s only one set of variables. As such, the actual syntax for simply storing
values in the data cache is as follows.
Cache(“Key”) = object
You cannot only store string values in cache variables, but another other type of
object. You could, for example, quite safely store a dataset. This makes data caching
even more useful.
Table 16.4 briefly outlines the purpose of each parameter, so that you can just go
to the relevant section in the chapter if you’re looking for something specific.
Parameter Purpose
key The key used to index the cache item by
value The object to be stored in the cache
dependencies The files or other cache keys that this item relies on – if they change, the
cache item must be refreshed.
absoluteExpiration The absolute date and time when the item in the cache will expire and be
removed.
slidingExpiration The time between the last access of the item and when the item will
expire.
priority When the Cache object needs to remove objects, it uses this value to
determine how important each item is
priorityDecay How quickly an item’s priority will decay.
onRemoveCallback A delegate (or “function pointer”) that will be called when the item is
removed from cache.
[ Table 16.4 ]
Since the Add method requires all possible parameters to be passed, using it is
slightly more cumbersome:
Cache.Add("MyKey", "My Value!", Nothing, Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal,
System.Web.Caching.CacheItemPriorityDecay.Default, Nothing)
This line of code passes the key “MyKey” and value “My Value!”, and then
proceeds to pass the values that Cache.Insert assumes as defaults if they are not
passed. As you can see, if would generally be easier to use the Insert method in this
case, as it obtains the same result without you having to bother completing numerous
parameters.
Dependencies
Previously one of the largest difficulties in caching was calculating when items in
the cache should be invalidated and new items retrieved. Previously a trade-off
between time delay length and necessity of up-to-date information was required.
Therefore, if a stock broking site was caching the latest prices from an XML file, they
may choose to invalidate the cache every minute, as it is essential for the stock quotes
to be kept relevant, whilst a news site may only update the cache of its latest stories
every 30 minutes, since it’s headlines are unlikely to change largely within a 30
minute period. Wouldn’t it be great if those sites could simply detect when the XML
file holding the data changed, and only update the cache then? That way there would
be no wasted updates (updates to the cache where nothing has changed), and there
would be no delayed information, since as soon as the file changes, the cache is
updated – no waiting for the cache expiration timeout to complete.
The 3rd parameter in the Add and Insert methods take care of this problem – the
parameter accepts an instance of the CacheDependency class, which specifies either a
file, an array of files or an array of files and an array of other Cache keys on which the
current entry is dependant. If any of the files or keys specified are modified, the item
in the cache is invalidated (it is deleted). Listing 16.18 shows an example of creating a
CacheDependency object and specify a file on which the key must be dependant.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
If Cache("DependentKey") = Nothing Then
Cache.Insert("DependentKey", DateTime.Now.ToString("G"), New
System.Web.Caching.CacheDependency(Server.MapPath("datafile.xml")))
End If
lblCachedValue.Text = Cache("DependentKey")
End Sub
[ Listing 16.18 ]
To try this out for yourself, you need a web form with a label Web Control
“lblCachedValue”, and a file “datafile.xml” in the same directory as the ASP.NET
page. When you load the page for the first time, the current date and time should be
displayed. Since the value is cached, if you reload the page, the date and time value
will remain the same. However, if you open the “datafile.xml” file, edit it and save it,
then refresh the page, you will see that a new value has been cached.
It is also possible to cause a cache key to be dependent on more than one file. This
is achieved using the 2nd overloaded constructor of the CacheDependency class,
which takes an array of strings containing the names of files as its only parameter.
Continuing from the previous example, you’ll need to add another file,
“datafile2.xml” to the application’s directory before testing out the code in Listing
16.19.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
Dim arrDependentFiles() As String = {Server.MapPath("datafile.xml"),
Server.MapPath("datafile2.xml")}
If Cache("DependentKey") = Nothing Then
Cache.Insert("DependentKey", DateTime.Now.ToString("G"), New
System.Web.Caching.CacheDependency(arrDependentFiles))
End If
lblCachedValue.Text = Cache("DependentKey")
End Sub
[ Listing 16.19 ]
If either one of the files “datafile.xml” or “datafile2.xml” are edited, then the
“MyKey” cached entry is invalidated. Again, you can test this out by loading the page
and modifying either file, then reloading the page to see that the cache entry’s value
has been updated.
XML files have been used in these examples to demonstrate how dependencies can
be used with data caching because the area where this functionality is bound to be
used the most is with database files. XML databases are probably most suited to this
application, since their “tables” are generally separated into different physical files,
but dependencies could also be used to invalidate the cache based on other
database files, such as Microsoft Access. However, since all the tables are included
in one file, this may become impractical.
[ Listing 16.20 ]
This really is a trivial example, but it demonstrates at least one important possible
pitfall when using cache key dependencies. The If blocks dealing with the Cache item
“KeyToDependOn” check whether the current second is an even or an odd number. If
event, “KeyToDependOn” should store True, and if not, False. You may then
observe, “Why don’t we just do this then?”
If DateTime.Now.Second Mod 2 = 0 Then
Cache("KeyToDependOn") = True
Else
Cache("KeyToDependOn") = False
End If
The reason is that every time that code is run, a value is always assigned to
Cache(“KeyToDependOn”) – either True or False. It doesn’t matter whether the key
originally stored the value, it is simply assigned. The problem caused by this is that
the cache key dependency functionality assumes that an assignment to a cache key
means that it’s content is changing. Therefore, if the If blocks handling the
“KeyToDependOn” logic were replaced with this one here, the “DependentKey” item
would be updated every time the page was loaded.
With that matter clearup, you can now run the sample. Take note of the time that
is displayed and remember whether the seconds are even or odd. When you reload the
page, if the time remains the same, you can safely assume that the current second’s
even-or-odd status was the same as the previous refresh. However, if the time
changed, it obviously was not.
You’re unlikely to ever actually do anything as trivial as this in a real application,
but the principles hold true for all scenarios. For example, if you’re using a key to
store that last date that a database table was changed, which other keys will depend
on, then you can’t simply load and assign the date and time of the most recent record
to the cache item – you must first check if it is different, then, if it is, do the
assignment.
Expirations
Absolute Expirations
[ Listing 16.21 ]
When the page is loaded, a message is display informing you that in a minute’s
time, the key will be invalidated. If you refresh the page within the minute, you will
see that the value is, of course, still cached. However, once the minute has elapsed,
the key will have been invalidated and refreshing the page will reload the key (and the
absolute expiration value) with a new time, one minute from the current.
The .NET documentation provides more detail, but the DateTime class provides
numerous methods for adding time to the value, and these include AddDays,
AddHours, AddMinutes, AddMonths, AddSeconds and AddYears.
Sliding Expirations
Sliding Expirations differ from Absolute Expirations, in that they allow not for a
specific date and time to be set, but an amount of time from the last request to a cache
item before it is invalidated. In other words, you can specify a time span of 10
seconds for the parameter in the Insert method, and 10 seconds after the last request
for the item from the cache, the item will be invalidated.
Sliding Expirations may at first appear pointless, since a seemingly identical result can
be obtained using Absolute Expirations by setting a value that is for example, the
current time plus 10 minutes. However, setting this value and setting a sliding
expiration value of 10 minutes produce entirely different results – the absolute
expiration invalidates the cache 10 minutes from when the item was created. The
sliding expiration invalidates the cache 10 minutes from when the cache was last
accessed.
Listing 16.22 shows the use of sliding expirations, with a value of 10 seconds.
Therefore, 10 seconds after the last time the entry is accessed, it will be invalidated.
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
If Cache("SlidingExpirationKey") = Nothing Then
Cache.Insert("SlidingExpirationKey", DateTime.Now.ToString("G"),
Nothing, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(10))
End If
lblCachedValue.Text = Cache("SlidingExpirationKey")
End Sub
[ Listing 16.22 ]
Besides showing the usage of Sliding Expirations, this example can also be used
to demonstrate the important difference between Absolute and Sliding Expirations, as
mentioned in the note above. You may wish to repeat the instructions below using an
Absolute Expiration with a value of 10 seconds from the current time as well, just to
be sure that it actually works as I’ve described it.
1. Load the page.
2. Take note of the time.
3. Wait 5 seconds, then reload the page.
4. Wait until 11 seconds has elapsed since the time of the first load, then
reload the page again. If this was an absolute expiration, the item would’ve
been invalidated, and therefore the time would’ve changed. However,
since the last time the cache item was accessed was in your 2nd load, the
cache would only invalidate 15 seconds after the 1st load.
5. Take note of the time from the system clock (not the page, since the value
is still cached).
6. Wait until 11 seconds has elapsed since this time, then reload the page –
you’ll notice that a new cache value has been entered, since the previous
one was invalidated.
[ Listing 16.23 ]
Notice that there is no overloaded version of Insert that can accept only the key,
value and priority. The overloaded version that supports cache item priorities is as
follows.
Overloads Public Sub Insert( _
ByVal key As String, _
ByVal value As Object, _
ByVal dependencies As CacheDependency, _
ByVal absoluteExpiration As DateTime, _
ByVal slidingExpiration As TimeSpan, _
ByVal priority As CacheItemPriority, _
ByVal priorityDecay As CacheItemPriorityDecay, _
ByVal onRemoveCallback As CacheItemRemovedCallback _
)
This means that even if you only want to specify a few of those parameters,
including a priority, you’ll need to provide suitable blank values for the rest, as was
shown in Listing 16.23.
Priority Decay
The Insert and Add methods also provide a parameter called “priorityDecay”. This
can be particularly useful when used in conjunction with the “priority” parameter in
sites that have a high volume of traffic, and allows you to specify how quickly an
item’s priority will be downgraded if it is not accessed frequently. For example, if an
item has a high priority and a priority decay value of “Slow”, then the item is highly
unlikely to be removed from the cache, even if it is accessed infrequently, as its
priority will only be downgraded slowly.
Table 16.6 shows the members of the CacheItemPriorityDecay enumeration, from
which the priorityDecay parameter value is obtained.
Value Description
Fast The priority of the item is downgraded the fastest.
Medium This is the default. The priority of the item is downgraded faster than
“Slow” but slower than “Fast”.
Slow The priority of the item is downgraded the slowest.
Never The priority of the item is never downgraded.
[ Listing 16.24 ]
This code adds a cache item that has high priority and a slow rate of decay if
infrequently accessed.
[ Listing 16.25 ]
The onRemove variable is of type CacheItemRemovedCallback. This is a
delegate, and a member of the Caching namespace. The definition of this delegate in
the namespace specifies what parameters the method that the delegate points to must
have, in this case, key, value and reason. Effectively, onRemove is a variable that
points to a method with 3 parameters of type “String”, “Object” and
“CacheItemRemovedReason” respectively.
In the following line, an item is added to the cache. There is nothing new, except
for the inclusion of a value for the CacheItemRemovedCallback parameter,
“onRemove”. This tells ASP.NET to call the method that our onRemove delegate
points to when this particular item is removed from the cache. In the very next line,
the item is removed from the cache, which will result in the method
“CacheItemRemoved” being called.
To test this code, you simply need a blank web form, with a label named
“lblMessage” on it, and the code in Listing 16.25 in the code behind. The page should
output the message “The item was removed.” when you load it.
Practical Callbacks
It’s very unlikely that you’re going to remove an item from the cache immediately
after adding it, and in any case, if you did, you wouldn’t need to use a callback to
write code to execute when the item was removed. The major advantage of callbacks
is that you can have code being called when an item is removed, even when nobody is
currently requesting the page where the callback was declared and the method is
located – it all happens in the background. One of the side effects of this fact is that
unless you manually remove the item immediately after adding it, you can’t directly
inform the user using a label, or the like, since it’s not guaranteed that any one
particular user will be requesting a page when the method fires. Of course, you
wouldn’t normally inform the user that an item has been removed from the cache in
the callback method – you’d normally do something along the lines of acquiring the
latest version of the data that the item stores and recreating it. But nonetheless, it is
important to realise this fact. In order to avoid this pitfall, it is necessary to store the
text that you want to output in a variable that can be accessed by all page requests,
such as an Application variable, or another Cache item. The value from this variable
can then be displayed.
The other important behaviour of callbacks that may not be expected is with
regard to the removal of items due to expiration, not direct removal using the
Remove() method. If you set an expiration value for a cache item (either Absolute or
Sliding) and the item is invalidated, the callback method will not be fired until the
minute is up (I.e. the system clock reaches the next minute). This does not apply if the
item is manually removed.
Listings 16.26 and 16.27 show the UI and code behind source code for a small
web form that can be used to demonstrate cache item removal callbacks by allowing
you to view and remove items in the cache, as well as add new items, specifying a
key, value and number of seconds from creation till expiration. The code also
provides for the user-output pitfall that has just been mentioned.
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="DataCachingDemo.aspx.vb"
Inherits="Caching.DataCachingDemo"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<title></title>
<meta content="Microsoft Visual Studio.NET 7.0"
name="GENERATOR">
<meta content="Visual Basic 7.0"
name="CODE_LANGUAGE">
<meta content="JavaScript" name="vs_defaultClientScript">
<meta content="http://schemas.microsoft.com/intellisense/ie5"
name="vs_targetSchema">
</HEAD>
<body>
<form id="frmDataCachingDemo" method="post"
runat="server">
<P>
<asp:label id="lblMessage"
runat="server"></asp:label>
</P>
<P>
<asp:ListBox id="lstItems"
runat="server"></asp:ListBox>
</P>
<P>
<asp:Button id="btnRemove" runat="server"
Text="Remove Item"></asp:Button>
<asp:Button id="btnView" runat="server"
Text="View Item"></asp:Button>
<asp:Button id="btnRefresh" runat="server"
Text="Refresh"></asp:Button>
</P>
<P>
<asp:TextBox id="txtKey" runat="server"
Width="64px" Height="24px">Key</asp:TextBox>
<asp:TextBox id="txtValue"
runat="server">Value</asp:TextBox>
<asp:TextBox id="txtAbsoluteExpiration"
runat="server" Width="31px" Height="24px">10</asp:TextBox>
<asp:Button id="btnAdd" runat="server"
Text="Add Item"></asp:Button>
</P>
</form>
</body>
</HTML>
[ Listing 16.26 ]
Public Class DataCachingDemo
Inherits System.Web.UI.Page
Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
Protected WithEvents lstItems As System.Web.UI.WebControls.ListBox
Protected WithEvents btnRemove As System.Web.UI.WebControls.Button
Protected WithEvents txtKey As System.Web.UI.WebControls.TextBox
Protected WithEvents btnAdd As System.Web.UI.WebControls.Button
Protected WithEvents btnView As System.Web.UI.WebControls.Button
Protected WithEvents txtAbsoluteExpiration As
System.Web.UI.WebControls.TextBox
Protected WithEvents btnRefresh As System.Web.UI.WebControls.Button
Protected WithEvents txtValue As System.Web.UI.WebControls.TextBox
End Sub
#End Region
End Class
[ Listing 16.27 ]
Once you’ve loaded the form, you can then add, remove and view items. The 1st
textbox is for entering a key, the 2nd is the value and the 3rd is the number of seconds
after the item has been created that it will expire. Once an item has been removed,
either by expiration or by manually removing it, a CallbackMessage item will appear
in the list of items in the cache. If you view this, a message will be displayed
informing you which item was removed, what its value was and why it was removed.
The form may look something like figure 16.3.
[ Figure 16.3 – Web Form using code from listings 16.26 and 16.27 ]
The RefreshList() method demonstrates how to loop through all the items in the
cache using a For Each loop. The If condition in this method checking for key’s
starting with “System.” and “ISAPI” is to prevent all the cache items that the system
creates from being shown in the listbox.
The CacheItemRemoved() method, which is that the delegate points to, shows
how to use the parameters that are passed to it, providing you with the key name and
value of the former item, as well as the reason for removal.
The Add button’s click event handler adds items to the cache in the same manner
as with the first callback example in Listing 16.25. It uses the user-specified key name
and value, has no dependencies, provides an expiration value that will invalidate the
key in the number of seconds specified by the user, has default priority and
priorityDecay values and specifies a delegate pointing to the CacheItemRemoved()
method for the onRemoveCallback parameter.
Conclusion
ASP.NET provides an extremely comprehensive set of functionality that should
cater for all caching scenarios. For simpler, more immediate results, output caching
can be used to cache pregenerated versions of pages that are regenerated after certain
time intervals, and multiple versions of a single page can be cached to allow for
numerous different scenarios. Data caching provides an advanced object where items
can be stored in memory, rather than requiring roundtrips to database servers or even
the local filesystem for each request. This programmatically accessible facility
provides greater control over cached items than output caching, but is generally more
difficult to implement and can’t be added as an afterthought, as output caching can.