Components enable
you to reuse application logic across multiple pages or even across
multiple applications. For example, you can write a method named
GetProducts() once and use the method in all the pages in your website.
By taking advantage of components, you can make your applications easier
to maintain and extend. For simple applications, there is no reason to
take advantage of components. However, as soon as your application
contains more than a few pages, you’ll discover that you are repeating
the same work over and over again. Whenever you discover that you need
to write the same method more than once, you should immediately rip the
method out of your page and add the method to a component.
In classic
ASP, programmers often used massive and difficult to maintain #INCLUDE
files to create libraries of reusable subroutines and functions. In
ASP.NET, you use components to build these libraries.
In this chapter,
you learn how to build components in the .NET Framework. First, you are
provided with an overview of writing components: You learn how to create
simple components and use them in the pages in your application. In
particular, you learn how to define component methods, properties, and
constructors. You also learn how to take advantage of overloading,
inheritance, and interfaces.
Next, you
learn how to build component libraries that can be shared across
multiple applications. Different methods of compiling a set of
components into assemblies are examined. You also learn how you can add a
component library to the Global Assembly Cache.
Finally,
architectural issues involved in using components are discussed. The
final section of this chapter shows you how to build a simple
three-tiered application that is divided into distinct User Interface,
Business Logic, and Data Access layers.
Let’s clarify the terminology. In this book, I use the word component as a synonym for the word class. Furthermore, by the word object, I mean an instance of a class. I am ignoring a special meaning for the word component in
the .NET Framework. Technically, a component is a class that implements
the System.ComponentModel.IComponent interface. I am ignoring this
special meaning of the word component in favor of the common language use of the word.
Building Basic Components
Let’s start by building a super simple component. The HelloWorld component is contained in Listing
public class HelloWorld
{
public string SayMessage()
{
return “Hello World!”;
}
}
The HelloWorld component consists of a single method named SayMessage() which returns the string Hello World!.
When using
Visual Web Developer, you create a component by selecting the menu
option Website, Add New Item, and then selecting the Class item. The
first time you add a component to a project, Visual Web Developer
prompts you to create a new folder named App_Code. You want your new
component to be added to this folder.
Components and Dynamic Compilation
You
are not required to explicitly compile (build) the component because
the ASP.NET Framework automatically compiles the component for you. Any
component that you add to the App_Code folder is compiled dynamically in
the same way as an ASP.NET page. If you add a new component to the
App_Code folder and request any page from your website, the contents of
the App_Code folder are compiled into a new assembly and saved to the
Temporary ASP.NET Files folder, located at the following path:
“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\[application name]”
Whenever
you modify the component, the existing assembly in the Temporary ASP.NET
Files folder is deleted. The App_Code folder is compiled again when you
make a new page request.
An assembly
is the dll file (or dll files) in which components are stored. You can
add as many subfolders to the App_Code folder as you need to organize
your components. The ASP.NET Framework finds your component no matter
how deeply you nest the component in a subfolder.
One
significant drawback of this process of dynamic compilation is that any
errors in any component contained in the App_Code folder prevent any
pages from executing. Even if a page does not use a particular
component, any syntax errors in the component raise an exception when
you request the page.
If a
component contains an error, and you want to temporarily hide the
component from the ASP.NET Framework, change the file extension to an
extension that the ASP.NET Framework does not recognize, such as
HelloWorld.cs.exclude. Visual Web Developer uses this method to hide a
component when you right-click a component and select the menu option
Exclude From Project.
Mixing Different Language Components in the App_Code Folder
You
don’t have to do anything special, just as long as all the components
in the App_Code folder are written in the same language. For example, if
you use Visual Basic .NET to create all your components, then the
ASP.NET Framework automatically infers the language of your components
and everything just works.
However, if
you mix components written in more than one language in the App_Code
folder—for example, Visual Basic .NET, and C#—then you must perform some
extra steps. First, you need to place components written in different
languages in different subfolders. You can name the subfolders anything
you want. The point is to not mix different language components in the
same folder. Furthermore, you need to modify your web configuration file
to recognize the different subfolders. For example, if you create two
subfolders in the App_Code folder named VBCode and CSCode, then you can
use the web configuration file to use components written in both VB.NET
and C#.
When the
contents of the App_Code folder are compiled, two assemblies are
created: one that corresponds to the VBCode folder and one that
corresponds to the CSCode folder. Notice that you don’t need to indicate
the language used for each folder—the ASP.NET Framework infers the
language for you. There is nothing wrong with mixing components written
in different languages in the same ASP.NET page. After a component is
compiled, the .NET Framework treats VB.NET and C# components in the same
way.
Declaring Methods
The
simple HelloWorld component in Listing contains a single method named
SayMessage(), which returns a string value. When writing components with
Visual Basic .NET, you create methods by creating either a subroutine
or a function. Use a subroutine when a method does not return a value,
and use a function when a method does return a value.
The
SayMessage() method is an instance method. In other words, you must
create a new instance of the HelloWorld class before you can call the
SayMessage(), method like this:
HelloWorld objHelloWorld = new HelloWorld();
lblMessage.Text = objHelloWorld.SayMessage();
In the
first line, a new instance of the HelloWorld component is created. The
SayMessage() method is called from this instance. For this reason, the
SayMessage() method is an instance method. As an alternative to creating
an instance method, you can create a static method. The advantage of a
static method is that you do not need to create an instance of a
component before calling it. For example, the SayMessage() method in the
modified HelloWorld component is a static method. Static methods are
called shared methods in Visual Basic .NET.
Declaring Fields and Properties
You
can define a property for a component in two ways: the lazy way and the
virtuous way. The lazy way to create a property is to create a public
field. If you declare any field with the Public access modifier, then
the field can be accessed from outside the component.
There are a
couple of serious disadvantages to creating properties by creating
public fields. First, the .NET Framework recognizes properties as
separate entities. Several methods in the .NET Framework recognize
properties but not fields. For example, you can refer to component
properties and not fields when using the Eval() method in a databinding
expression. If you want to bind a collection of Product objects to a
GridView control, then you should expose the properties of the Product
component as true properties and not as fields.
The other
disadvantage of fields is that they do not provide you with a chance to
validate the value being assigned to the field. For example, imagine
that a property represents a database column and the column accepts no
more than five characters. In that case, you should check whether the
value being assigned to the property is less than five characters. The
component in Listing 15.8 uses a property instead of a field. (It does
things the virtuous way.)
The version of C# included with the .NET Framework 3.5 supports a new feature named automatic properties.
Automatic properties provide you with a shorthand syntax for creating a
property with a backing field. To learn more about automatic
properties, “Data Access with LINQ to SQL.”
Declaring Constructors
A
constructor is a special class method that is called automatically when
you create a new instance of a class. Typically, you use the
constructor to initialize private fields contained in the class. When
creating a constructor in C#, you create a method with the same name as
the class name.
You can
create static constructors by using the static keyword when declaring a
constructor. A static constructor is called once before any instance
constructors.
Overloading Methods and Constructors
When
a method is overloaded, a component contains two methods with exactly
the same name. Many methods in the .NET Framework are overloaded,
including the String.Replace() method, the Random.Next() method, and the
Page.FindControl() method.
For example, here is a list of the three overloaded versions of the Random.Next() method:
- Next()—Returns a random number between 0 and 2,147,483,647.
- Next(upperbound)—Returns a number between 0 and the upper bound.
- Next(lowerbound, upperbound)—Returns a number between the lower bound and the upper bound.
Because
all three methods do the same thing—they all return a random number—it
makes sense to overload the Next() method. The methods differ only in
their signatures. A method signature consists of the order and
type of parameters that a method accepts. For example, you can’t
overload two methods that have exactly the same set of parameters (even
if the names of the parameters differ). Overloading is useful when you
want to associate related methods. Overloading is also useful when you
want to provide default values for parameters.
When typing
an overloaded method in Source view, the Intellisense pops up with all
the different sets of parameters that you can use with the overloaded
method.
Declaring Namespaces
A
namespace enables you to group logically related classes. You are not
required to provide a class with a namespace. To this point, all the
components you have seen created have been members of the global
namespace. However, several advantages result from grouping components
into namespaces.
First,
namespaces prevent naming collisions. If two companies produce a
component with the same name, then namespaces provide you with a method
of distinguishing the components.
Second,
namespaces make it easier to understand the purpose of a class. If you
group all your data access components into a DataAccess namespace and
all your business logic components in a BusinessLogic namespace, then
you can immediately understand the function of a particular class.
In an ASP.NET page, you import a namespace like this:
<%@ Import Namespace=”System.Collections” %>
In a C# component, on the hand, you import a namespace like this:
using System.Collections;
You can create your own custom namespaces and group your components into namespaces by using the namespace statement.
Creating Partial Classes
You can define a single component that spans multiple files by taking advantage of a feature of the .NET Framework called partial classes.
Partial
classes are the basis for code-behind pages in the ASP.NET Framework.
The code-behind file and the presentation page are two partial classes
that get compiled into the same class.
Inheritance and Abstract Classes
When
one class inherits from a second class, the inherited class
automatically includes all the nonprivate methods and properties of its
parent class. In other words, what’s true of the parent is true of the
child, but not the other way around. Inheritance is used throughout the
.NET Framework. For example, every ASP.NET page inherits from the base
System.Web.UI.Page class. The only reason that you can use properties
such as the IsPostback property in an ASP.NET page is that the page
derives from the base Page class.
All classes
in the .NET Framework derive from the base System.Object class. The
Object class is the great-grandmother of every other class. This means
that any methods or properties of the Object class, such as the
ToString() method, are shared by all classes in the Framework. You can
take advantage of inheritance when building your own components. You
indicate that one class inherits from a second class when you declare a
class.
Declaring Interfaces
An
interface is a list of properties and methods that a class must
implement. If a class implements an interface, then you know that the
class includes all the properties and methods contained in the
interface.
Interfaces
are similar to abstract classes with two important differences. First, a
component can inherit from only one class. On the other hand, a
component can implement many different interfaces.
Second, an
abstract class can contain application logic. You can add methods to an
absract class that all derived classes inherit and can use. An
interface, on the other hand, cannot contain any logic. An interface is
nothing more than a list of methods and properties.
Using Access Modifiers
C# supports the following access modifiers, which you can use when declaring a class, method, or property:
- Public—A public class, method, or property has no access restrictions.
- Protected—A protected method or property can be accessed only within the class itself or a derived class.
- Internal—An internal class, method, or property can be accessed only by a component within the same assembly (dll file). Because ASP.NET pages are compiled into different assemblies than the contents of the App_Code folder, you cannot access an internal member of a class outside of the App_Code folder.
- Private—A private class, method, or property can be accessed only within the class itself.
Visual Basic .NET supports the following access modifiers (also called access levels), which you can use when declaring a class, method, or property:
- Public—A Public class, method, or property has no access restrictions.
- Protected—A Protected method or property can be accessed only within the class itself or a derived class.
- Friend—A Friend class, method, or property can be accessed only by a component within the same assembly (dll file). Because ASP.NET pages are compiled into different assemblies than the contents of the App_Code folder, you cannot access a Friend member of a class outside of the App_Code folder.
- Protected Friend—A Protected Friend method or property can be accessed within the class itself or a derived class, or any other class located in the same assembly.
- Private—A Private class, method, or property can be accessed only within the class itself.
Intellisense and Components
Visual
Web Developer automatically pops up with Intellisense when you type the
names of classes, properties, or methods in Source view. You can add
comments that appear in Intellisense to your custom components to make
it easier for other developers to use your components.
You can
generate an XML documentation file—a file that contains all the XML
comments— for the components contained in a folder by using the /doc
switch with the C# or Visual Basic command-line compiler. The
command-line compiler is discussed in the second part of this chapter,
“Building Component Libraries.”
Using ASP.NET Intrinsics in a Component
When
you add code to an ASP.NET page, you are adding code to an instance of
the Page class. The Page class exposes several ASP.NET intrinsic objects
such as the Request, Response, Cache, Session, and Trace objects. If
you want to use these objects within a component, then you need to do a
little more work. Realize that when you create a component, you are not
creating an ASP.NET component. In this chapter, we are creating .NET
components, and a .NET component can be used by any type of .NET
application, including a Console application or Windows Forms
application.
Building Component Libraries
One
of the advertised benefits of using components is code reuse. You write
a method once, and then you never need to write the same method ever
again. One problem with the components that have been created to this
point is that they have all been application specific. In other words,
you cannot reuse the components across multiple websites without copying
all the source code from one App_Code folder to another. If you want to
share components among multiple websites, then you can no longer take
advantage of dynamic compilation. To share components, you need to
compile the components explicitly in a separate assembly.
Compiling Component Libraries
You can use a number of methods to compile a set of components into an assembly:
- Use the command-line compiler.
- Use C# or Visual Basic Express.
- Use the full version of Visual Studio 2008.
Using the C#
Command-Line Compiler You can use the C# or Visual Basic command-line
compiler to compile a source code file, or set of source code files,
into an assembly. The C# compiler is located at the following path:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe
The Visual Basic command-line compiler is located at the following path:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\vbc.exe
If you have
installed the .NET Framework SDK, then you can open the SDK Command
Prompt from the Microsoft .NET Framework SDK program group. When the
command prompt opens, the paths to the C# and Visual Basic .NET compiler
are added to the environment automatically.
You can use the csc.exe tool to compile any C# source file like this:
csc /t:library SomeFile.cs
The /t
(target) option causes the compiler to create a component library and
not a Console or Windows application. When you execute this command, a
new file named SomeFile.dll is created, which is the compiled assembly.
As an alternative to compiling a single file, you can compile all the
source code files in a folder (and every subfolder) like this:
csc /t:library /recurse:*.cs /out:MyLibrary.dll
Using Visual C# Express
You
can download a trial edition of Visual C# Express from the MSDN website
(http://msdn.microsoft.com). Visual C# Express enables you to build
Windows applications, Console applications, and class libraries.
To create a
class library that you can use with an ASP.NET application, you create a
Class Library project in Visual C# Express (see Figure 15.7). When you
build the project, a new assembly is created. If you need to use ASP.NET
classes in your class library, such as the HttpContext class, then you
need to add a reference to the System.Web.dll assembly to your Class
Library project. Select the menu option Project, Add Reference and add
the System.Web.dll from beneath the .NET tab
Using Visual Studio 2008
The
easiest way to create a class library that you can share among multiple
ASP.NET applications is to use the full version of Visual Studio 2008
instead of Visual Web Developer. Visual Studio 2008 was designed to
enable you to easily build enterprise applications. Building class
libraries is one of the features you get in Visual Studio 2008 that you
don’t get in Visual Web Developer Express.
Visual
Studio 2008 enables you to add multiple projects to a single solution.
For example, you can add both an ASP.NET project and a Class Library
project to the same solution. When you update the Class Library project,
the ASP.NET project is updated automatically.
Adding a Reference to a Class Library
Now
that you understand how you can create a class library in a separate
assembly, you need to know how you can use this class library in another
project. In other words, how do you use the components contained in an
assembly within an ASP.NET page?
There are
two ways to make an assembly available to an ASP.NET application. You
can add the assembly to the application’s /Bin folder or you can add the
assembly to the Global Assembly Cache. Adding an Assembly to the Bin
Folder In general, the best way to use an assembly in an ASP.NET
application is to add the assembly to the application’s root Bin folder.
There is nothing magical about this folder. The ASP.NET Framework
automatically checks this folder for any assemblies. If the folder
contains an assembly, the assembly is referenced automatically by the
ASP.NET application when it is compiled dynamically. If you are using
Visual Web Developer, then you can select the menu option Website, Add
Reference
to add a new assembly to your application’s Bin folder. Alternatively,
you can simply copy an assembly into this folder.
Adding an Assembly to the Global Assembly Cache
All
the assemblies that make up the .NET Framework class library are
contained in the Global Assembly Cache. For example, the Random class is
located in the System.dll assembly, and the System.dll assembly is
contained in the Global Assembly Cache. Any assembly located in the
Global Assembly Cache can be referenced by any application running on a
server.
The Global Assembly Cache’s physical location is at the following path:
C:\WINDOWS\assembly
Before
you can add an assembly to the Global Assembly Cache, you must add a
strong name to the assembly. A strong name is similar to a GUID. You use
a strong name to provide your assembly with a universally unique
identifier.
Technically,
a strong name consists of the name, version number, and culture of the
assembly. A strong name also includes the public key from a
public/private key pair. Finally, a strong name includes a hash of the
assembly’s contents so that you know whether the assembly has been
modified.
You can generate a strong name by using the sn.exe command-line tool like this:
sn.exe -k KeyPair.snk
Executing this command creates a new file named KeyPair.snk, which includes a new random public/private key pair.
Protect
your key file. You should not reveal the private key to anyone. You can
compile an assembly that includes a strong name by executing the Visual
Basic
.NET command-line compiler like this:
csc /t:library /keyfile:KeyPair.snk /recurse:*.cs /out:MyLibrary.dll
The
resulting assembly is strongly named with the public key from the
KeyPair.snk file. The /keyfile option associates the key file with the
assembly. In this case, the name of the resulting assembly is
MyLibrary.dll.
An
alternative method of associating a strong name with an assembly is to
use the <Assembly: AssemblyKeyFile> attribute. You can add this
attribute to any of the source files that get compiled into the
assembly. For example, you can drop the file in
When
using Visual C# Express or Visual Studio 2008, you can create a strong
name automatically and associate the strong name with an assembly.
Right-click the name of your project in the Solution Explorer window and
select Properties. Next, select the Signing tab.
Creating the User Interface Layer
The
User Interface layer is contained. Notice that the User Interface layer
consists of a single ASP.NET page. This page contains no code
whatsoever.
Creating the Business Logic Layer
The
ASP.NET pages in your application should contain a minimum amount of
code. All your application logic should be pushed into separate
components contained in either the Business Logic or Data Access layers.
Your ASP.NET pages should not communicate directly with the Data Access
layer. Instead, the pages should call the methods contained in the
Business Logic layer.
Creating the Data Access Layer
The
Data Access layer contains all the specialized code for interacting with a database. The Data Access layer consists of the single component.
(A real-world application might contain dozens or even hundreds of
components in its Data Access layer.)
The
SqlDataAccessLayer component has four public methods:
ProductSelectAll(), ProductInsert(), ProductUpdate(), and
ProductDelete(). These methods use the ADO.NET classes from the
System.Data.SqlClient namespace to communicate with Microsoft SQL
Server.
Notice
that the SqlDataAccessLayer component is not completely isolated from
the components in the Business Logic Layer. The ProductSelectAll()
method builds a collection of Product objects, which the method returns
to the Business Logic layer. You should strive to isolate each layer as
much as possible. However, in some cases, you cannot completely avoid
mixing objects from different layers.