Chapter 15. Building Components

<feature>

IN THIS CHAPTER

</feature>

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.

Classic ASP Note

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.

Note

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 15.1.

Example 15.1. HelloWorld.cs

public class HelloWorld
{
    public string SayMessage()
    {
        return "Hello World!";
    }
}

Visual Web Developer Note

When using Visual Web Developer, you create a component by selecting the menu option Website, Add New Item, and then selecting the Class item (see Figure 15.1). 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.

Creating a new component with Visual Web Developer.

Figure 15.1. Creating a new component with Visual Web Developer.

The HelloWorld component consists of a single method named SayMessage() which returns the string Hello World!.

Make sure that you save the HelloWorld.cs file to your application’s App_Code folder. If you don’t save the component to this folder, then you won’t be able to use the component in your pages.

Next, you need to create a page that uses the new component. This page is contained in Listing 15.2.

Example 15.2. ShowHelloWorld.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    void Page_Load()
    {
        HelloWorld objHelloWorld = new HelloWorld();
        lblMessage.Text = objHelloWorld.SayMessage();
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Hello World</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In the Page_Load() event handler, an instance of the HelloWorld component is created. Next, the result returned by a call to the SayMessage() method is assigned to a Label control. When you open the page in your browser, you’ll see the message Hello World!.

Notice how simple this process of creating the component is. You don’t need to perform any special registration and you don’t need to compile anything explicitly. Everything just works magically.

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:WINDOWSMicrosoft.NETFrameworkv2.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.

Note

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.

Tip

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 in Listing 15.3 to use components written in both VB.NET and C#.

Example 15.3. Web.Config

<configuration>
  <system.web>
    <compilation>
    <codeSubDirectories>
      <add directoryName="VBCode" />
      <add directoryName="CSCode" />
    </codeSubDirectories>
    </compilation>
  </system.web>
</configuration>

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 15.1 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 in Listing 15.1 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 in Listing 15.4 is a static method.

Note

Static methods are called shared methods in Visual Basic .NET.

Example 15.4. StaticHelloWorld.cs

public class StaticHelloWorld
{
    public static string SayMessage()
    {
        return "Hello World!";
    }
}

The StaticHelloWorld component defined in Listing 15.3 is exactly the same as the HelloWorld component created in Listing 15.1 with one change: The SayMessage() method includes a static modifier.

The page in Listing 15.5 uses the StaticHelloWorld component to display the Hello World! message.

Example 15.5. ShowStaticHelloWorld.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    void Page_Load()
    {
        lblMessage.Text = StaticHelloWorld.SayMessage();
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Shared Hello World</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Notice that the page in Listing 15.5 does not create an instance of the StaticHelloWorld component. The SayMessage() method is called directly from the StaticHelloWorld class.

The advantage of using static methods is that they save you typing. You don’t have to go through the pain of instantiating a component before calling the method. Many classes in the .NET Framework include static methods. For example, the String.Format() method, the Int32.Parse() method, and the DateTime.DaysInMonth() method are all static methods.

There is nothing wrong with mixing both static and instance methods in the same component. For example, you might want to create a Product component that has a static GetProducts() method and an instance SaveProduct() method.

The one significant limitation of using a static method is that a static method cannot refer to an instance field or property. In other words, static methods must be stateless.

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.

For example, the component in Listing 15.6 contains a public field named Message.

Example 15.6. FieldHelloWorld.cs

public class FieldHelloWorld
{
    public string Message;
    public string SayMessage()
    {
        return Message;
    }
}

The Message field is declared near the top of the FieldHelloWorld class definition. Notice that the Message field is returned by the SayMessage() method.

The page in Listing 15.7 uses the FieldHelloWorld component to display a message.

Example 15.7. ShowFieldHelloWorld.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    void Page_Load()
    {
        FieldHelloWorld objFieldHelloWorld = new FieldHelloWorld();
        objFieldHelloWorld.Message = "Good Day!";
        lblMessage.Text = objFieldHelloWorld.SayMessage();
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Field Hello World</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        Runat="server" />

    </div>
    </form>
</body>
</html>

In the Page_Load() event handler in Listing 15.7, an instance of the FieldHelloWorld class is created, a value is assigned to the Message field, and the SayMessage() method is called.

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.)

Example 15.8. PropertyHelloWorld.cs

using System;


public class PropertyHelloWorld
{
    private string _message;

    public string Message
    {
        get
        {

            return _message;
        }
        set
        {

            if (value.Length > 5)
                throw new Exception("Message too long!");
            _message = value;
        }
    }

    public string SayMessage()
    {

        return _message;
    }
}

Notice that the component in Listing 15.8 contains a property named Message and a private backing field named _message. The Message property contains both a getter (get) and a setter (set). The getter is called when you read the value of the Message property, and the setter is called when you assign a value to the Message property.

The getter simply returns the value of the private _message field. The setter assigns a value to the private _message field. The setter throws an exception if the length of the value being assigned to the _message field exceeds five characters.

Note

In Listing 15.8, the private field is named _message. The underscore character (_) has no programmatic significance. By convention, private members of a class are named with a leading underscore, but there is nothing wrong with following some other convention.

Note

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, see Chapter 18, “Data Access with LINQ to SQL.”

The page in Listing 15.9 uses the PropertyHelloWorld component.

Example 15.9. ShowPropertyHelloWorld.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">
    void Page_Load()
    {
        PropertyHelloWorld objPropertyHelloWorld = new PropertyHelloWorld();
        objPropertyHelloWorld.Message = "Hello World!";
        lblMessage.Text = objPropertyHelloWorld.SayMessage();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Property Hello World</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblMessage"
        Runat="server" />

    </div>
    </form>
</body>
</html>

If you open the page in Listing 15.9 in your web browser, you will get a big, fat error message (see Figure 15.2). Because a string longer than five characters is assigned to the Message property in the Page_Load() method, the Message property raises an exception.

Assigning more than five characters.

Figure 15.2. Assigning more than five characters.

You can also create read-only properties when the situation warrants it. For example, the component in Listing 15.10 returns the current server time. It would not make sense to assign a value to this property, so the property is declared as read-only.

Example 15.10. ServerTime.cs

using System;


public class ServerTime
{
    public string CurrentTime
    {
        get
        {
            return DateTime.Now.ToString();
        }
    }
}

Note

You can create static fields and properties in the same way as you create shared methods, by using the static keyword. Any value you assign to a static field or property is shared among all instances of a component.

I recommend that you avoid using static fields and properties when building ASP.NET applications. Using static fields and properties raise nasty concurrency issues in a multithreaded environment such as ASP.NET. If you insist on creating a static field or property, make it read-only.

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. For example, the class in Listing 15.11 displays a random quotation (see Figure 15.3). The collection of random quotations is created in the component’s constructor.

Displaying a random quotation.

Figure 15.3. Displaying a random quotation.

Example 15.11. Quote.cs

using System;
using System.Collections.Generic;

public class Quote
{
    private List<string> _quotes = new List<string>();

    public string GetQuote()
    {

        Random rnd = new Random();
        return _quotes[rnd.Next(_quotes.Count)];
    }

    public Quote()
    {
        _quotes.Add("All paid jobs absorb and degrade the mind — Aristotle");
        _quotes.Add("No evil can happen to a good man, either in life or after
         death — Plato");
        _quotes.Add("The only good is knowledge and the only evil is ignorance —
         Plato");
    }
}

Notice that the collection named _quotes is declared in the body of the class. That way, you can refer to the _quotes field in both the constructor and the GetQuote() method.

Note

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. For example, the StoreProduct component in Listing 15.12 contains three overloaded versions of its SaveProduct() method.

Example 15.12. StoreProduct.cs

using System;


public class StoreProduct
{
    public void SaveProduct(string name)
    {
        SaveProduct(name, 0, String.Empty);
    }

    public void SaveProduct(string name, decimal price)
    {
        SaveProduct(name, price, String.Empty);
    }

    public void SaveProduct(string name, decimal price, string description)
    {

        // Save name, price, description to database
    }
}

You can call any of the three SaveProduct() methods in Listing 15.12 to save a new product. You can supply the new product with a name, a name and a price, or a name and a price and a description.

Visual Web Developer Note

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. See Figure 15.4.

Typing an overloaded method in Visual Web Developer.

Figure 15.4. Typing an overloaded method in Visual Web Developer.

Because a constructor is just a special method, you also can use overloading when declaring constructors for a class. For example, the ProductConstructor class in Listing 15.13 contains three overloaded constructors that can be used to initialize the Product class.

Example 15.13. ProductConstructor.cs

using System;


public class ProductConstructor
{
    public ProductConstructor(string name)
        : this(name, 0, String.Empty) { }

    public ProductConstructor(string name, decimal price)
        : this(name, price, String.Empty) { }

    public ProductConstructor(string name, decimal price, string description)
    {

        // Use name, price, and description
    }
}

When you instantiate the component in Listing 15.13, you can instantiate it in any of the following ways:

ProductConstructor objProduct = new ProductConstructor("Milk");
ProductConstructor objProduct = new ProductConstructor("Milk", 2.99d);

ProductConstructor objProduct = new ProductConstructor("Milk", 2.99d, "While Milk");

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. For example, the component in Listing 15.14 is contained in the AspUnleashed.SampleCode namespace.

Example 15.14. Namespaced.cs

namespace AspNetUnleashed.SampleCode
{
    public class Namespaced
    {
        public string SaySomething()
        {
            return "Something";
        }
    }
}

The file in Listing 15.14 uses the Namespace statement to group the Namespaced component into the AspUnleashed.SampleCode namespace. Components in different files can share the same namespace, and different components in the same file can occupy different namespaces.

The periods in a namespace name have no special significance. The periods are used to break up the words in the namespace, but you could use another character, such as an underscore character, instead.

Microsoft recommends a certain naming convention when creating namespaces:

CompanyName.TechnologyName[.Feature][.Design]

So, if your company is named Acme Consulting and you are building a data access component, you might add your component to the following namespace:

AcmeConsulting.DataAccess

Of course this is simply a naming convention. No serious harm will come to you if you ignore it.

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.

For example, the files in Listings 15.15 and 15.16 contain two halves of the same component.

Example 15.15. FirstHalf.cs

public partial class Tweedle
{
    private string _message = @"THEY were standing under a tree,
        each with an arm round the other's neck, and Alice knew
        which was which in a moment, because one of them had
        ""DUM"" embroidered on his collar, and the other ""DEE"".";
}

Example 15.16. SecondHalf.cs

public partial class Tweedle
{
    public string GetMessage()
    {
        return _message;
    }
}

Notice that the private _message field is defined in the first file, but this private field is used in the GetMessage() method in the second file. When the GetMessage() method is called, it returns the value of the private field from the other class.

Both files define a class with the same name. The class declaration includes the keyword Partial. The Partial keyword marks the classes as partial classes.

Note

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.

For example, the file in Listing 15.17 includes three components: a BaseProduct class, a ComputerProduct class, and a TelevisionProduct class.

Example 15.17. Inheritance.cs

public class BaseProduct
{
    private decimal _price;

    public decimal Price
    {

        get { return _price; }
        set { _price = value; }
    }
}

public class ComputerProduct : BaseProduct
{
    private string _processor;

    public string Processor
    {

        get { return _processor; }
        set { _processor = value; }
    }

}

public class TelevisionProduct : BaseProduct
{
    private bool _isHDTV;

    public bool IsHDTV
    {
        get { return _isHDTV; }
        set { _isHDTV = value; }
    }
}

Notice that both the ComputerProduct and TelevisionProduct components inherit from the BaseProduct component. Because the BaseProduct class includes a Price property, both inherited components automatically inherit this property.

When inheriting one class from another, you also can override methods and properties of the base class. Overriding a method or property is useful when you want to modify the behavior of an existing class.

To override a property or method of a base class, the property or method must be marked with the C# virtual or abstract keyword or the Visual Basic .NET Overridable or MustOverride keyword. Only methods or properties marked with the virtual or abstract keyword can be overridden.

For example, the file in Listing 15.18 contains two components: a ProductBase class and a OnSaleProduct class. The second class inherits from the first class and overrides its Price property. The Price property of the OnSaleProduct component divides the price by half.

Example 15.18. OnSaleProduct.cs

public class ProductBase
{
    private decimal _price;

    public virtual decimal Price
    {
        get { return _price; }
        set { _price = value; }
    }
}

public class OnSaleProduct : ProductBase
{
    override public decimal Price
    {
        get { return base.Price / 2; }
        set { base.Price = value; }
    }
}

Notice that the base keyword (MyBase in Visual Basic) is used in Listing 15.18 to refer to the base class (the ProductBase class).

Finally, you can use the abstract keyword when declaring a class to mark the class as class that requires inheritance. You cannot instantiate an abstract class. To use an abstract class, you must derive a new class from the abstract class and instantiate the derived class.

Abstract classes are the foundation for the ASP.NET Provider Model. Personalization, Membership, Roles, Session State, and Site Maps all use the Provider model.

For example, the MembershipProvider class is the base class for all Membership Providers. The SqlMembershipProvider and ActiveDirectoryMembershipProvider classes both derive from the base MembershipProvider class.

Note

Chapter 23, “Using ASP.NET Membership,” discusses the MembershipProvider classes in detail. The MembershipProvider is responsible for saving and loading membership information such as application usernames and passwords.

The base MembershipProvider class is an abstract class. You cannot use this class directly in your code. Instead, you must use one of its derived classes. However, the base MembershipProvider class provides a common set of methods and properties that all MembershipProvider-derived classes inherit.

The base MembershipProvider class includes a number of methods and properties marked as abstract. A derived MembershipProvider class is required to override these properties and methods.

The file in Listing 15.19 contains two components. The first component, the BaseEmployee component, is an abstract class that contains an abstract property named Salary. The second component, the SalesEmployee, inherits the BaseEmployee component and overrides the Salary property.

Example 15.19. Employees.cs

public abstract class BaseEmployee
{
    public abstract decimal Salary
    {
        get;
    }

    public string Company
    {
        get { return "Acme Software"; }
    }
}

public class SalesEmployee : BaseEmployee
{
    public override decimal Salary
    {
        get { return 67000.23m; }
    }
}

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.

For example, the file in Listing 15.20 contains an interface named IProduct and two components named MusicProduct and BookProduct.

Example 15.20. Products.cs

public interface IProduct
{
    decimal Price
    {
        get;
    }

    void SaveProduct();
}

public class MusicProduct : IProduct
{
    public decimal Price
    {
        get { return 12.99m; }
    }

    public void SaveProduct()
    {
        // Save Music Product
    }
}

public class BookProduct : IProduct
{
    public decimal Price
    {
        get { return 23.99m; }
    }

    public void SaveProduct()
    {

        // Save Book Product
    }
}

Both components in Listing 15.17 are declared as implementing the IProduct interface. (The colon can mean implements or inherits.) Notice, furthermore, that both components include the SaveProduct() method and the Price property. Both components are required to have this method and property since they are declared as implementing the IProduct 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.

Using access modifiers is useful when you are developing a component library that might be used by other members of your development team (or you in the future). For example, you should mark all methods that you don’t want to expose from your component as private.

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.

If you add XML comments to a component, then the contents of the XML comments appear automatically in Intellisense. For example, the component in Listing 15.21 includes XML comments for its class definition, property definitions, and method definition (see Figure 15.5).

Adding comments to a component.

Figure 15.5. Adding comments to a component.

Example 15.21. Employee.cs

/// <summary>
/// Represents an employee of Acme.com
/// </summary>
public class Employee
{
    private string _firstName;
    private string _lastName;

    /// <summary>
    /// The employee first name
    /// </summary>
    public string FirstName
    {
        get { return _firstName; }
    }

    /// <summary>
    /// The employee last name
    /// </summary>
    public string LastName
    {
        get { return _lastName; }
    }

    /// <summary>
    /// Returns an employee from the database
    /// </summary>
    /// <param name="id">The unique employee identifier</param>
    /// <returns>An instance of the Employee class</returns>
    public static Employee getEmployee(int id)
    {
        return null;
    }

    /// <summary>
    /// Initializes an employee
    /// </summary>
    /// <param name="firstName">First Name</param>
    /// <param name="lastName">Last Name</param>
    public Employee(string firstName, string lastName)
    {
        _firstName = firstName;
        _lastName = lastName;
    }
}

Note

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 Components.”

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.

To use the ASP.NET instrinsics in a component, you need to get a reference to the current HtppContext. The HttpContext object is the one object that is available behind the scenes through the entire page processing lifecycle. You can access the HttpContext object from any user control, custom control, or component contained in a page.

Note

The HttpContext object includes an Items collection. You can add anything to the Items collection and share the thing among all the elements contained in a page.

To get a reference to the current HttpContext object, you can use the static (shared) Current property included in the HttpContext class. For example, the component in Listing 15.22 uses the HttpContext object to use both the Session and Trace objects.

Example 15.22. Preferences.cs

using System.Web;


public class Preferences
{
    public static string FavoriteColor
    {
        get
        {
            HttpContext context = HttpContext.Current;
            context.Trace.Warn("Getting FavoriteColor");
            if (context.Session["FavoriteColor"] == null)
                return "Blue";
            else
                return (string)context.Session["FavoriteColor"];
        }
        set
        {
            HttpContext context = HttpContext.Current;
            context.Trace.Warn("Setting FavoriteColor");
            context.Session["FavoriteColor"] = value;
        }
    }
}

The Preferences component contains a single property named FavoriteColor. The value of this property is stored in Session state. Anytime this property is modified, the Trace object writes a warning.

You can use the Preferences component in the page contained in Listing 15.23.

Example 15.23. ShowPreferences.aspx

<%@ Page Language="C#" trace="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    void Page_PreRender()
    {
        body1.Style["background-color"] = Preferences.FavoriteColor;
    }

    protected void btnSelect_Click(object sender, EventArgs e)
    {
        Preferences.FavoriteColor = ddlFavoriteColor.SelectedItem.Text;
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .content
        {
            width:80%;
            padding:20px;
            background-color:white;
        }
    </style>
    <title>Show Preferences</title>
</head>
<body id="body1" runat="server">
    <form id="form1" runat="server">
    <div class="content">

    <h1>Show Preferences</h1>

    <asp:DropDownList
        id="ddlFavoriteColor"
        Runat="server">
        <asp:ListItem Text="Blue" />
        <asp:ListItem Text="Red" />
        <asp:ListItem Text="Green" />
    </asp:DropDownList>
    <asp:Button
        id="btnSelect"
        Text="Select"
        Runat="server" OnClick="btnSelect_Click" />



    </div>
    </form>
</body>
</html>

After you open the page in Listing 15.23, you can select your favorite color from the DropDownList control. Your favorite color is stored in the Preferences object (see Figure 15.6).

Selecting a favorite color.

Figure 15.6. Selecting a favorite color.

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.

These options are explored in turn.

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:WINDOWSMicrosoft.NETFrameworkv2.0.50727csc.exe

The Visual Basic command-line compiler is located at the following path:

C:WINDOWSMicrosoft.NETFrameworkv2.0.50727vbc.exe

Note

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

The /recurse option causes the compiler to compile the contents of all the subfolders. The /out option provides a name for the resulting assembly.

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.

Creating a Class Library in C# Express.

Figure 15.7. Creating a Class Library in C# Express.

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 (see Figure 15.8).

Adding a reference to System.Web.dll.

Figure 15.8. Adding a reference to System.Web.dll.

Note

If you are a VB.NET developer, then you can download Visual Basic Express from the MSDN Website (http://msdn.microsoft.com).

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 (see Figure 15.9).

A solution that contains multiple projects.

Figure 15.9. A solution that contains multiple projects.

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 (see Figure 15.10). Alternatively, you can simply copy an assembly into this folder. (If the folder doesn’t exist, just create it.)

Adding an assembly reference with Visual Web Developer.

Figure 15.10. Adding an assembly reference with Visual Web Developer.

When you add an assembly to an ASP.NET application’s Bin folder, the assembly is scoped to the application. This means that you can add different versions of the same assembly to different applications without worrying about any conflicts.

Furthermore, if you add an assembly to the Bin folder, then you can take advantage of XCopy deployment. In other words, if you need to move your website to a new server, then you can simply copy all the files in your website from one server to another. As long as you copy your Bin folder, the assembly is available at the new location.

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:WINDOWSassembly

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.

Note

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.

Warning

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 Listing 15.24 into the folder that you are compiling and it associates the public key from the KeyPair.snk file with the compiled assembly.

Example 15.24. AssemblyInfo.cs

using System.Reflection;


[assembly:AssemblyKeyFile("KeyPair.snk")]
[assembly:AssemblyVersion("0.0.0.0")]

The file in Listing 15.24 actually includes two attributes. The first attribute associates the KeyPair.snk public key with the assembly. The second attribute associates a version number with the assembly. The version number consists of four sets of numbers: the major version, minor version, build number, and revision number.

After you add the file in Listing 15.24 to a folder that contains the source code for your components, use the following command to compile the folder:

csc /t:library /recurse:*.cs /out:MyLibrary.dll

After you associate a strong name with an assembly, you can use the GacUtil.exe command-line tool to add the assembly to the Global Assembly Cache. Executing the following statement from a command prompt adds the MyLibrary.dll assembly to the Global Assembly Cache:

GacUtil.exe /i MyLibrary.dll

You can verify that the MyLibrary.dll assembly has been added successfully to the Global Assembly Cache by opening your Global Assembly Cache folder located at the following path:

C:WINDOWSassembly

You should see the MyLibrary.dll assembly listed in the Assembly Name column (see Figure 15.11). Note the Version and the PublicKeyToken columns. You need to know the values of these columns to use the assembly in an application.

Viewing the Global Assembly Cache.

Figure 15.11. Viewing the Global Assembly Cache.

After you install an assembly in the Global Assembly Cache, you can use the assembly in your ASP.NET Pages and App_Code components by adding a reference to the assembly in your web configuration file. The web configuration file in Listing 15.25 adds the MyLibrary.dll assembly to your application.

Example 15.25. Web.Config

<configuration>
  <system.web>
    <compilation>
      <assemblies>
        <add assembly="MyLibrary,Version=0.0.0.0,Culture=neutral,
          PublicKeyToken=250c66fc9dd31989"/>
      </assemblies>
    </compilation>
  </system.web>
</configuration>

The web configuration file in Listing 15.25 adds the MyLibrary assembly. Notice that you must supply the Version, Culture, and PublicKeyToken associated with the assembly. You need to substitute the correct values for these properties in Listing 15.25 before you use the file with an assembly that you have compiled. (Remember that you can get these values by opening the c:WINDOWSassembly folder.)

Note

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.

In general, you should avoid adding your assemblies to the Global Assembly Cache because using the Global Assembly Cache defeats XCopy deployment. Using the Global Assembly Cache makes it more difficult to back up an application. It also makes it more difficult to move an application from one server to another.

Architectural Considerations

If you embark on a large ASP.NET project, you’ll quickly discover that you spend more time writing code for components than writing code for your pages. This is not a bad thing. Placing as much of your application logic as possible in components makes it easier to maintain and extend your application.

However, the process of organizing the components itself can become time consuming. In other words, you start to run into architectural issues concerning the best way to design your web application.

The topic of architecture, like the topics of politics and religion, should not be discussed in polite company. People have passionate opinions about architecture, and discussions on this topic quickly devolve into people throwing things. Be aware that any and all statements about proper architecture are controversial.

With these disclaimers out of the way, in this section I provide you with an overview of one of the most common architectures for ASP.NET applications. In this section, you learn how to build a three-tiered ASP.NET application.

Building Multi-Tier Applications

One very common architecture for an application follows an n-tier design model. When using an n-tier architecture, you encapsulate your application logic into separate layers.

In particular, it is recommended that an application should be divided into the following three application layers:

  • User Interface layer.

  • Business Logic layer.

  • Data Access layer.

The idea is that the User Interface layer should contain nothing but user interface elements such as HTML and ASP.NET controls. The User Interface layer should not contain any business logic or data access code.

The Business Logic layer contains all your business rules and validation code. It manages all data access for the User Interface layer.

Finally, the Data Access layer contains all the code for interacting with a database. For example, all the code for interacting with Microsoft SQL Server should be encapsulated in this layer.

The advantage of encapsulating your application logic into different layers is that it makes it easier to modify your application without requiring you to rewrite your entire application. Changes in one layer can be completely isolated from the other layers.

For example, imagine that (one fine day) your company decides to switch from using Microsoft SQL Server to using Oracle as their database server. If you have been careful to create an isolated Data Access layer, then you would need to rewrite only your Data Access layer. It might be a major project, but you would not need to start from scratch.

Or imagine that your company decides to create a Silverlight version of an existing ASP.NET application. Again, if you have been careful to isolate your User Interface layer from your Business Logic layer, then you can extend your application to support a Silverlight interface without rewriting your entire application. The Siverlight application can use your existing Business Logic and Data Access layers.

Note

I spend my working life training companies on implementing ASP.NET applications. Typically, a company is migrating a web application written in some other language such as Java or ASP Classic to the ASP.NET Framework. It always breaks my heart to see how much code is wasted in these transitions (thousands of man hours of work lost). If you are careful in the way that you design your ASP.NET application now, you can avoid this sorry fate in the future.

I realize that this is all very abstract, so let’s examine a particular sample. We’ll create a simple product management system that enables you to select, insert, update, and delete products. However, we’ll do it the right way by dividing the application into distinct User Interface, Business Logic, and Data Access layers.

Creating the User Interface Layer

The User Interface layer is contained in Listing 15.26. Notice that the User Interface layer consists of a single ASP.NET page. This page contains no code whatsoever.

Example 15.26. Products.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
    html
    {
        background-color:silver;
    }
    .content
    {
        padding:10px;
        background-color:white;
    }
    .products
    {
        margin-bottom:20px;
    }
    .products td,.products th
    {
        padding:5px;
        border-bottom:solid 1px blue;
    }
    a
    {
        color:blue;
    }
    </style>
    <title>Products</title>
</head>
<body>
    <form id="form1" runat="server">
    <div class="content">

    <asp:GridView
        id="grdProducts"
        DataSourceID="srcProducts"
        DataKeyNames="Id"
        AutoGenerateEditButton="true"
        AutoGenerateDeleteButton="true"
        AutoGenerateColumns="false"
        CssClass="products"
        GridLines="none"
        Runat="server">
        <Columns>
        <asp:BoundField
            DataField="Id"
            ReadOnly="true"
            HeaderText="Id" />
        <asp:BoundField
            DataField="Name"
            HeaderText="Name" />
        <asp:BoundField
            DataField="Price"
            DataFormatString="{0:c}"
            HeaderText="Price" />
        <asp:BoundField
            DataField="Description"
            HeaderText="Description" />
        </Columns>
    </asp:GridView>

    <fieldset>
    <legend>Add Product</legend>
    <asp:DetailsView
        id="dtlProduct"
        DataSourceID="srcProducts"
        DefaultMode="Insert"
        AutoGenerateInsertButton="true"
        AutoGenerateRows="false"
        Runat="server">
        <Fields>
        <asp:BoundField
            DataField="Name"
            HeaderText="Name:" />
        <asp:BoundField
            DataField="Price"
            HeaderText="Price:"/>
        <asp:BoundField
            DataField="Description"
            HeaderText="Description:" />
        </Fields>
    </asp:DetailsView>
    </fieldset>


    <asp:ObjectDataSource
        id="srcProducts"
        TypeName="AcmeStore.BusinessLogicLayer.Product"
        SelectMethod="SelectAll"
        UpdateMethod="Update"
        InsertMethod="Insert"
        DeleteMethod="Delete"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 15.26 contains a GridView, DetailsView, and ObjectDataSource control. The GridView control enables you to view, update, and delete the products contained in the Products database table (see Figure 15.12). The DetailsView enables you to add new products to the database. Both controls use the ObjectDataSource as their data source.

The Products.aspx page.

Figure 15.12. The Products.aspx page.

Note

The next chapter is entirely devoted to the ObjectDataSource control.

The page in Listing 15.26 does not interact with a database directly. Instead, the ObjectDataSource control is used to bind the GridView and DetailsView controls to a component named AcmeStore.BusinessLogicLayer.Product. The Product component is contained in the Business Logic layer.

Note

The page in Listing 15.26 does not contain any validation controls. I omitted adding validation controls for reasons of space. In a real application, you would want to toss some RequiredFieldValidator and CompareValidator controls into the page.

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.

The Business Logic layer consists of a single component named Product, which is contained in Listing 15.27. (A real-world application might contain dozens or even hundreds of components in its Business Logic layer.)

Example 15.27. BLL/Product.cs

using System;
using System.Collections.Generic;
using AcmeStore.DataAccessLayer;

namespace AcmeStore.BusinessLogicLayer
{
    /// <summary>
    /// Represents a store product and all the methods
    /// for selecting, inserting, and updating a product
    /// </summary>
    public class Product
    {
        private int _id = 0;
        private string _name = String.Empty;
        private decimal _price = 0;
        private string _description = String.Empty;

        /// <summary>
        /// Product Unique Identifier
        /// </summary>
        public int Id
        {
            get { return _id; }
        }

        /// <summary>
        /// Product Name
        /// </summary>
        public string Name
        {
            get { return _name; }
        }

        /// <summary>
        /// Product Price
        /// </summary>
        public decimal Price
        {
            get { return _price; }
        }

        /// <summary>
        /// Product Description
        /// </summary>
        public string Description
        {
            get { return _description; }
        }

        /// <summary>
        /// Retrieves all products
        /// </summary>
        /// <returns></returns>
        public static List<Product> SelectAll()
        {
            SqlDataAccessLayer dataAccessLayer = new SqlDataAccessLayer();
            return dataAccessLayer.ProductSelectAll();
        }

        /// <summary>
        /// Updates a particular product
        /// </summary>
        /// <param name="id">Product Id</param>
        /// <param name="name">Product Name</param>
        /// <param name="price">Product Price</param>
        /// <param name="description">Product Description</param>
        public static void Update(int id, string name, decimal price, string description)
        {
            if (id < 1)
                throw new ArgumentException("Product Id must be greater than 0", "id");

            Product productToUpdate = new Product(id, name, price, description);
            productToUpdate.Save();
        }

        /// <summary>
        /// Inserts a new product
        /// </summary>
        /// <param name="name">Product Name</param>
        /// <param name="price">Product Price</param>
        /// <param name="description">Product Description</param>
        public static void Insert(string name, decimal price, string description)
        {
            Product newProduct = new Product(name, price, description);
            newProduct.Save();
        }

        /// <summary>
        /// Deletes an existing product
        /// </summary>
        /// <param name="id">Product Id</param>
        public static void Delete(int id)
        {
            if (id < 1)
                throw new ArgumentException("Product Id must be greater than 0", "id");

            SqlDataAccessLayer dataAccessLayer = new SqlDataAccessLayer();
            dataAccessLayer.ProductDelete(id);
        }

        /// <summary>
        /// Validates product information before saving product
        /// properties to the database
        /// </summary>
        private void Save()
        {
            if (String.IsNullOrEmpty(_name))
                throw new ArgumentException("Product Name not supplied", "name");
            if (_name.Length > 50)
                throw new ArgumentException("Product Name must be less than 50 characters", "name");
            if (String.IsNullOrEmpty(_description))
                throw new ArgumentException("Product Description not supplied", "description");

            SqlDataAccessLayer dataAccessLayer = new SqlDataAccessLayer();
            if (_id > 0)
                dataAccessLayer.ProductUpdate(this);
            else
                dataAccessLayer.ProductInsert(this);
        }

        /// <summary>
        /// Initializes Product
        /// </summary>
        /// <param name="name">Product Name</param>
        /// <param name="price">Product Price</param>
        /// <param name="description">Product Description</param>
        public Product(string name, decimal price, string description)
            : this(0, name, price, description) { }

        /// <summary>
        /// Initializes Product
        /// </summary>
        /// <param name="id">Product Id</param>
        /// <param name="name">Product Name</param>
        /// <param name="price">Product Price</param>
        /// <param name="description">Product Description</param>
        public Product(int id, string name, decimal price, string description)
        {
            _id = id;
            _name = name;
            _price = price;
            _description = description;
        }

    }
}

The Product component contains four public methods named SelectAll(), Update(), Insert(), and Delete(). All four of these methods use the SqlDataAccessLayer component to interact with the Products database table. The SqlDataAccessLayer is contained in the Data Access layer.

For example, the SelectAll() method returns a collection of Product objects. This collection is retrieved from the SqlDataAccessLayer component.

The Insert(), Update(), and Delete() methods validate their parameters before passing the parameters to the Data Access layer. For example, when you call the Insert() method, the length of the Name parameter is checked to verify that it is less than 50 characters.

Notice that the Business Logic layer does not contain any data access logic. All this logic is contained in the Data Access 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 in Listing 15.28. (A real-world application might contain dozens or even hundreds of components in its Data Access layer.)

Example 15.28. DALSqlDataAccessLayer.cs

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
using System.Collections.Generic;
using AcmeStore.BusinessLogicLayer;

namespace AcmeStore.DataAccessLayer
{
    /// <summary>
    /// Data Access Layer for interacting with Microsoft
    /// SQL Server 2005
    /// </summary>
    public class SqlDataAccessLayer
    {
        private static readonly string _connectionString = string.Empty;

        /// <summary>
        /// Selects all products from the database
        /// </summary>
        public List<Product> ProductSelectAll()
        {
            // Create Product collection
            List<Product> colProducts = new List<Product>();

            // Create connection
            SqlConnection con = new SqlConnection(_connectionString);

            // Create command
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandText = "SELECT Id,Name,Price,Description FROM Products";

            // Execute command
            using (con)
            {
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    colProducts.Add(new Product(
                        (int)reader["Id"],
                        (string)reader["Name"],
                        (decimal)reader["Price"],
                        (string)reader["Description"]));
                }
            }
            return colProducts;
        }

        /// <summary>
        /// Inserts a new product into the database
        /// </summary>
        /// <param name="newProduct">Product</param>
        public void ProductInsert(Product newProduct)
        {
            // Create connection
            SqlConnection con = new SqlConnection(_connectionString);

            // Create command
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandText = "INSERT Products (Name,Price,Description) VALUES (@Name,@Price,

            // Add parameters
            cmd.Parameters.AddWithValue("@Name", newProduct.Name);
            cmd.Parameters.AddWithValue("@Price", newProduct.Price);
            cmd.Parameters.AddWithValue("@Description", newProduct.Description);

            // Execute command
            using (con)
            {
                con.Open();
                cmd.ExecuteNonQuery();

            }
        }

        /// <summary>
        /// Updates an existing product into the database
        /// </summary>
        /// <param name="productToUpdate">Product</param>
        public void ProductUpdate(Product productToUpdate)
        {
            // Create connection
            SqlConnection con = new SqlConnection(_connectionString);

            // Create command
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandText = "UPDATE Products SET Name=@Name,Price=@Price,Description=
            // Add parameters
            cmd.Parameters.AddWithValue("@Name", productToUpdate.Name);
            cmd.Parameters.AddWithValue("@Price", productToUpdate.Price);
            cmd.Parameters.AddWithValue("@Description", productToUpdate.Description);
            cmd.Parameters.AddWithValue("@Id", productToUpdate.Id);

            // Execute command
            using (con)
            {
                con.Open();
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Deletes an existing product in the database
        /// </summary>
        /// <param name="id">Product Id</param>
        public void ProductDelete(int Id)
        {
            // Create connection
            SqlConnection con = new SqlConnection(_connectionString);

            // Create command
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandText = "DELETE Products WHERE Id=@Id";

            // Add parameters
            cmd.Parameters.AddWithValue("@Id", Id);

            // Execute command
            using (con)
            {
                con.Open();
                cmd.ExecuteNonQuery();

            }
        }

        /// <summary>
        /// Initialize the data access layer by
        /// loading the database connection string from
        /// the Web.Config file
        /// </summary>
        static SqlDataAccessLayer()
        {
            _connectionString = WebConfigurationManager.ConnectionStrings["Store"]. ConnectionString;
            if (string.IsNullOrEmpty(_connectionString))
                throw new Exception("No connection string configured in Web.Config file");
        }
    }
}

The SqlDataAccessLayer component in Listing 15.28 grabs the database connection string that it uses when communicating with Microsoft SQL Server in its constructor. The connection string is assigned to a private field so that it can be used by all the component’s methods.

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.

Note

In this section, the Data Access layer was built using ADO.NET. It could just have as easily been built using LINQ to SQL. We discuss LINQ to SQL in Chapter 18.

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.

Summary

In this chapter, you learned how to build components in the .NET Framework. In the first part, you were given an overview of component building. You learned how to take advantage of dynamic compilation by using the App_Code folder. You also learned how to create component properties, methods, and constructors. You also examined several advanced topics related to components such as overloading, inheritance, MustInherit classes, and interfaces.

In the second half of this chapter, you learned how to build component libraries. You saw different methods for compiling a set of components into an assembly. You also examined how you can add components to both an application’s Bin folder and the Global Assembly Cache.

Finally, you had a chance to consider architectural issues related to building applications with components. You learned how to build a three-tiered application, divided into isolated User Interface, Business Logic, and Data Access layers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset