Writing Business Objects


First, let's look at creating business objects that can be used by .NET applications. These business objects will perform the same types of functions that business objects in COM or other objects models do, plus they will be able to make use of all of the advantages offered by the .NET architecture and the CLR.

This section looks at how to create an object and then extend that object through inheritance. Then it looks at how to extend the functionality of an existing class and also how to utilize some of the COM+ component services within the .NET object. After creating the object, you compile it and place it in an assembly. Once the assembly is created, you will create an ASP.NET page to test the new object.

There are two concurrent examples through the chapter. They share exactly the same functionality, except that one is written in Visual Basic .NET and the other is written in C#. These objects will be tested from an ASP.NET page, but could just as easily be tested from a Windows Forms application or from a command line application. Use a simple text editor to create the files, and the command line compilers and tools to create the assemblies.

Building the Object

To create the object, you need to look at the:

  • Guidelines for creating a component

  • Attributes that can be set to describe a component

With all of that out of the way, you can move to actually creating the example objects.

Class Design Guidelines

When writing components for .NET, some of the existing component design guidelines can still be used. However, just as the .NET Framework is different from COM and COM+, some of the design guidelines you have used in the past are now implemented in a different way. In the past, there may have even been different guidelines depending on the language used. Now with .NET, those guidelines are unified. One of the other keys to using these design guidelines is that they have been adhered to by Microsoft in the creation of the System Frameworks themselves , along with all of the sample code that comes with the SDK.

Error Handling

Now that robust error handling (including structured exception handling) is part of the CLR, and therefore available to all languages supported by the CLR, you should use it wherever possible. The former practice of using error codes and checking return values or even On Error Goto has been replaced with catching exceptions and handling the errors at that point.

This doesn't mean you should use exceptions everywhere “ exceptions are designed to handle errors , not something you should expect to happen. However, there are instances where error codes can come in handy. For example, if you are trying to open a file and the file doesn't exist, return a null value, since that error could be expected in normal use. But if the file system returns an I/O error, throw an exception, since that condition isn't normally expected.

Properties versus Methods

One of the most difficult choices in designing a component is choosing what type of interface to use. This holds true for all component-based architectures, not just .NET. Knowing when to use a property as opposed to a method and vice versa, is as much a matter of personal taste as are the following of design guidelines. The basic guidelines to follow are:

  • If there is an internal data member being exposed outside the component, use a property.

  • If the execution of the code causes some measurable side effect to the component or the environment, use a method.

  • If the order of code execution is important, use a method. Since the CLR has the ability to short- circuit expression testing, a property may not be accessed when you expect it will. Let's look at an example of a short-circuited expression.

    An object X has two properties, A and B. These properties do more than just expose an internal data member “they actually do some work as they return a value. For this example, they each return an integer between 1 and 10. They are being used in code that looks like this:

     if (X.A > 5) AndAlso (X.B < 7) then ... ' do something end if 

    If the evaluation of X.A returns a 4, then the first part of the Boolean expression is False . In order for an AndAlso statement to be True , both parts have to be True . The CLR knows this too, and seeing that the first part is False , it will skip or short-circuit the evaluation of X.B , since its value doesn't matter. However, because the code violated good design principles and did work during the evaluation of B , that work will not be performed in this case. This is probably not the desired effect.

Memory Management

Memory management is one of the most difficult things that most programmers face. Now that the operating system has gone to a flat memory model, developers don't have the issues from the days of Windows 3.1 about allocating memory. However, they still have had to deal with how and when to discard the memory. With the CLR handling most of the memory management for .NET components, there are only a few things that developers need to do differently when dealing with memory than in the past.

The CLR has the ability to create small, short-lived objects very quickly and cheaply. Thus you shouldn't be worried about creating objects that make the development easier to follow. According to performance testing done by Microsoft, the runtime can allocate nearly 10 million objects per second on a moderately fast machine. Also, objects running in the CLR will be garbage-collected by the runtime after they are no longer being referenced. This happens automatically, and keeps the developer from having to deal with memory leaks from improperly-freed objects. While automatic garbage collection does deal with a lot of headaches , there still have to be processor cycles dedicated to the garbage collector running. When it actually runs is also unpredictable, and could cause a temporary hiccup in performance.

Using Attributes

Attributes in the CLR allow developers to add additional information to the classes they have created. These are then available to the application using the component through the System.Reflection classes. You can use attributes to provide hints or flags to a number of different systems that may be using the component. Attributes can be used as compiler flags to tell the compiler how to handle the compilation of the class. They can be used by tools to provide more information about the usage of the component at design-time. This means developers can get away from having to embed comments in code simply as clues for the tool to know where certain parts of the code are. Attributes can also be used to identify the transaction characteristics of a class when interacting with the Components Services feature of the operating system.

The following two tables show the standard attributes for properties and events that are defined in the System.ComponentModel namespace in the CLR. As these attributes are already defined, they can be used by the developer without having to create a corresponding class for a custom attribute.

Here are the attributes common to events and properties:

Attribute

Description

Usage “ default in bold

Browsable

Declares if this property should appear in the property window of a design tool.

[Browsable (false true )]

Category

Used to group properties when being displayed by a design tool.

[Category (categoryName)]

Description

Help text displayed by the design tool when this property or event is selected.

[Description (descriptionString)]

The attributes for properties are as follows :

Attribute

Description

Usage “ default in bold

Bindable

Declares if data should be bound to this property.

[Bindable ( false true)]

DefaultProperty

Indicates that this is the default property for the class.

[DefaultProperty]

DefaultValue

Sets a default value for the property.

[DefaultValue (value)]

Localizable

Indicates that a property can be localized. The compiler will cause all properties with this attribute to store the property in a resource file. The resources file can then be localized without having to modify any code.

[Localizable ( false true)]

The attribute for events is:

Attribute

Description

Usage

DefaultEvent

Specifies the default event for the class.

[DefaultEvent]

Sample Object

In this section you will see how to create a sample object,which will be used to encapsulate business and data access functionality “ this is the typical usage for objects in the applications that most developers are creating. The business object will encapsulate the interaction with the IBuyAdventure database that is used in the case study discussed in Chapter 24. Since this is an example of how to build components rather than a full case study on a business and data component, the component will have limited functionality.

The component will have one property:

Property

Type

Usage

DatabaseConnection

String

The database connection string

The component will have three methods:

Method

Returns

Parameters

Usage

GetProductTypes

String Collection

none

Returns a string collection of all of the product types in the database.

GetProducts

DataSet

productType

Returns a DataSet containing the records for a specific product type.

AveragePrice

Single

productType

Returns a single value that represents the average price of the items of a specific product type.

With the interface defined, it is now time to write the component. As stated earlier, the component will be developed in both Visual Basic .NET and in C#. Let's start with the Visual Basic .NET version.

Visual Basic .NET Class Example

Here is how the final class looks when written in Visual Basic .NET:

  Option Explicit   Option Strict     Imports System   Imports System.Data   Imports System.Data.SqlClient     Namespace BusObjectVB     Public Class IBAProducts     Private m_DSN As String     Public Sub New ()   MyBase.New   m_DSN = ""   End Sub     Public Sub New(DSN As string)   MyBase.New   m_DSN = DSN   End Sub     Public Property DatabaseConnection As string   Set   m_DSN = value   End Set   Get   Return m_DSN   End Get   End Property     Public Function GetProductTypes () As DataSet   If m_DSN = "" Then   Throw(New ArgumentNullException("DatabaseConnection", _   "No value for the database connection string"))   End If   Dim myConnection As New SqlConnection(m_DSN)   Dim sqlAdapter1 As New SqlDataAdapter("SELECT DISTINCT ProductType " _   & "FROM Products", myConnection)     Dim types As New DataSet()   sqlAdapter1.Fill(types, "ProdTypes")     Return types   End Function     Public Function GetProducts ( productType As String) As DataSet   If m_DSN = "" Then   Throw(New ArgumentNullException("DatabaseConnection", _   "No value for the database connection string"))   End If   Dim myConnection As New SqlConnection(m_DSN)   Dim sqlAdapter1 As New SqlDataAdapter("SELECT * FROM Products WHERE " _   & "ProductType='" & productType & "'", myConnection)     Dim products As New DataSet()   sqlAdapter1.Fill(products, "products")     Return products   End Function     Public Function AveragePrice ( productType As string) As Double   If m_DSN = "" Then   Throw(New ArgumentNullException("DatabaseConnection", _   "No value for the database connection string"))   End If   Dim myConnection As New SqlConnection(m_DSN)     Dim sqlAdapter1 As New SqlDataAdapter("SELECT AVG(UnitPrice) AS " _   & "AveragePrice FROM Products WHERE " _   & "ProductType='"+productType+"'", myConnection)     Dim AvgPrice As New DataSet()   sqlAdapter1.Fill(AvgPrice, "AveragePrice")     Dim priceTable As DataTable   priceTable = AvgPrice.Tables("AveragePrice")   If (Not priceTable.Rows(0).IsNull("AveragePrice")) Then   Return CDbl(priceTable.Rows(0)("AveragePrice"))   Else   Return 0   End If   End Function   End Class   End Namespace  

Let's break down each part and describe what it does and how.

Look at the object in detail. The first two statements are unique to Visual Basic. With its roots as a loosely-typed language, Visual Basic .NET has had some directives added to it to tell the compiler that it should do some level of type checking when compiling the application. The Option Explicit statement is familiar to Visual Basic programmers. It forces the declaration of all variables before they are used, and will generate a compiler error if a variable is used before it is declared. The Option Strict statement is introduced with Visual Basic .NET. It greatly limits the implicit data type conversions that Visual Basic has been able to do in the past. Option Strict also disallows any late binding. This will increase the performance in components since types are checked at compile-time, and not at runtime.

 Option Explicit Option Strict 

The next section states which parts of the System Frameworks will be used in this object:

 Imports System Imports System.Data Imports System.Data.SqlClient 

You can actually use any part of the System Frameworks at any time in the code by simply referencing the full path to it “ System.Data.SqlClient.DataTable “ but that would begin to make the code cumbersome and unnecessarily long. By explicitly stating which parts of the System Frameworks the component will use, you can refer to the particular class without having to state the full path “ DataTable “ as shown in the example.

This object uses the System namespace, which contains the necessary base classes to build the object. The System.Data namespace contains the classes that make up the ADO.NET data access architecture. Since the component accesses data in a SQL Server 2000 database, the component also includes the System.Data.SqlClient namespace. This namespace contains the classes to access the SQL Server-managed provider.

The object will be encapsulated in its own unique namespace, BusObjectVB , so first declare all of the classes that make up the object within that namespace. The business component is defined as a class “ after creating an instance of it in the program it will then be an object:

 Namespace BusObjectVB         Public Class IBAProducts 

Within the object, there will be one private variable, which will be used to hold the database connection string. The next two methods are the constructors for the class. The constructor is automatically called by the runtime when an object is instantiated . There are actually two constructors. The first one takes no parameters and is therefore called the default constructor:

 Private m_DSN As String      Public Sub New ()    MyBase.New    m_DSN = "" End Sub 

The second constructor takes a parameter, DSN , and will set the database connection string at the same time as the object is created:

 Public Sub New(DSN As string)    MyBase.New    m_DSN = DSN End Sub 

Since a constructor cannot return any values, it is declared as a Sub rather than a Function . In Visual Basic, you must explicitly call the constructor for the base class, using the MyBase.New statement.

While the second constructor sets the database connection string when the object is created, the object needs to provide a way to set and read it at other times. Since the member variable holding this data is marked as private, there is a property function to set and retrieve the value. The external name of the property is DatabaseConnection :

 Public Property DatabaseConnection As String    Set       m_DSN = value    End Set    Get       Return m_DSN    End Get End Property 

Next, look at the methods that work with the information in the database.

The first method, GetProductTypes , will retrieve a listing of the product types for the products stored in the database. This will return the listing to the calling program in a DataSet . A DataSet represents an in-memory cache of data. This means that it is a copy of the data in the database, so there is no underlying connection to the stored data. To access the database, you first need to connect to it. The SqlConnection object provides this functionality and when the Open method is called, it will connect to the database using the connection string that was stored in the private member variable m_DSN .

It is therefore important that this value be set properly. If the user of the component does not set the value of the DatabaseConnection property, the object won't be able to open the database. The best way to indicate this is to throw an exception. The object uses the Throw statement and passes it an instance of the ArgumentNullException class. This version of the constructor for this class takes two strings “the parameter that was Null and a text description of the error:

 Public Function GetProductTypes () As DataSet    If m_DSN = "" Then       Throw(New ArgumentNullException("DatabaseConnection", _                         "No value for the database connection string"))    End If    Dim myConnection As New SqlConnection(m_DSN) 

To retrieve the desired information from the database, use a SQL query. To process the query, use the SqlDataAdapter . When you create the object, pass in the text of the SQL query that will be executed by this object. Also, tell the object which database connection object to use to access the data. That is the object that was created in the previous steps. The creation of this object will automatically open the database connection:

 Dim sqlAdapter1 As New SqlDataAdapter("SELECT DISTINCT ProductType " _                                     & "FROM Products", myConnection) 

With the mechanism for retrieving the data from the database, you need a place to store it to pass it back to the caller. This will be in a DataSet object. Create a new instance of this class and call it types . The data will be placed in this object by using the Fill method of the SqlDataAdapter class. This method takes the destination DataSet object as well as a name to represent the data within the data set. To send the data back to the caller, return the DataSet object types :

 Dim types As New DataSet()    sqlAdapter1.Fill(types, "ProdTypes")         Return types End Function 

The next method, GetProducts , shown in the following code will retrieve the list of products for a specified product type. Specify the product type by passing in the product type string. The list of products will be returned as a DataSet . The main part of the method is the same as the previous method; connect to the database and fill up a DataSet object with the information needed:

 Public Function GetProducts ( productType As String) As DataSet    If m_DSN = "" Then       Throw(New ArgumentNullException("DatabaseConnection", _                         "No value for the database connection string"))    End If    Dim myConnection As New SqlConnection(m_DSN)    Dim sqlAdapter1 As New SqlDataAdapter("SELECT * FROM Products WHERE " _                      & "ProductType='" & productType & "'", myConnection)         Dim products As New DataSet()    sqlAdapter1.Fill(products, "products")         Return products End Function 

The resulting filled DataSet object can then be returned to the calling application.

Next, look at the method to calculate the average selling price of the items of a particular type:

 Public Function AveragePrice ( productType As string) As Double    If m_DSN = "" Then       Throw(New ArgumentNullException("DatabaseConnection", _                         "No value for the database connection string"))    End If    Dim myConnection As New SqlConnection(m_DSN)         Dim sqlAdapter1 As New SqlDataAdapter("SELECT AVG(UnitPrice) AS " _                      & "AveragePrice FROM Products WHERE " _                      & "ProductType='"+productType+"'", myConnection) 

The method to calculate the average selling price for a product type will pass that value back as a return value of type double. Just as with the previous method, the one parameter for this method will be the product type of the desired product group. The database access code is again very similar “ the primary difference being that the SQL statement calculates an average rather than returning a set of rows from the database:

 Dim AvgPrice As New DataSet() sqlAdapter1.Fill(AvgPrice, "AveragePrice")      Dim priceTable As DataTable priceTable = AvgPrice.Tables("AveragePrice") 

With the results of the database query in the DataSet object, take a look at the contents of the data to see what the average price was. To examine the data directly, first grab the table that contains the result of the query from the DataSet . In the Fill method, ADO.NET put the results of the query into a table named AveragePrice . Then to get a reference to that specific table from the DataSet , retrieve that table by name from the Tables collection.

If there was no data returned, you will have a table with no rows in it. If this is the case, then return the average price as 0. If there is one row in the table “ a SQL statement to calculate an average will return at most one row “ then look at the value contained in the field named AveragePrice and return that value as the average price for the product type. The field named AveragePrice does not exist in the physical database, but is an alias that is created with the SQL SELECT statement to hold the results of the AVG function:

 If (Not priceTable.Rows(0).IsNull("AveragePrice")) Then             Return CDbl(priceTable.Rows(0)("AveragePrice"))          Else             Return 0          End If       End Function    End Class End Namespace 

This is the end of the Visual Basic .NET class. Before compiling and testing it, let's look at the same component coded in C#.

C# Class Example

Here is the same class written in C#:

  using System;   using System.Data;   using System.Data.SqlClient;     namespace BusObjectCS {     public class IBAProducts {     private string m_DSN;     public IBAProducts () {   m_DSN="";   }     public IBAProducts (string DSN) {   m_DSN = DSN;   }     public string DatabaseConnection {   set { m_DSN = value; }   get { return m_DSN; }   }   public DataSet GetProducts (string productType) {   if (m_DSN == "")   {   throw new ArgumentNullException("DatabaseConnection",   "No value for the database connection string");   }   SqlConnection myConnection = new SqlConnection(m_DSN);   SqlDataAdapter sqlAdapter1 = new SqlDataAdapter(   "SELECT * FROM Products WHERE ProductType='"+productType+"'", myConnection);   DataSet products = new DataSet();   sqlAdapter1.Fill(products, "products");     return products;   }     public DataSet GetProductTypes () {     if (m_DSN == "")   {   throw new ArgumentNullException("DatabaseConnection",   "No value for the database connection string");   }   SqlConnection dbConnection = new SqlConnection(m_DSN);   dbConnection.Open();     SqlDataAdapter sqlAdapter1 = new SqlDataAdapter(   "SELECT DISTINCT ProductType FROM Products", dbConnection);     DataSet types = new DataSet();   sqlAdapter1.Fill(types, "ProdTypes");     return types;   }     public Double AveragePrice (string productType) {   if (m_DSN == "")   {   throw new ArgumentNullException("DatabaseConnection",   "No value for the database connection string");   }   SqlConnection dbConnection = new SqlConnection(m_DSN);   dbConnection.Open();     SqlDataAdapter sqlAdapter1 = new SqlDataAdapter(   "SELECT AVG(UnitPrice) AS AveragePrice FROM Products WHERE " +   "ProductType='" + productType + "'", dbConnection);     DataSet AvgPrice = new DataSet();   sqlAdapter1.Fill(AvgPrice, "AveragePrice");     DataTable priceTable;   priceTable = AvgPrice.Tables["AveragePrice"];   if (priceTable.Rows.Count > 0)   {   return (Double)priceTable.Rows[0]["AveragePrice"];   }   else   return 0;   }   }   }  

Let's look at this object in detail. Focus on the differences between the C# version and the Visual Basic .NET version. The functionality is exactly the same.

The first set of statements is used to specify the System Framework namespaces that will be used by this class. Rather than using the Visual Basic .NET Imports keyword, C# uses the using keyword. As before, use the same three namespaces as the Visual Basic .NET version. In the Visual Basic .NET version, you had Option Explicit and Option Strict statements. Since by default C# is a strongly- typed language, these statements (or rather a C# equivalent) are not necessary:

 using System; using System.Data; using System.Data.SqlClient; 

The next step is to declare the namespace and the class. Note that the name of the class is the same as the Visual Basic .NET version. This is OK, and both can even run at the same time, because of the namespace. Class names only need to be unique within a namespace.

 namespace BusObjectCS { public class IBAProducts { 

The next major difference in the two implementations comes with the way that constructors are defined. In C#, a constructor is defined with a method that has the same name as the class. In Visual Basic .NET, the constructor for a class is always named New . In C#, the base class constructor is called automatically by the compiler:

 private string m_DSN;      public IBAProducts () {    m_DSN=""; }      public IBAProducts (string DSN) {    m_DSN = DSN; } 

A property is defined in a similar way to Visual Basic .NET, but uses a different syntax to declare the accessor methods. Where C# uses a set (or get ) statement followed by a block delimited by braces, Visual Basic .NET uses a specific Set...End Set (or Get...End Get ) block to denote the accessor methods:

 public string DatabaseConnection {    set { m_DSN = value; }    get { return m_DSN; } } 

The remainder of the component is identical to the C# version, except for the syntax differences. With both of the components created, let's move on to the next step: compilation.

Compiling the Classes

As seen earlier in the book, the .NET architecture executes code stored in the MSIL format. This intermediate language is created from the sourcecode of the various languages supported by the CLR. You have already created the sourcecode for your components. The next step is to compile this sourcecode into the MSIL version. This is done by executing the appropriate compiler with the proper arguments, based on the language and the destination.

Compiling the VB.NET Class

To compile the Visual Basic .NET component, use a batch file that should be run from the command line. This file is named makevb.bat and is as follows:

  vbc /out:bin\BusObjectvb.dll /t:library sampleObject.vb /r:System.Data.dll   /r:System.dll /r:System.XML.dll  

Take a look at the parameters that are passed to the compiler. The first parameter, /out , defines the output file that the MSIL code will be placed in. In this example, the compiled output is placed in the file named BusObjectvb.dll , and stored in the bin subdirectory below the directory where the source file resides. If the command did not include an /out parameter, the compiler would have automatically created the filename based on the name of the source file and the target type and placed it in the current directory.

The next parameter, /t , is used to specify the type of output file format the compiler should create. This is a shortened version of the /target parameter; either version can be legally used. There are four possible values for this parameter:

  • /target:exe : This tells the compiler to create a command line executable program. This is the default value, so if the /target parameter is not included, an .exe file will be created.

  • /target:library : This tells the compiler to create a DLL file that will contain an assembly that consists of all the source files passed to the compiler. The compiler will also automatically create the manifest for this assembly.

  • /target:module : This tells the compiler to create a DLL, but not to create a manifest for it. In order for the module to be used by the .NET Framework, it will need to be manually added to an assembly using the Assembly Generation tool ( AL.EXE ). This tool allows you to create the assembly manifest information manually, and then add modules to it.

  • /target:winexe : This tells the compiler to create a Windows Forms application.

The next parameter is the name of the file to be compiled. In your example, the source file is named sampleObject.vb . The file extension is not critical, but it does make it easier to recognize the type of source file without having to open it up. If there are multiple source files, specify multiple source files on the same command line. They will be combined into the file type specified by the /target parameter. The final set of parameters indicates the other assemblies that are referenced from within the component.

Compiling the C# Class

To make it easier to run the compiler during development, create a batch file that will execute the C# compiler with all of the proper parameters. This file is called makecs.bat and looks like this:

  csc /out:bin\BusObjectcs.dll /t:library sampleObject.cs /r:System.Data.dll   /r:System.dll /r:System.XML.dll  

As you can see, it is identical to the makevb.bat file except for three small changes. First, since the batch file calls the C# compiler rather than the Visual Basic .NET compiler, the file to execute is csc instead of vbc. The next difference is the name of the output file; since the C# component is in a separate assembly, it gets a different name of BusObjectcs.dll. Finally, the source file that contains the C# sourcecode is called sampleObject.cs.

Testing the Class

Now that you have created the two identical objects, the next step is to test them. For this test, access these components from within an ASP.NET page.

The first page, ProductTypes.aspx , will test the GetProductTypes method. This page will simply be used to display the results of a call to this method. The sourcecode for the page looks like this:

  <%@ Page Language="C#" Description="Component Test Program" %>   <%@ Import Namespace="System.Data" %>   <%@ Import Namespace="BusObjectCS" %>   <%@ Import Namespace="BusObjectVB" %>   <html>   <script language="C#" runat="server">     void Page_Load(Object sender, EventArgs evArgs){   // BusObjectCS.IBAProducts objProducts = new BusObjectCS.IBAProducts();   BusObjectVB.IBAProducts objProducts = new BusObjectVB.IBAProducts();     String dsn = "server=localhost;uid=sa;pwd=;database=IBuyAdventure";     objProducts.DatabaseConnection = dsn;     DataSet prodTypes = objProducts.GetProductTypes();     productList.DataSource = prodTypes;   productList.DataBind();   }     </script>     <body>   <h3><font face="Verdana">List of Product Types</font></h3>   <asp:DataList id="productList" runat="server">   <HeaderTemplate>   <table border="1">   <tr><th>Click to Display list of products</th></tr>   </HeaderTemplate>     <ItemTemplate>   <tr>   <td align="center">   <%# DataBinder.Eval(Container.DataItem, "ProductType",   "<a href=\"displayProducts.aspx?PID={0}\">{0}</a>") %>   </td>   </tr>   </ItemTemplate>     <FooterTemplate>   </table>   </FooterTemplate>   </asp:DataList>   </body>   </html>  

The first part of the page sets up the language to use, as well as the namespaces of the assemblies that will be used on the page:

 <%@ Page Language="C#" Description="Component Test Program" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="BusObjectCS" %> <%@ Import Namespace="BusObjectVB" %> 

The code in this page is written using C#. The language used in the ASP.NET page does not have to correlate to the language that was used in the business components. Since both the page and the components will be compiled down to MSIL before they are executed, the language they are written in does not need to correspond .

Also include the namespaces of the assemblies being used. Even though the component is not calling any of the methods from the System.Data namespace, it is using one of the classes ( DataSet ) as a return value from the method in the component. In order for the page to understand how to deal with this class, add the namespace that contains it as an @Import to the page. Since this page will be used to test both the C# and Visual Basic .NET versions of the component, import the namespaces for both. There are no drawbacks in doing this, as the different namespaces guarantee that there will be no name conflicts, and the compiler is smart enough not to load a namespace if there is nothing in the code that references it.

The Page_Load method will be working with the data provided by the business component. This test page will be used to test both components. Since each component has exactly the same interface (properties and methods), you can use the same local variable to represent the object to test. You can simply have two lines (of which one is commented out) in the page that create the object in the language you are testing with. In the test page, the first line creates the C# version of the component, and the second the Visual Basic .NET version:

 void Page_Load(Object sender, EventArgs evArgs){ // BusObjectCS.IBAProducts objProducts = new BusObjectCS.IBAProducts();    BusObjectVB.IBAProducts objProducts = new BusObjectVB.IBAProducts(); 

The component needs to know from where to retrieve its data. Pass in a database connection string as the DatabaseConnection property of the object. This value is simply stored in the page as a string. In a production environment, values like database connection strings are usually stored in the web.config file for the ASP.NET application:

 String dsn = "server=localhost;uid=sa;pwd=;database=IBuyAdventure";      objProducts.DatabaseConnection = dsn; 
Note

This example uses the database from the IBuyAdventure case study in Chapter 24. Follow the instructions in the readme.txt file in the database folder to set up the database in SQL Server 2000.

Once the instance of the class has been created and initialized with the proper database connection string, call the GetProductTypes method to return the list of product types. This data will be passed back as a DataSet , which in turn will serve as the data source for the asp:DataList element named productList that will actually display the data. By calling the DataBind method, the values in the DataSet will be rendered out into the DataList element:

 DataSet prodTypes = objProducts.GetProductTypes(); productList.DataSthece = prodTypes; productList.DataBind(); 

The asp:DataList will use a template to add some formatting to the data. The header and footer will simply begin and end the TABLE element being used to display the data. The ItemTemplate template sets up the format for each row in the table. In each row, there will be one column. This will be the product type string as a hyperlink to the page named displayProducts.aspx . The value of the product type will be passed to this page as part of the query string:

 <ItemTemplate>    <tr>       <td align="center">       <%# DataBinder.Eval(Container.DataItem, "ProductType",          "<a href=\"displayProducts.aspx?PID={0}\">{0}</a>") %>       </td>    </tr> </ ItemTemplate>      <FooterTemplate>    </table> </ FooterTemplate> 

When this page is displayed in the browser, it shows Figure 17-1:

click to expand
Figure 17-1:

When the user clicks on any of the hyperlinks , they will be taken to the detail test page called displayProducts.aspx . The page's sourcecode looks like this:

  <%@ Page Language="VB" Description="Component Test Program" Debug="true" %>   <%@ Import Namespace="BusObjectCS" %>   <%@ Import Namespace="BusObjectVB" %>   <%@ Import Namespace="System.Data" %>     <html>   <script runat="server">   Sub Page_Load(sender As Object, evtArgs As EventArgs)   Dim dsn As String = "server=localhost;uid=sa;pwd=;database=IBuyAdventure"     ' Dim objProducts = New BusObjectCS.IBAProducts()   Dim objProducts = New BusObjectVB.IBAProducts()     objProducts.DatabaseConnection = dsn     Dim products As DataSet = objProducts.GetProducts(Request.Params("PID"))     prodGrid.DataSthece = products   prodGrid.DataBind()     avgPrice.Text = objProducts.AveragePrice(Request.Params("PID")).ToString()   End sub     </script>     <body>     <h2>Average price for products in this category =   <asp:Label runat="server" id="avgPrice" /></h2>     <h3>List of Products</h3>     <asp:DataGrid id="prodGrid" runat="server" ShowHeader="False">   </asp:DataGrid>     </body>   </html>  

In this page, we use the final two methods of the business component. The first thing to do in the page, however, is to make sure that the proper namespaces are included with it:

 <%@ Page Language="VB" Description="Component Test Program" Debug="true" %> <%@ Import Namespace="BusObjectCS" %> <%@ Import Namespace="BusObjectVB" %> <%@ Import Namespace="System.Data" %> 

The methods in this page will be returning a double value from one and a DataSet object from the other. In order for the code on this page to understand how to work with the DataSet class, import the System.Data namespace into the page. As in the previous page, import both the C# and Visual Basic .NET versions of the components.

The first part of the Page_Load method is the same as the previous page. There are statements to create the object in both Visual Basic .NET and in C#, but only one of these will be active when the page is run, the other will be commented out. In fact, if for some reason both lines were left in the page when it was executed, the C# object will be created first. Then the VB object will be created and assigned to the same variable name. When that happens, the C# object will be marked for disposal, and will be destroyed the next time the CLR garbage collector is run. So the page ends up with a Visual Basic .NET component:

 Sub Page_Load(sender As Object, evtArgs As EventArgs)    Dim dsn As String = "server=localhost;uid=sa;pwd=;database=IBuyAdventure"      ' Dim objProducts = New BusObjectCS.IBAProducts()    Dim objProducts = New BusObjectVB.IBAProducts()         objProducts.DatabaseConnection = dsn 

Retrieve the list of products for the requested product type by using the GetProducts method. The product type has been passed in on the URL, and can be retrieved from the Request.Params collection. The information is returned as a DataSet object. Use the values in this collection as the source to populate the DataGrid :

 Dim products As DataSet = objProducts.GetProducts(Request.Params("PID"))      prodGrid.DataSthece = products prodGrid.DataBind() 

The other piece of information to display on the page is the average price for all of the products of this product type. This is retrieved by calling the AveragePrice function of the business component. This method will return a double value. Since the Text property of the Label control requires a string, convert the double value to a string using the ToString function:

 avgPrice.Text = objProducts.AveragePrice(Request.Params("PID")).ToString() End sub 

All of the difficult work in the page is done in the Page_Load method. All that needs to be done in the display portion is provide the proper server-side controls to display the information. The asp:Label element is a server-side control that is used to display the average selling price for the product type. The asp:DataGrid element will display a simple table that contains the list of products for that product type:

 <h2>Average price for products in this category =    <asp:Label runat="server" id="avgPrice" /></h2>         <h3>List of Products</h3>      <asp:DataGrid id="prodGrid" runat="server" ShowHeader="False"> </asp:DataGrid> 

On selecting a hyperlink from the previous page, the browser will show something like Figure 17-2:

click to expand
Figure 17-2:



Professional ASP. NET 1.1
Professional ASP.NET MVC 1.0 (Wrox Programmer to Programmer)
ISBN: 0470384611
EAN: 2147483647
Year: 2006
Pages: 243

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net