Vous êtes sur la page 1sur 70

c 


    

Scott Mitchell

June 2006

Download the ASPNET_Data_Tutorial_1_CS.exe sample code.

Download the ASPNET_Data_Tutorial_1_CS.exe sample code.

  c  




Introduction
Step 1: Creating a Web Project and Connecting to the Database
Step 2: Creating the Data Access Layer
Step 3: Adding Parameterized Methods to the Data Access Layer
Step 4: Inserting, Updating, and Deleting Data
Step 5: Completing the Data Access Layer
Summary

   
As Web developers, our lives revolve around working with data. We create databases to store the
data, code to retrieve and modify it, and web pages to collect and summarize it. This is the first
tutorial in a lengthy series that will explore techniques for implementing these common patterns
in ASP.NET 2.0. We'll start with creating a software architecture composed of a Data Access
Layer (DAL) using Typed DataSets, a Business Logic Layer (BLL) that enforces custom
business rules, and a presentation layer composed of ASP.NET pages that share a common page
layout. Once this backend groundwork has been laid, we'll move into reporting, showing how to
display, summarize, collect, and validate data from a web application. These tutorials are geared
to be concise and provide step-by-step instructions with plenty of screen shots to walk you
through the process visually. Each tutorial is available in C# and Visual Basic versions and
includes a download of the complete code used. (This first tutorial is quite lengthy, but the rest
are presented in much more digestible chunks.)

For these tutorials we'll be using a Microsoft SQL Server 2005 Express Edition version of the
Northwind database placed in the   directory. In addition to the database file, the
  folder also contains the SQL scripts for creating the database, in case you want to use
a different database version. These scripts can be also be downloaded directly from Microsoft, if
you'd prefer. If you use a different SQL Server version of the Northwind database, you will need
to update the 
    setting in the application's
  file. The web
application was built using Visual Studio 2005 Professional Edition as a file system-based Web
site project. However, all of the tutorials will work equally well with the free version of Visual
Studio 2005, Visual Web Developerhttp://msdn.microsoft.com/vstudio/express/vwd/.

In this tutorial we'll start from the very beginning and create the Data Access Layer (DAL),
followed by creating the Business Logic Layer (BLL) in the second tutorial, and working on
page layout and navigation in the third. The tutorials after the third one will build upon the
foundation laid in the first three. We've got a lot to cover in this first tutorial, so fire up Visual
Studio and let's get started!

`  
     
   
 
Before we can create our Data Access Layer (DAL), we first need to create a web site and setup
our database. Start by creating a new file system-based ASP.NET web site. To accomplish this,
go to the File menu and choose New Web Site, displaying the New Web Site dialog box. Choose
the ASP.NET Web Site template, set the Location drop-down list to File System, choose a folder
to place the web site, and set the language to C#.

!  "
  # $! ` %&'  `

This will create a new web site with a   ASP.NET page and an   folder.

With the web site created, the next step is to add a reference to the database in Visual Studio's
Server Explorer. By adding a database to the Server Explorer you can add tables, stored
procedures, views, and so on all from within Visual Studio. You can also view table data or
create your own queries either by hand or graphically via the Query Builder. Furthermore, when
we build the Typed DataSets for the DAL we'll need to point Visual Studio to the database from
which the Typed DataSets should be constructed. While we can provide this connection
information at that point in time, Visual Studio automatically populates a drop-down list of the
databases already registered in the Server Explorer.

The steps for adding the Northwind database to the Server Explorer depend on whether you want
to use the SQL Server 2005 Express Edition database in the   folder or if you have a
Microsoft SQL Server 2000 or 2005 database server setup that you want to use instead.

V     ! 

If you do not have a SQL Server 2000 or 2005 database server to connect to, or you simply want
to avoid having to add the database to a database server, you can use the SQL Server 2005
Express Edition version of the Northwind database that is located in the downloaded website's
  folder (
).

A database placed in the   folder is automatically added to the Server Explorer.
Assuming you have SQL Server 2005 Express Edition installed on your machine you should see
a node named NORTHWND.MDF in the Server Explorer, which you can expand and explore its
tables, views, stored procedure, and so on (see Figure 2).

The   folder can also hold Microsoft Access  files, which, like their SQL Server
counterparts, are automatically added to the Server Explorer. If you don't want to use any of the
SQL Server options, you can always download a Microsoft Access version of the Northwind
database file and drop into the   directory. Keep in mind, however, that Access
databases aren't as feature-rich as SQL Server, and aren't designed to be used in web site
scenarios. Furthermore, a couple of the 35+ tutorials will utilize certain database-level features
that aren't supported by Access.

     (` ` ) *+++*++, 


` ) 

Alternatively, you may connect to a Northwind database installed on a database server. If the
database server does not already have the Northwind database installed, you first must add it to
database server by running the installation script included in this tutorial's download or by
downloading the SQL Server 2000 version of Northwind and installation script directly from
Microsoft's Web site.

Once you have the database installed, go to the Server Explorer in Visual Studio, right-click on
the Data Connections node, and choose Add Connection. If you don't see the Server Explorer go
to the View / Server Explorer, or hit Ctrl+Alt+S. This will bring up the Add Connection dialog
box, where you can specify the server to connect to, the authentication information, and the
database name. Once you have successfully configured the database connection information and
clicked the OK button, the database will be added as a node underneath the Data Connections
node. You can expand the database node to explore its tables, views, stored procedures, and so
on.

!  *"
  -  ` ) .# $ 

` * 
     
When working with data one option is to embed the data-specific logic directly into the
presentation layer (in a web application, the ASP.NET pages make up the presentation layer).
This may take the form of writing ADO.NET code in the ASP.NET page's code portion or using
the SqlDataSource control from the markup portion. In either case, this approach tightly couples
the data access logic with the presentation layer. The recommended approach, however, is to
separate the data access logic from the presentation layer. This separate layer is referred to as the
Data Access Layer, DAL for short, and is typically implemented as a separate Class Library
project. The benefits of this layered architecture are well documented (see the "Further
Readings" section at the end of this tutorial for information on these advantages) and is the
approach we will take in this series.

All code that is specific to the underlying data source ± such as creating a connection to the
database, issuing   , !, "#, and   commands, and so on ± should be
located in the DAL. The presentation layer should not contain any references to such data access
code, but should instead make calls into the DAL for any and all data requests. Data Access
Layers typically contain methods for accessing the underlying database data. The Northwind
database, for example, has #  and   tables that record the products for sale and
the categories to which they belong. In our DAL we will have methods like:

O? Ë  $%&'which will return information about all of the categories
O? Ë# $%, which will return information about all of the products
O? Ë# ()  )!$Ô %, which will return all products that belong to
a specified category
O? Ë# ()# !$°
Ô%, which will return information about a particular
product

These methods, when invoked, will connect to the database, issue the appropriate query, and
return the results. How we return these results is important. These methods could simply return a
DataSet or DataReader populated by the database query, but ideally these results should be
returned using strongly-typed objects. A strongly-typed object is one whose schema is rigidly
defined at compile time, whereas the opposite, a loosely-typed object, is one whose schema is
not known until runtime.

For example, the DataReader and the DataSet (by default) are loosely-typed objects since their
schema is defined by the columns returned by the database query used to populate them. To
access a particular column from a loosely-typed DataTable we need to use syntax like:
   *+ ,+-Ô
 -,. The DataTable's loose typing in this example is
exhibited by the fact that we need to access the column name using a string or ordinal index. A
strongly-typed DataTable, on the other hand, will have each of its columns implemented as
properties, resulting in code that looks like:    *+ ,Ô
 .

To return strongly-typed objects, developers can either create their own custom business objects
or use Typed DataSets. A business object is implemented by the developer as a class whose
properties typically reflect the columns of the underlying database table the business object
represents. A Typed DataSet is a class generated for you by Visual Studio based on a database
schema and whose members are strongly-typed according to this schema. The Typed DataSet
itself consists of classes that extend the ADO.NET DataSet, DataTable, and DataRow classes. In
addition to strongly-typed DataTables, Typed DataSets now also include TableAdapters, which
are classes with methods for populating the DataSet's DataTables and propagating modifications
within the DataTables back to the database.

# For more information on the advantages and disadvantages of using Typed DataSets
versus custom business objects, refer to Designing Data Tier Components and Passing Data
Through Tiers.

We'll use strongly-typed DataSets for these tutorials' architecture. Figure 3 illustrates the
workflow between the different layers of an application that uses Typed DataSets.
!  /" 
 0    

  c `  c  

To begin creating our DAL, we start by adding a Typed DataSet to our project. To accomplish
this, right-click on the project node in the Solution Explorer and choose Add a New Item. Select
the DataSet option from the list of templates and name it  .* .

!  1"
 # $` -  

After clicking Add, when prompted to add the DataSet to the    folder, choose Yes. The
Designer for the Typed DataSet will then be displayed, and the TableAdapter Configuration
Wizard will start, allowing you to add your first TableAdapter to the Typed DataSet.
A Typed DataSet serves as a strongly-typed collection of data; it is composed of strongly-typed
DataTable instances, each of which is in turn composed of strongly-typed DataRow instances.
We will create a strongly-typed DataTable for each of the underlying database tables that we
need to work with in this tutorials series. Let's start with creating a DataTable for the # 
table.

Keep in mind that strongly-typed DataTables do not include any information on how to access
data from their underlying database table. In order to retrieve the data to populate the DataTable,
we use a TableAdapter class, which functions as our Data Access Layer. For our # 
DataTable, the TableAdapter will contain the methods ± Ë# $%,
Ë# ()  )!$Ô %, and so on ± that we'll invoke from the presentation
layer. The DataTable's role is to serve as the strongly-typed objects used to pass data between the
layers.

The TableAdapter Configuration Wizard begins by prompting you to select which database to
work with. The drop-down list shows those databases in the Server Explorer. If you did not add
the Northwind database to the Server Explorer, you can click the New Connection button at this
time to do so.

!  ,"
  # $  % &$ 
After selecting the database and clicking Next, you'll be asked if you want to save the connection
string in the
  file. By saving the connection string you'll avoid having it hard coded
in the TableAdapter classes, which simplifies things if the connection string information changes
in the future. If you opt to save the connection string in the configuration file it's placed in the
/   0 section, which can be optionally encrypted for improved security or
modified later through the new ASP.NET 2.0 Property Page within the IIS GUI Admin Tool,
which is more ideal for administrators.

!  2"`)  
  ` Ä 

Next, we need to define the schema for the first strongly-typed DataTable and provide the first
method for our TableAdapter to use when populating the strongly-typed DataSet. These two
steps are accomplished simultaneously by creating a query that returns the columns from the
table that we want reflected in our DataTable. At the end of the wizard we'll give a method name
to this query. Once that's been accomplished, this method can be invoked from our presentation
layer. The method will execute the defined query and populate a strongly-typed DataTable.

To get started defining the SQL query we must first indicate how we want the TableAdapter to
issue the query. We can use an ad-hoc SQL statement, create a new stored procedure, or use an
existing stored procedure. For these tutorials we'll use ad-hoc SQL statements. Refer to Brian
Noyes's article, Build a Data Access Layer with the Visual Studio 2005 DataSet Designer for an
example of using stored procedures.
!  3"  V  &4` ` % 

At this point we can type in the SQL query by hand. When creating the first method in the
TableAdapter you typically want to have the query return those columns that need to be
expressed in the corresponding DataTable. We can accomplish this by creating a query that
returns all columns and all rows from the #  table:
!  5"6   `    c 77

Alternatively, use the Query Builder and graphically construct the query, as shown in Figure 9.
!  8"
    9 :    6

After creating the query, but before moving onto the next screen, click the Advanced Options
button. In Web Site Projects, "Generate Insert, Update, and Delete statements" is the only
advanced option selected by default; if you run this wizard from a Class Library or a Windows
Project the "Use optimistic concurrency" option will also be selected. Leave the "Use optimistic
concurrency" option unchecked for now. We'll examine optimistic concurrency in future
tutorials.
!  +"`  ;  <9    :V :     % <;

After verifying the advanced options, click Next to proceed to the final screen. Here we are
asked to select which methods to add to the TableAdapter. There are two patterns for populating
data:

O? !c ± with this approach a method is created that takes in a DataTable as a


parameter and populates it based on the results of the query. The ADO.NET DataAdapter
class, for example, implements this pattern with its $% method.
O? 0   c ± with this approach the method creates and fills the DataTable for
you and returns it as the methods return value.

You can have the TableAdapter implement one or both of these patterns. You can also rename
the methods provided here. Let's leave both checkboxes checked, even though we'll only be
using the latter pattern throughout these tutorials. Also, let's rename the rather generic Ë
method to Ë# .

If checked, the final checkbox, "GenerateDBDirectMethods," creates ! $%, " $%, and
$% methods for the TableAdapter. If you leave this option unchecked, all updates will
need to be done through the TableAdapter's sole " $% method, which takes in the Typed
DataSet, a DataTable, a single DataRow, or an array of DataRows. (If you've unchecked the
"Generate Insert, Update, and Delete statements" option from the advanced properties in Figure 9
this checkbox's setting will have no effect.) Let's leave this checkbox selected.
!  "
  (  #% %Ë Ë 


Complete the wizard by clicking Finish. After the wizard closes we are returned to the DataSet
Designer, which shows the DataTable we just created. You can see the list of columns in the
#  DataTable (# !, # , and so on), as well as the methods of the
#   ($% and Ë# $%).
!  *"c 
c  
     )    
c ` 

At this point we have a Typed DataSet with a single DataTable ( .* # ) and a
strongly-typed DataAdapter class ( .*  #  ) with a
Ë# $% method. These objects can be used to access a list of all products from code
like:

 .*  #  '   '1' *''


 .*  #  $%2'
 .* # '  2'
'
 '1'   Ë# $%2'
'
 .'$ .* #  *'   *' '  %'
'''' 
$-# 3'-'4'   *# '4'-/'50-%2'

This code did not require us to write one bit of data access-specific code. We did not have to
instantiate any ADO.NET classes, we didn't have to refer to any connection strings, SQL queries,
or stored procedures. Instead, the TableAdapter provides the low-level data access code for us.

Each object used in this example is also strongly-typed, allowing Visual Studio to provide
IntelliSense and compile-time type checking. And best of all the DataTables returned by the
TableAdapter can be bound to ASP.NET data Web controls, such as the GridView, DetailsView,
DropDownList, CheckBoxList, and several others. The following example illustrates binding the
DataTable returned by the Ë# $% method to a GridView in just a scant three lines of
code within the #  event handler.

 "7

/67'#'  1- 8-' 9 


 1--' 1-#  -'
! .1-# -'60'
'
/: ;#'.'#"( ! '-<55
= 55'>  '?@'  55-'
-. 355****= 55.?55.?<  -0'
'
/.' 1-. 355****= 5?AAA5.-'0'
/.' 1-9-0'
''''/0B*''# ' ''ËB*/50'
''''/ C'.1-)-'1-).-') 1-5-'50'
/5.0'
/ )0'
''''/ '1- ?-' 1-9-0'
''''/90'
''''''''/.?0'
'''''''''''''# /5.?0'
''''''''/ 0'
''''''''''''/ 3ËB*'!1-ËB*?-' 1-9-'
 1-
  )-0'
'''''''''''''''/ )'  1- )-'50'
'''''''''''''''/   *)'  1-   *)-'50'
''''''''''''/5 3ËB*0'
''''''''''''D  2/5 0'
'''''
''''/590'
''''/5 0'
/5 )0'
/5.0'

 "7"

 ')2'
 ')2'
 ')  2'
 ')  2'
 ')
2'
 ')
)2'
 ')
"!2'
 ')
"!
  2'
 ')
"!
  
#2'
 ')
"!   2'
 ' .*  2'
'
' ''# '3')
"!#'
E'
''''  '9 '# $ F' &'9 '%'
''''E'
''''''''#  '   '1' *'#  $%2'
''''''''ËB*? '1'   Ë# $%2'
''''''''ËB*?( $%2'
''''G'
G'

!  /"c    9 $

While this example required that we write three lines of code in our ASP.NET page's # 
event handler, in future tutorials we'll examine how to use the ObjectDataSource to declaratively
retrieve the data from the DAL. With the ObjectDataSource we'll not have to write any code and
will get paging and sorting support as well!

` /  %  = (    


 
At this point our #   class has but one method, Ë# $%, which
returns all of the products in the database. While being able to work with all products is
definitely useful, there are times when we'll want to retrieve information about a specific
product, or all products that belong to a particular category. To add such functionality to our
Data Access Layer we can add parameterized methods to the TableAdapter.

Let's add the Ë# ()  )!$Ô % method. To add a new method to the
DAL, return to the DataSet Designer, right-click in the #   section, and
choose Add Query.
!  1"0 &
> c   
  

We are first prompted about whether we want to access the database using an ad-hoc SQL
statement or a new or existing stored procedure. Let's choose to use an ad-hoc SQL statement
again. Next, we are asked what type of SQL query we'd like to use. Since we want to return all
products that belong to a specified category, we want to write a    statement which returns
rows.
!  ,"
 
  `  ` %   0   0$

The next step is to define the SQL query used to access the data. Since we want to return only
those products that belong to a particular category, I use the same    statement from
Ë# $%, but add the following
 clause:
'  )!'1'7  )!. The
7  )! parameter indicates to the TableAdapter wizard that the method we're creating will
require an input parameter of the corresponding type (namely, a nullable integer).
!  2"6   ; 0     `  
 

In the final step we can choose which data access patterns to use, as well as customize the names
of the methods generated. For the Fill pattern, let's change the name to ()  )! and
for the return a DataTable return pattern (the ˝ methods), let's use
Ë# ()  )!.
!  3"
  #%  c  (  

After completing the wizard, the DataSet Designer includes the new TableAdapter methods.
!  5"c  
 #$   
 

Take a moment to add a Ë# ()# !$°


Ô% method using the same
technique.

These parameterized queries can be tested directly from the DataSet Designer. Right-click on the
method in the TableAdapter and choose Preview Data. Next, enter the values to use for the
parameters and click Preview.

!  8"c   '    ' )  


  ` $

With the Ë# ()  )!$Ô % method in our DAL, we can now create an
ASP.NET page that displays only those products in a specified category. The following example
shows all products that are in the Beverages category, which have a  )! of 1.

' )  "7

/67'#'  1- 8-' 9 


 1--' 1-(9 -'
! .1-(9-'60'
'
/: ;#'.'#"( ! '-<55
= 55'>  '?@'  55-'
-. 355****= 55.?55.?<  -0'
'
/.' 1-. 355****= 5?AAA5.-'0'
/.' 1-9-0'
''''/0" '#/50'
''''/ C'.1-)-'1-).-') 1-5-'50'
/5.0'
/ )0'
''''/ '1- ?-' 1-9-0'
''''/90'
''''''''/.?0(9/5.?0'
''''''''/ 0'
''''''''''''/ 3ËB*'!1-ËB*?-' 1-9-'
 1-
  )-0'
'''''''''''''''/ )'  1- )-'50'
'''''''''''''''/   *)'  1-   *)-'50'
''''''''''''/5 3ËB*0'
''''''''''''D  2/5 0'
''''/590'
''''/5 0'
/5 )0'
/5.0'

' )  "7"

 ')2'
 ')2'
 ')  2'
 ')  2'
 ')
2'
 ')
)2'
 ')
"!2'
 ')
"!
  2'
 ')
"!
  
#2'
 ')
"!   2'
 ' .*  2'
'
' ''(9'3')
"!#'
E'
''''  '9 '# $ F' &'9 '%'
''''E'
''''''''#  '   '1' *'#  $%2'
''''''''ËB*? '1'   Ë# ()  )!$?%2'
''''''''ËB*?( $%2'
''''G'
G'
!  *+"c     ' )  
   

` 1    :V :    


There are two patterns commonly used for inserting, updating, and deleting data. The first
pattern, which I'll call the database direct pattern, involves creating methods that, when invoked,
issue an !, "#, or   command to the database that operates on a single database
record. Such methods are typically passed in a series of scalar values (integers, strings, Booleans,
DateTimes, and so on) that correspond to the values to insert, update, or delete. For example,
with this pattern for the #  table the delete method would take in an integer parameter,
indicating the # ! of the record to delete, while the insert method would take in a string
for the # , a decimal for the " #, an integer for the "   C, and so on.
!  *"6   :V :    0 ? `   %%  

The other pattern, which I'll refer to as the batch update pattern, is to update an entire DataSet,
DataTable, or collection of DataRows in one method call. With this pattern a developer deletes,
inserts, and modifies the DataRows in a DataTable and then passes those DataRows or
DataTable into an update method. This method then enumerates the DataRows passed in,
determines whether or not they've been modified, added, or deleted (via the DataRow's RowState
property value), and issues the appropriate database request for each record.

!  **"
  `   = $     V (  
 )> 

The TableAdapter uses the batch update pattern by default, but also supports the DB direct
pattern. Since we selected the "Generate Insert, Update, and Delete statements" option from the
Advanced Properties when creating our TableAdapter, the #   contains an
" $% method, which implements the batch update pattern. Specifically, the TableAdapter
contains an " $% method that can be passed the Typed DataSet, a strongly-typed DataTable,
or one or more DataRows. If you left the "GenerateDBDirectMethods" checkbox checked when
first creating the TableAdapter the DB direct pattern will also be implemented via ! $%,
" $%, and $% methods.

Both data modification patterns use the TableAdapter's !   , "   , and
   properties to issue their !, "#, and   commands to the
database. You can inspect and modify the !   , "   , and
   properties by clicking on the TableAdapter in the DataSet Designer and then
going to the Properties window. (Make sure you have selected the TableAdapter, and that the
#   object is the one selected in the drop-down list in the Properties
window.)

!  */"c c   h   :    :     


  

To examine or modify any of these database command properties, click on the  
subproperty, which will bring up the Query Builder.
!  *1"
    h ` :  :    ` %    '  

The following code example shows how to use the batch update pattern to double the price of all
products that are not discontinued and that have 25 units in stock or less:

 .*  #  '   '1' *''


 .*  #  $%2''
'
55' '.'  &' '' '''' '  ' '
55'.''HI'' ' C' ''
 .* # '  '1'   Ë# $%2'
 .'$ .* #  *'  ' '  %'
''''$:    'DD'  " !  C'/1'HI%'
''''''  " #'J1'H2'
'
55'" '.'  '
  " $  %2'

The code below illustrates how to use the DB direct pattern to programmatically delete a
particular product, then update one, and then add a new one:

 .*  #  '   '1' *'


 .*  #  $%2''
'
55''.'  '*.'# !'='
  $=%2'
'
55'" ' .'$# !' '?%&' '.'"  ' '?I'
  " $- .-&'?&'?&'-?@' ''H@'-&'?K@&'=A&''
?I&'?@&'&'?%2'
'
55''' *'  '
  ! $-*'# -&'?&'?&'-?H' ' ' -&''
?LAI&'?I&'@&'?@&'%2'

  
%  :V :    (  

The ! $%, " $%, and $% methods created by the DB direct method can be a bit
cumbersome, especially for tables with many columns. Looking at the previous code example,
without IntelliSense's help it's not particularly clear what #  table column maps to each
input parameter to the " $% and ! $% methods. There may be times when we only
want to update a single column or two, or want a customized ! $% method that will,
perhaps, return the value of the newly inserted record's !!; (auto-increment) field.

To create such a custom method, return to the DataSet Designer. Right-click on the
TableAdapter and choose Add Query, returning to the TableAdapter wizard. On the second
screen we can indicate the type of query to create. Let's create a method that adds a new product
and then returns the value of the newly added record's # !. Therefore, opt to create an
! query.
!  *,"
  (  # $0$ 
c

On the next screen the !   's   appears. Augment this query by adding
  ' #!!;$% at the end of the query, which will return the last identity value
inserted into an !!; column in the same scope. (See the technical documentation for more
information about  #!!;$% and why you probably want to use SCOPE_IDENTITY()
in lieu of @@IDENTITY.) Make sure that you end the ! statement with a semi-colon
before adding the    statement.
!  *2" %   0    ` h h


Finally, name the new method ! # .


!  *3"`  # $(  #% h 


When you return to the DataSet Designer you'll see that the #   contains a
new method, ! # . If this new method doesn't have a parameter for each column in
the #  table, chances are you forgot to terminate the ! statement with a semi-colon.
Configure the ! #  method and ensure you have a semi-colon delimiting the !
and    statements.

By default, insert methods issue non-query methods, meaning that they return the number of
affected rows. However, we want the ! #  method to return the value returned by the
query, not the number of rows affected. To accomplish this, adjust the ! #  method's
  property to .
!  *5"
    
   ` 

The following code shows this new ! #  method in action:

 .*  #  '   '1' *'


 .*  #  $%2'
'
55''' *'  '
 ' *  !'1' 9 ! =H$   ! # $-*'
# -&'?&'?&'-?H' ' ' -&'?LAI&'?@&'@&'?@&'%%2'
'
55' ' '. .&''.'  '
  $ *  !%2'

` , 
%     
Note that the #   class returns the  )! and  ! values
from the #  table, but doesn't include the  ) column from the  
table or the   ) column from the   table, although these are likely the
columns we want to display when showing product information. We can augment the
TableAdapter's initial method, Ë# $%, to include both the  ) and
  ) column values, which will update the strongly-typed DataTable to include these
new columns as well.

This can present a problem, however, as the TableAdapter's methods for inserting, updating, and
deleting data are based off of this initial method. Fortunately, the auto-generated methods for
inserting, updating, and deleting are not affected by subqueries in the    clause. By taking
care to add our queries to   and   as subqueries, rather than M!s, we'll
avoid having to rework those methods for modifying data. Right-click on the Ë# $%
method in the #   and choose Configure. Then, adjust the    clause so
that it looks like:

  '''''# !&'# &' !&'  )!&''


N )#" &'" #&'" !  C&'"  &'  9&'
  &'
$  '  )''  '
'    )!'1'
#   )!''  )&'$  '   )'' ''

'  !'1'#  !%'' '


'''''''''# '

!  *8"V  `  ` %  Ë 



(  

After updating the Ë# $% method to use this new query the DataTable will include two
new columns:  ) and  .
!  /+"c 
c  c$# $
 % 

Take a moment to update the    clause in the Ë# ()  )!$Ô %
method as well.

If you update the Ë# $%   using M! syntax the DataSet Designer won't be able
to auto-generate the methods for inserting, updating, and deleting database data using the DB
direct pattern. Instead, you'll have to manually create them much like we did with the
! #  method earlier in this tutorial. Furthermore, you'll manually have to provide the
!   , "   , and    property values if you want to use the
batch updating pattern.

  0 %  c  

Up until now, we've only looked at working with a single TableAdapter for a single database
table. However, the Northwind database contains several related tables that we'll need to work
with in our web application. A Typed DataSet can contain multiple, related DataTables.
Therefore, to complete our DAL we need to add DataTables for the other tables we'll be using in
these tutorials. To add a new TableAdapter to a Typed DataSet, open the DataSet Designer,
right-click in the Designer, and choose Add / TableAdapter. This will create a new DataTable
and TableAdapter and walk you through the wizard we examined earlier in this tutorial.

Take a few minutes to create the following TableAdapters and methods using the following
queries. Note that the queries in the #   include the subqueries to grab each
product's category and supplier names. Additionally, if you've been following along, you've
already added the #   class's Ë# $% and
Ë# ()  )!$Ô % methods.

 c  

9  

  '''''# !&'# &' !&'  )!&''


N )#" &'" #&'" !  C&'"  &'  9&''
  '&'$  '  )''  '
''
   )!'1'# # !%''  )&'$  ''
  )'' '
'  !'1''
#  !%'' '
'''''''''# '

9  '
 

  '''''# !&'# &' !&'  )!&''


N )#" &'" #&'" !  C&'"  &'  9&''
  '&'$  '  )''  '
''
   )!'1'# # !%''  )&'$  ''
  )'' '
'  !'1''
#  !%'' '
'''''''''# '

''''''  )!'1'7  )!'

9  '`  

  '''''# !&'# &' !&'  )!&''


N )#" &'" #&'" !  C&'"  &'  9&''
  '&'$  '  )''  '
''
   )!'1'# # !%''  )&'$  ''
  )'' '
'  !'1''
#  !%'' '
'''''''''# '

' !'1'7 !'

9  ' 

  '''''# !&'# &' !&'  )!&''


N )#" &'" #&'" !  C&'"  &'  9&''
  '&'$  '  )''  '
''
   )!'1'# # !%''  )&'$  ''
  )'' '
'  !'1''
#  !%'' '
'''''''''# '

'# !'1'7# !'

  c  

Ë  '


  '''''  )!&'  )&'  '
'''''''''  '

9 
 '
 

  '''''  )!&'  )&'  '


'''''''''  '

'  )!'1'7  )!'

`  c  

Ë '
  ''''' !&'   )&'&' )&'  )&'#. '
''''''''' '

GetSuppliersByCountry

  ''''' !&'   )&'&' )&'  )&'#. '


''''''''' '

'  )'1'7  )'

9 `  '`  

  ''''' !&'   )&'&' )&'  )&'#. '


''''''''' '

' !'1'7 !'

6% c  

Ë  )'
  '''''  )!&' &'&'&' &'  &'
 )'
'''''''''  )'

9 6% '(  

  '''''  )!&' &'&'&' &'  &'


 )'
'''''''''  )'

'  '1'7 !'

9 6% '6% 

  '''''  )!&' &'&'&' &'  &'


 )'
'''''''''  )'

'  )!'1'7  )!'

!  /"c `     ! c  4) '  

 
%
  
The TableAdapters and DataTables added to the Typed DataSet are expressed as an XML
Schema Definition file ( .* ). You can view this schema information by right-
clicking on the  .*  file in the Solution Explorer and choosing View Code.

!  /*"c @(` %   @`!  # $ c ` 

This schema information is translated into C# or Visual Basic code at design time when
compiled or at runtime (if needed), at which point you can step through it with the debugger. To
view this auto-generated code go to the Class View and drill down to the TableAdapter or Typed
DataSet classes. If you don't see the Class View on your screen, go to the View menu and select
it from there, or hit Ctrl+Shift+C. From the Class View you can see the properties, methods, and
events of the Typed DataSet and TableAdapter classes. To view the code for a particular method,
double-click the method name in the Class View or right-click on it and choose Go To
Definition.
!  //"    &9  
 `   9c   % 

 $

While auto-generated code can be a great time saver, the code is often very generic and needs to
be customized to meet the unique needs of an application. The risk of extending auto-generated
code, though, is that the tool that generated the code might decide it's time to "regenerate" and
overwrite your customizations. With .NET 2.0's new partial class concept, it's easy to split a class
across multiple files. This enables us to add our own methods, properties, and events to the auto-
generated classes without having to worry about Visual Studio overwriting our customizations.

To demonstrate how to customize the DAL, let's add a Ë# $% method to the
  * class. The   * class represents a single record in the   table;
each supplier can provider zero to many products, so Ë# $% will return those products
of the specified supplier. To accomplish this create a new class file in the    folder
named   * and add the following code:

 ')2'
 ')2'
 ' .*  2'
'
' '' .* '
E'
'''' ' ''  *'
''''E'
'''''''' ' .* # 'Ë# $%'
''''''''E'
''''''''''''#  '   '1' *'
#  $%2'
'''''''''''' '   Ë# () !$. !%2'
''''''''G'
''''G'
G'

This partial class instructs the compiler that when building the  .*   * class
to include the Ë# $% method we just defined. If you build your project and then return
to the Class View you'll see Ë# $% now listed as a method of
 .*   *.
!  /1"c Ë 

(  #$    `
  


The Ë# $% method can now be used to enumerate the set of products for a particular
supplier, as the following code shows:

 .*    '  '1' *''


 .*    $%2'
'
55'Ë'' '.' '
 .*  ' '1'  Ë $%2'
'
55' '.' '
 .'$ .*   *' ' ' %'
E'
'''' 
$- 3'-'4'    )%2'
'''' 
$-/0-%2'
'
''''55' '.'  ' '.' '
'''' .* # '  '1' Ë# $%2'
'''' .'$ .* #  *'  ' '  %'
'''''''' 
$-/0-'4'  # '4'-/50-%2'
'
'''' 
$-/50/ 0D  2/5 0-%2'
G'

This data can also be displayed in any of ASP.NET's data Web controls. The following page uses
a GridView control with two fields:
O? A BoundField that displays the name of each supplier, and
O? A TemplateField that contains a BulletedList control that is bound to the results returned
by the Ë# $% method for each supplier.

We'll examine how to display such master-detail reports in future tutorials. For now, this
example is designed to illustrate using the custom method added to the
 .*   * class.

`    "7

/67'#'  1- 8-' 9 


 1--''
1-  #  -'! .1-  # -'60'
'
/: ;#'.'#"( ! '-<55
= 55'>  '?@'  55-'
-. 355****= 55.?55.?<  -0'
'
/.' 1-. 355****= 5?AAA5.-'0'
/.' 1-9-0'
''''/0" '#/50'
''''/ C'.1-)-'1-).-') 1-5-'50'
/5.0'
/ )0'
''''/ '1- ?-' 1-9-0'
''''/90'
''''''''/.?0'
'''''''''''' ' '.'# /5.?0'
''''''''/ 0'
''''''''''''/ 3ËB*'!1-ËB*?-' 1-9-'
 Ë   1--'  1-
  )-0'
''''''''''''''''/ )'  1- )-'50'
''''''''''''''''/   *)'  1-   *)-'50'
''''''''''''''''/  0'
''''''''''''''''''''/ 3(  '1-   )-'
1- -'50'
''''''''''''''''''''/ 3 ' 1-# -0'
''''''''''''''''''''''''/! 0'
''''''''''''''''''''''''''''/ 3( '!1-( ?-'
 1-9-' 1O/68'
$$ .*   *%$$) *B*%'
 !% *%Ë# $%'60O'
''''''''''''''''''''''''''''''''''''1-# -0'
''''''''''''''''''''''''''''/5 3( 0'
''''''''''''''''''''''''/5! 0'
''''''''''''''''''''/5 3 0'
''''''''''''''''/5  0'
''''''''''''/5 3ËB*0'
''''''''''''D  2/5 0'
'''''
''''/590'
''''/5 0'
/5 )0'
/5.0'

`    "7"


 ')2'
 ')2'
 ')  2'
 ')  2'
 ')
2'
 ')
)2'
 ')
"!2'
 ')
"!
  2'
 ')
"!
  
#2'
 ')
"!   2'
 ' .*  2'
'
' ''  # '3')
"!#'
E'
''''  '9 '# $ F' &'9 '%'
''''E'
''''''''  '  '1' *'  $%2'
''''''''ËB*? '1'  Ë $%2'
''''''''ËB*?( $%2'
''''G'
G'
!  /,"c `  .
% #%     
 % :c   
 0 

` %%
When building a web application creating the DAL should be one of your first steps, occurring
before you start creating your presentation layer. With Visual Studio, creating a DAL based on
Typed DataSets is a task that can be accomplished in 10-15 minutes without writing a line of
code. The tutorials moving forward will build upon this DAL. In the next tutorial we'll define a
number of business rules and see how to implement them in a separate Business Logic Layer.

Happy Programming!

?
c * 
  '    

Scott Mitchell

June 2006

Download the ASPNET_Data_Tutorial_2_CS.exe sample code.

Download the ASPNET_Data_Tutorial_2_CS.exe sample code.

  c * 




Introduction
Step 1: Creating the BLL Classes
Step 2: Accessing the Typed DataSets Through the BLL Classes
Step 3: Adding Field-Level Validation to the DataRow Classes
Step 4: Adding Custom Business Rules to the BLL's Classes
Summary

   
The Data Access Layer (DAL) created in the first tutorial cleanly separates the data access logic
from the presentation logic. However, while the DAL cleanly separates the data access details
from the presentation layer, it does not enforce any business rules that may apply. For example,
for our application we may want to disallow the  )! or  ! fields of the
#  table to be modified when the    field is set to 1, or we might want to
enforce seniority rules, prohibiting situations in which an employee is managed by someone who
was hired after them. Another common scenario is authorization ± perhaps only users in a
particular role can delete products or can change the " # value.

In this tutorial we'll see how to centralize these business rules into a Business Logic Layer (BLL)
that serves as an intermediary for data exchange between the presentation layer and the DAL. In
a real-world application, the BLL should be implemented as a separate Class Library project;
however, for these tutorials we'll implement the BLL as a series of classes in our   
folder in order to simplify the project structure. Figure 1 illustrates the architectural relationships
among the presentation layer, BLL, and DAL.
!  "c '`       %    
% '  0  

`  
   '
 
Our BLL will be composed of four classes, one for each TableAdapter in the DAL; each of these
BLL classes will have methods for retrieving, inserting, updating, and deleting from the
respective TableAdapter in the DAL, applying the appropriate business rules.

To more cleanly separate the DAL- and BLL-related classes, let's create two subfolders in the
   folder,  and ( . Simply right-click on the    folder in the Solution
Explorer and choose New Folder. After creating these two folders, move the Typed DataSet
created in the first tutorial into the  subfolder.

Next, create the four BLL class files in the ( subfolder. To accomplish this, right-click on the
( subfolder, choose Add a New Item, and choose the Class template. Name the four classes
# ( ,  ( ,  ( , and   )( .
!  *"! # $
    ! 

Next, let's add methods to each of the classes to simply wrap the methods defined for the
TableAdapters from the first tutorial. For now, these methods will just call directly into the DAL;
we'll return later to add any needed business logic.

# If you are using Visual Studio Standard Edition or above (that is, you're not using Visual
Web Developer), you can optionally design your classes visually using the Class Designer. Refer
to the Class Designer Blog for more information on this new feature in Visual Studio.

For the # ( class we need to add a total of seven methods:

O? Ë# $% ± returns all products


O? Ë# ()# !$°
Ô% ± returns the product with the specified product
ID
O? Ë# ()  )!$Ô % ± returns all products from the specified
category
O? Ë# () $
°° % ± returns all products from the specified
supplier
O? # $°
Ô 
°° Ô 


Ô
Ô
      Ô
 % ±
inserts a new product into the database using the values passed-in; returns the # !
value of the newly inserted record
O? " # $°
Ô 
°° Ô 


Ô
Ô
      Ô
 
°
Ô% ± updates an existing product in the database using the passed-in values;
returns  if precisely one row was updated,  otherwise
O? # $°
Ô% ± deletes the specified product from the database

 '"
 ')2'
 ')2'
 ')  2'
 ')
2'
 ')
)2'
 ')
"!2'
 ')
"!
  2'
 ')
"!
  
#2'
 ')
"!   2'
 ' .*  2'
'
+)    F,'
''# ( '
E'
'''' 9'#  '   '1' 2'
''''  '#  ' '
''''E'
'''''''''E'
'''''''''''''$   '11' %'
''''''''''''''''   '1' *'#  $%2'
'
'''''''''''' '   2''
''''''''G'
''''G'
'
'
+)    F. $)    
F. ) &'%,'
'''' ' .* # 'Ë# $%'
''''E'''''''''
'''''''' ' Ë# $%2'
''''G'
'
''''
+)    F. $)    
F. ) &'%,'
'''' ' .* # 'Ë# ()# !$ '  !%'
''''E'
'''''''' ' Ë# ()# !$  !%2'
''''G'
'
+)    F. $)    
F. ) &'%,'
'''' ' .* # 'Ë# ()  )!$ '
 )!%'
''''E'
'''''''' ' Ë# ()  )!$ )!%2'
''''G'
'
+)    F. $)    
F. ) &'%,'
'''' ' .* # 'Ë# () !$ '
 !%'
''''E'
'''''''' ' Ë# () !$ !%2'
''''G'
''''
+)    F. $)    
F. ) ! &'%,'
'''' ' '# $ '  &' P' !&' P'
 )!&' 'Q )#" &''
''''''''''''''''''''''''''P' #&'. P' !  C&'. P'
  &'. P'  9&''
'''''''''''''''''''''''''' '  %'
''''E'
''''''''55' '' *'#  *'  '
'''''''' .* # '  '1' *'
 .* # $%2'
'''''''' .* #  *'  '1'  *#  *$%2'
'
''''''''  # '1'  2'
'''''''''$ !'11' %'   !$%2''
  !'1' !B2'
'''''''''$ )!'11' %'    )!$%2''
   )!'1' )!B2'
'''''''''$Q )#" '11' %'  N )#" $%2''
 N )#" '1'Q )#" 2'
'''''''''$ #'11' %'  " #$%2''
 " #'1' #B2'
'''''''''$ !  C'11' %'  " !  C$%2''
 " !  C'1' !  CB2'
'''''''''$  '11' %'  "  $%2''
 "  '1'  B2'
'''''''''$  9'11' %'    9$%2''
   9'1'  9B2'
''''''''    '1'  2'
'
''''''''55''.' *'  '
''''''''  #  *$  %2'
'''''''' ' *'1' " $  %2'
'
''''''''55' ''' )' ' *'*' &' .*''
'''''''' ' *'11'?2'
''''G'
'
''''
+)    F. $)    
F. ) " &'%,'
'''' ' '" # $ '  &' P' !&' P'
 )!&' 'Q )#" &'
''''''''''''''''''''''''''''''P' #&'. P' !  C&'. P'
  &'. P'  9&'
'''''''''''''''''''''''''''''' '  &' '  !%'
''''E'
'''''''' .* # '  '1'
 Ë# ()# !$  !%2'
'''''''''$    '11'@%'
''''''''''''55' '. ' '  &' ''
'''''''''''' '2'
'
'''''''' .* #  *'  '1'  +@,2'
'
''''''''  # '1'  2'
'''''''''$ !'11' %'   !$%2''
  !'1' !B2'
'''''''''$ )!'11' %'    )!$%2''
   )!'1' )!B2'
'''''''''$Q )#" '11' %'  N )#" $%2''
 N )#" '1'Q )#" 2'
'''''''''$ #'11' %'  " #$%2''
 " #'1' #B2'
'''''''''$ !  C'11' %'  " !  C$%2''
 " !  C'1' !  CB2'
'''''''''$  '11' %'  "  $%2''
 "  '1'  B2'
'''''''''$  9'11' %'    9$%2''
   9'1'  9B2'
''''''''    '1'  2'
'
''''''''55'" '.'  ' '
'''''''' ' *'1' " $  %2'
'
''''''''55' ''' )' ' *'*' &' .*''
'''''''' ' *'11'?2'
''''G'
'
''''
+)    F. $)    
F. ) &'%,'
'''' ' '# $ '  !%'
''''E'
'''''''' ' *'1' $  !%2'
'
''''''''55' ''' )' ' *'*'&' .*''
'''''''' ' *'11'?2'
''''G'
G'

The methods that simply return data ± Ë# , Ë# ()# !,
Ë# ()  )!, and Ë# () ! ± are fairly straightforward as
they simply call down into the DAL. While in some scenarios there may be business rules that
need to be implemented at this level (such as authorization rules based on the currently logged on
user or the role to which the user belongs), we'll simply leave these methods as-is. For these
methods, then, the BLL serves merely as a proxy through which the presentation layer accesses
the underlying data from the Data Access Layer.

The #  and " #  methods both take in as parameters the values for the
various product fields and add a new product or update an existing one, respectively. Since many
of the #  table's columns can accept " values (  )!,  !, and
" #, to name a few), those input parameters for #  and " #  that
map to such columns use use nullable types. Nullable types are new to .NET 2.0 and provide a
technique for indicating whether a value type should, instead, be . In C# you can flag a
value type as a nullable type by adding P after the type (like  P'2). Refer to the Nullable
Types section in the C# Programming Guide for more information.
All three methods return a Boolean value indicating whether a row was inserted, updated, or
deleted since the operation may not result in an affected row. For example, if the page developer
calls #  passing in a # ! for a non-existent product, the   statement
issued to the database will have no affect and therefore the #  method will return
.

Note that when adding a new product or updating an existing one we take in the new or modified
product's field values as a list of scalars as opposed to accepting a #  * instance. This
approach was chosen because the #  * class derives from the ADO.NET  * class,
which doesn't have a default parameterless constructor. In order to create a new #  *
instance, we must first create a #  instance and then invoke its
*#  *$% method (which we do in # ). This shortcoming rears its head when
we turn to inserting and updating products using the ObjectDataSource. In short, the
ObjectDataSource will try to create an instance of the input parameters. If the BLL method
expects a #  * instance, the ObjectDataSource will try to create one, but fail due to the
lack of a default parameterless constructor. For more information on this problem, refer to the
following two ASP.NET Forums posts: Updating ObjectDataSources with Strongly-Typed
DataSets and Problem With ObjectDataSource and Strongly-Typed DataSet.

Next, in both #  and " # , the code creates a #  * instance and
populates it with the values just passed in. When assigning values to DataColumns of a DataRow
various field-level validation checks can occur. Therefore, manually putting the passed in values
back into a DataRow helps ensure the validity of the data being passed to the BLL method.
Unfortunately the strongly-typed DataRow classes generated by Visual Studio do not use
nullable types. Rather, to indicate that a particular DataColumn in a DataRow should correspond
to a " database value we must use the „
 $% method.

In " #  we first load in the product to update using


Ë# ()# !$°
Ô%. While this may seem like an unnecessary trip to the
database, this extra trip will prove worthwhile in future tutorials that explore optimistic
concurrency. Optimistic concurrency is a technique to ensure that two users who are
simultaneously working on the same data don't accidentally overwrite one another's changes.
Grabbing the entire record also makes it easier to create update methods in the BLL that only
modify a subset of the DataRow's columns. When we explore the  ( class we'll see
such an example.

Finally, note that the # ( class has the DataObject attribute applied to it (the
+)    F, syntax right before the class statement near the top of
the file) and the methods have DataObjectMethodAttribute attributes. The F attribute
marks the class as being an object suitable for binding to an ObjectDataSource control, whereas
the F.  indicates the purpose of the method. As we'll see in future
tutorials, ASP.NET 2.0's ObjectDataSource makes it easy to declaratively access data from a
class. To help filter the list of possible classes to bind to in the ObjectDataSource's wizard, by
default only those classes marked as F are shown in the wizard's drop-down list. The
# ( class will work just as well without these attributes, but adding them makes it
easier to work with in the ObjectDataSource's wizard.
  ; 
 

With the # ( class complete, we still need to add the classes for working with
categories, suppliers, and employees. Take a moment to create the following classes and methods
using the concepts from the example above:

O? CategoriesBLL.cs
O? Ë  $%
O? Ë  )()  )!$Ô %
O? SuppliersBLL.cs
O? Ë $%
O? Ë () !$
°° %
O? Ë ()  )$Ô
%
O? "  $
°° &' &'Ô&'Ô
%
O? EmployeesBLL.cs
O? Ë  )$%
O? Ë  )()  )!$° %
O? Ë  )() $  %

The one method worth noting is the  ( class's "   method.
This method provides an interface for updating just the supplier's address information. Internally,
this method reads in the   * object for the specified  ! (using
Ë () !), sets its address-related properties, and then calls down into the
 's "  method. The "   method follows:

+)    F. $)    


F. ) " &'%,'
' '"  $ ' !&' '&' '
)&' '  )%'
E'
'''' .*  ' '1'
 Ë () !$ !%2'
'''''$   '11'@%'
''''''''55' '. ' '  &' ''
'''''''' '2'
'''''
''''E'
'''''''' .*   *' '1' +@,2'
'
'''''''''$'11' %' $%2'' '
1'2'
'''''''''$)'11' %'  )$%2''  )'1')2'
'''''''''$  )'11' %'   )$%2''   )'
1'  )2'
'
''''''''55'" '.' '<'   '
'''''''' ' *'1' " $ %2'
'
''''''''55' ''' )' ' *'*' &' .*''
'''''''' ' *'11'?2'
''''G'
G'

Refer to this article's download for my complete implementation of the BLL classes.

` *    c ` c   '

 
In the first tutorial we saw examples of working directly with the Typed DataSet
programmatically, but with the addition of our BLL classes, the presentation tier should work
against the BLL instead. In the #   example from the first tutorial, the
#   was used to bind the list of products to a GridView, as shown in the
following code:

#  '   '1' *'#  $%2'


ËB*? '1'   Ë# $%2'
ËB*?( $%2'

To use the new BLL classes, all that needs to be changed is the first line of code ± simply replace
the #   object with a # ( object:


 
 


Ë  `
  
Ë 


Ë  


The BLL classes can also be accessed declaratively (as can the Typed DataSet) by using the
ObjectDataSource. We'll be discussing the ObjectDataSource in greater detail in the following
tutorials.
!  /"c    9 $

` /  ! & )   0$

 
Field-level validation are checks that pertains to the property values of the business objects when
inserting or updating. Some field-level validation rules for products include:

O? The #  field must be 40 characters or less in length


O? The N )#"  field must be 20 characters or less in length
O? The # !, # , and    fields are required, but all other fields
are optional
O? The " #, " !  C, "  , and   9 fields must be
greater than or equal to zero

These rules can and should be expressed at the database level. The character limit on the
#  and N )#"  fields are captured by the data types of those columns in
the #  table ( 9.$L@% and 9.$H@%, respectively). Whether fields are
required and optional are expressed by if the database table column allows " s. Four check
constraints exist that ensure that only values greater than or equal to zero can make it into the
" #, " !  C, "  , or   9 columns.
In addition to enforcing these rules at the database they should also be enforced at the DataSet
level. In fact, the field length and whether a value is required or optional are already captured for
each DataTable's set of DataColumns. To see the existing field-level validation automatically
provided, go to the DataSet Designer, select a field from one of the DataTables and then go to
the Properties window. As Figure 4 shows, the N )#"  DataColumn in the
#  has a maximum length of 20 characters and does allow " values. If we
attempt to set the #  *'s N )#"  property to a string value longer than
20 characters an    will be thrown.

!  1"c 
 % ) '! & ) 

Unfortunately, we can't specify bounds checks, such as the " # value must be greater
than or equal to zero, through the Properties window. In order to provide this type of field-level
validation we need to create an event handler for the DataTable's ColumnChanging Event. As
mentioned in the preceding tutorial, the DataSet, DataTables, and DataRow objects created by
the Typed DataSet can be extended through the use of partial classes. Using this technique we
can create a  .   event handler for the #  class. Start by creating
a class in the    folder named #   .  .
!  ,"# $
   ! 

Next, create an event handler for the  .   event that ensures that the " #,
" !  C, "  , and   9 column values (if not " ) are greater than
or equal to zero. If any such column is out of range, throw an    .

 c "


 %
  "

' '' .* '


E'
''' ' ''# '
'''E'
'''''''' ' 9'9 '( ! $%'
'''''''''E'
''''''''''''.  .  '41'B  2'
'''''''''G'
'
'''''''''9 'B  $ F' &'  . 9 '%'
'''''''''E'
''''''''''''$  Q$." #  %%'
''''''''''''E'
'''''''''''''''$: 9!($# B%'DD'
$%# B'/'@%'
'''''''''''''''E'
''''''''''''''''''. *' *'   $-" #' '''. '
R -&'-" #-%2'
'''''''''''''''G'
''''''''''''G'
''''''''''''''$  Q$." !  C  %'SS'
''''''''''''''''''''  Q$."    %'SS'
''''''''''''''''''''  Q$.  9  %%'
''''''''''''E'
'''''''''''''''''$: 9!($# B%'DD'
$. %# B'/'@%'
''''''''''''''''E'
''''''''''''''''''''. *' *'   $  $-E@G' ''
'. 'R -&'    %&'    %2'
''''''''''''''''G'
''''''''''''G'
'''''''''G'
'''G'
G'

` 1  
%'  0   '.
 
In addition to field-level validation, there may be high-level custom business rules that involve
different entities or concepts not expressible at the single column level, such as:

O? If a product is discontinued, its " # cannot be updated


O? An employee's country of residence must be the same as their manager's country of
residence
O? A product cannot be discontinued if it is the only product provided by the supplier

The BLL classes should contain checks to ensure adherence to the application's business rules.
These checks can be added directly to the methods to which they apply.

Imagine that our business rules dictate that a product could not be marked discontinued if it was
the only product from a given supplier. That is, if product  was the only product we purchased
from supplier , we could not mark  as discontinued; if, however, supplier supplied us with
three products, , , and , then we could mark any and all of these as discontinued. An odd
business rule, but business rules and common sense aren't always aligned!

To enforce this business rule in the " #  method we'd start by checking if
   was set to  and, if so, we'd call Ë# () ! to determine
how many products we purchased from this product's supplier. If only one product is purchased
from this supplier, we throw an     .

' '" # $ '  &' P' !&' P'


 )!&' 'Q )#" &'
''''''''''''''''''''''''''''''P' #&'. P' !  C&'. P'
  &'. P'  9&'
'''''''''''''''''''''''''''''' '  &' '  !%'
E'
'''''''' .* # '  '1'
 Ë# ()# !$  !%2'
'''''''''$    '11'@%'
''''''''''''55' '. ' '  &' ''
'''''''''''' '2'
'
'''''''' .* #  *'  '1'  +@,2'
'
''''''''55'( ''.C'<' '  ''  '.O' '
)' )'
''''''''55' ' '
'''''''''$  %'
''''''''E'
''''''''''''55'Ë'.'  '*')' '.' '
'''''''''''' .* # '  () '1'
 Ë# () !$   !%2'
'
'''''''''''''$  ()   '11'?%'
''''''''''''''''55'.''.' )'  '*')' '.' '
''''''''''''''''. *' *'    $-; ' 'C''  ''
  '''.' )'  ' .' '' -%2'
''''''''G'
'
''''''''  # '1'  2'
'''''''''$ !'11' %'   !$%2''
  !'1' !B2'
'''''''''$ )!'11' %'    )!$%2''
   )!'1' )!B2'
'''''''''$Q )#" '11' %'  N )#" $%2''
 N )#" '1'Q )#" 2'
'''''''''$ #'11' %'  " #$%2''
 " #'1' #B2'
'''''''''$ !  C'11' %'  " !  C$%2''
 " !  C'1' !  CB2'
'''''''''$  '11' %'  "  $%2''
 "  '1'  B2'
'''''''''$  9'11' %'    9$%2''
   9'1'  9B2'
''''''''    '1'  2'
'
''''''''55'" '.'  ' '
'''''''' ' *'1' " $  %2'
'
''''''''55' ''' )' ' *'*' &' .*''
'''''''' ' *'11'?2'
G''

0    6     c 

When calling the BLL from the presentation tier we can decide whether to attempt to handle any
exceptions that might be raised or let them bubble up to ASP.NET (which will raise the
   's   event). To handle an exception when working with the BLL
programmatically, we can use a Try...Catch block, as the following example shows:

# ( '   '1' *'# ( $%2'


'
55'" '# !'?O'   '
)'
E'
''''55'.'*'' '*O'  ' '''
''''55'" #'9''. '@'
''''   " # $- O'-&'?&'?&' &'<?L&'?@&' &'
&'&'?%2'
G'
.'$   '%'
E'
'''' 
$-.'*''  3'-'4'%2'
G'

As we'll see in future tutorials, handling exceptions that bubble up from the BLL when using a
data Web control for inserting, updating, or deleting data can be handled directly in an event
handler as opposed to having to wrap code in ). blocks.

` %%
A well-architected application is crafted into distinct layers, each of which encapsulates a
particular role. In the first tutorial of this article series we created a Data Access Layer using
Typed DataSets; in this tutorial we built a Business Logic Layer as a series of classes in our
application's    folder that call down into our DAL. The BLL implements the field-level
and business-level logic for our application. In addition to creating a separate BLL, as we did in
this tutorial, another option is to extend the TableAdapters' methods through the use of partial
classes. However, using this technique does not allow us to override existing methods nor does it
separate our DAL and our BLL as cleanly as the approach we've taken in this article.

With the DAL and BLL complete, we're ready to start on our presentation layer. In the next
tutorial we'll take a brief detour from data access topics and define a consistent page layout for
use throughout the tutorials.

Happy Programming!

?
c / (   ` #)  

Scott Mitchell

June 2006

Download the ASPNET_Data_Tutorial_3_CS.exe sample code.

Download the ASPNET_Data_Tutorial_3_CS.exe sample code.

  c / 




Introduction
Step 1: Creating the Master Page
Step 2: Adding a Homepage to the Web Site
Step 2: Creating a Site Map
Step 3: Displaying a Menu Based on the Site Map
Step 4: Adding Breadcrumb Navigation
Step 5: Adding the Default Page for Each Section
Summary

   
One common characteristic of user-friendly websites is that they have a consistent, site-wide
page layout and navigation scheme. ASP.NET 2.0 introduces two new features that greatly
simplify implementing both a site-wide page layout and navigation scheme: master pages and
site navigation. Master pages allow for developers to create a site-wide template with designated
editable regions. This template can then be applied to ASP.NET pages in the site. Such
ASP.NET pages need only provide content for the master page's specified editable regions ± all
other markup in the master page is identical across all ASP.NET pages that use the master page.
This model allows developers to define and centralize a site-wide page layout, thereby making it
easier to create a consistent look and feel across all pages that can easily be updated.

The site navigation system provides both a mechanism for page developers to define a site map
and an API for that site map to be programmatically queried. The new navigation Web controls ±
the Menu, TreeView, and SiteMapPath ± make it easy to render all or part of the site map in a
common navigation user interface element. We'll be using the default site navigation provider,
meaning that our site map will be defined in an XML-formatted file.
To illustrate these concepts and make our tutorials website more usable, let's spend this lesson
defining a site-wide page layout, implementing a site map, and adding the navigation UI. By the
end of this tutorial we'll have a polished website design for building our tutorial web pages.

!  "c 6 0  c c 

`  
   (  
The first step is to create the master page for the site. Right now our website consists of only the
Typed DataSet ( .* , in the    folder), the BLL classes (# ( ,
 ( , and so on, all in the    folder), the database (
, in the
  folder), the configuration file (
 ), and a CSS stylesheet file ()).
I cleaned out those pages and files demonstrating using the DAL and BLL from the first two
tutorials since we will be reexamining those examples in greater detail in future tutorials.
!  *"c !  ;  

To create a master page, right-click on the project name in the Solution Explorer and choose Add
New Item. Then select the Master Page type from the list of templates and name it .

!  /"# $(    


Define the site-wide page layout here in the master page. You can use the Design view and add
whatever Layout or Web controls you need, or you can manually add the markup by hand in the
Source view. In my master page I use cascading style sheets for positioning and styles with the
CSS settings defined in the external file ). While you cannot tell from the markup
shown below, the CSS rules are defined such that the navigation /90's content is absolutely
positioned so that it appears on the left and has a fixed width of 200 pixels.

` "% 

/67''  1- 8-' 9 


 1--' 1--'
! .1--'60'
'
/: ;#'.'#"( ! '-<55
= 55'>  '?@'  55-'
-. 355****= 55.?55.?<  -0'
'
/.' 1-. 355****= 5?AAA5.-'0'
/.' 1-9-0'
''''/0
C '*.'' /50'
''''/ C'.1-)-'1-).-') 1-5-'50'
/5.0'
/ )0'
''''/9'1-* -0'
'
''''''''/ '1- ?-' 1-9-0'
'''''''''
''''''''''''/9'1-.-0'
''''''''''''''''/  '1--0
C '*.'' /5  0'
''''''''''''''''/  '1--03'('*' '
./5  0'
''''''''''''/590'
'''''''''
''''''''''''/9'1-  -0'
''''''''''''''''/ 3   . '1-  -' 1-9-0'
''''''''''''''''''/:<<'#< '  '*' '.'<<0'
''''''''''''''''/5 3   . 0'
''''''''''''/590'
'''''''''''''
''''''''''''/9'1- 9 -0'
''''''''''''''''3' '*' '.'
''''''''''''/590'
''''''''/5 0'
''''/590'
/5 )0'
/5.0'

A master page defines both the static page layout and the regions that can be edited by the
ASP.NET pages that use the master page. These content editable regions are indicated by the
ContentPlaceHolder control, which can be seen within the content /90. Our master page has a
single ContentPlaceHolder (  ), but master page's may have multiple
ContentPlaceHolders.
With the markup entered above, switching to the Design view shows the master page's layout.
Any ASP.NET pages that use this master page will have this uniform layout, with the ability to
specify the markup for the    region.

!  1"c (  :  $ c      $

` *  4%    ` 


With the master page defined, we're ready to add the ASP.NET pages for the website. Let's start
by adding  , our website's homepage. Right-click on the project name in the
Solution Explorer and choose Add New Item. Pick the Web Form option from the template list
and name the file  . Also, check the "Select master page" checkbox.
!  ,"# $ !%:
>  <`  %  <
>7

After clicking the OK button, we're asked to choose what master page this new ASP.NET page
should use. While you can have multiple master pages in your project, we have only one.

!  2"
  (   `"#6c `  V
After picking the master page, the new ASP.NET pages will contain the following markup:

  "7

/67'#'  1- 8-'#1-T5-' 9 


 1--'
1- -'! .1--'1-" '#-'60'
/ 3  '!1-  ?-'  # !1-  -' 1-9-0'
/5 3  0'

In the 7# directive there's a reference to the master page file used
(#1-T5-), and the ASP.NET page's markup contains a Content
control for each of the ContentPlaceHolder controls defined in the master page, with the control's
 # ! mapping the Content control to a specific ContentPlaceHolder. The
Content control is where you place the markup you want to appear in the corresponding
ContentPlaceHolder. Set the 7# directive's  attribute to Home and add some welcoming
content to the Content control:

  "7

/67'#'  1- 8-'#1-T5-' 9 


 1--'
1- -'! .1--'1- -'60'
/ 3  '!1-  ?-'  # !1-  -' 1-9-0'
''''/.?0
 ' '.'
C '*.'' '/5.?0'
'
''''/ 0.''' ''' ' ''' ' '.''
 ' '.' *''' '  '' '#'H@' '
B'
'9 /5 0'
'
''''/ 09'&''*' ''. ' ' '.' 3/5 0'
'''''
''''/0'
''''''''/0( '' '$'')%&/50'
''''''''/0" ' )') ' ' '/50'
''''''''/0<' /50'
''''''''/0 /50'
''''''''/0# &/50'
''''''''/0* <*)'  &/50'
''''''''/0 &/50'
''''''''/0 &/50'
''''''''/0!  &/50'
''''''''/0 .'' * &/50'
''''''''/0 .'< * &/50'
''''''''/0 '  )&/50'
''''''''/0 ' :/50'
''''/50'
/5 3  0'

The  attribute in the 7# directive allows us to set the page's title from the ASP.NET
page, even though the /0 element is defined in the master page. We can also set the title
programmatically, using #. Also note that the master page's references to stylesheets
(such as )) are automatically updated so that they work in any ASP.NET page,
regardless of what directory the ASP.NET page is in relative to the master page.
Switching to the Design view we can see how our page will look in a browser. Note that in the
Design view for the ASP.NET page that only the content editable regions are editable ± the non-
ContentPlaceHolder markup defined in the master page is grayed out.

!  3"c    $ `"#6c ` $'  6  # &


6 0  

When the   page is visited by a browser, the ASP.NET engine automatically
merges the page's master page content and the ASP.NET's content, and renders the merged
content into the final HTML that is sent down to the requesting browser. When the master page's
content is updated, all ASP.NET pages that use this master page will have their content remerged
with the new master page content the next time they are requested. In short, the master page
model allows for a single page layout template to be defined (the master page) whose changes
are immediately reflected across the entire site.

  `"#6c   ` 

Let's take a moment to add additional ASP.NET page stubs to the site that will eventually hold
the various reporting demos. There will be more than 35 demos in total, so rather than creating
all of the stub pages let's just create the first few. Since there will also be many categories of
demos, to better manage the demos add a folder for the categories. Add the following three
folders for now:
O? (  
O?  
O?    

Finally, add new files as shown in the Solution Explorer in Figure 8. When adding each file,
remember to check the "Select master page" checkbox.

!  5" !$ ! 

` * 
  ` (
One of the challenges of managing a website composed of more than a handful of pages is
providing a straightforward way for visitors to navigate through the site. To begin with, the site's
navigational structure must be defined. Next, this structure must be translated into navigable user
interface elements, such as menus or breadcrumbs. Finally, this whole process needs to be
maintained and updated as new pages are added to the site and existing ones removed. Prior to
ASP.NET 2.0, developers were on their own for creating the site's navigational structure,
maintaining it, and translating it into navigable user interface elements. With ASP.NET 2.0,
however, developers can utilize the very flexible built in site navigation system.
The ASP.NET 2.0 site navigation system provides a means for a developer to define a site map
and to then access this information through a programmatic API. ASP.NET ships with a site map
provider that expects site map data to be stored in an XML file formatted in a particular way.
But, since the site navigation system is built on the provider model it can be extended to support
alternative ways for serializing the site map information. Jeff Prosise's article, The SQL Site Map
Provider You've Been Waiting For shows how to create a site map provider that stores the site
map in a SQL Server database; another option is to create a site map provider based on the file
system structure.

For this tutorial, however, let's use the default site map provider that ships with ASP.NET 2.0.
To create the site map, simply right-click on the project name in the Solution Explorer, choose
Add New Item, and choose the Site Map option. Leave the name as
 and click the
Add button.

!  8"` (-  

The site map file is an XML file. Note that Visual Studio provides IntelliSense for the site map
structure. The site map file must have the / 0 node as its root node, which must contain
precisely one /  0 child element. That first /  0 element can then contain
an arbitrary number of descendent /  0 elements.

Define the site map to mimic the file system structure. That is, add a /  0 element for
each of the three folders, and child /  0 elements for each of the ASP.NET pages in
those folders, like so:
 " %

/P'9 1-?@-'   1-<K-'P0'


/ ' 1-. 355.   5 5 <<?@-'0'
'
''/  '1-T5 -'1- -'  1- -0'
''''''/  '1-('  -'
1-T5(  5 -'  1-('  ' -0'
''''''''/  '1-T5(  5  ) -'1- '
 )-'  1- )'.'  '  ' '''-'50'
''''''''/  '1-T5(  59# -'
1-9'#-''  1- )''' '.'
  ' '''' ' -'50'
''''''''/  '1-T5(  5# # -'
1- '#'B-'  1-. *'. *' '' '
9'  )-'50'
''''''/5  0'
'''''''''
''''''/  '1- ' -'1-T5 5 -'
  1- ' ' '.' ' -0'
''''''''/  '1-T5 5()  *  -'
1-')' < * ' -'  1-'' '' <
 * '-'50'
''''''''/  '1-T5 5 -'
1-<<-'  1-''* '9' * -'
50'
''''''''/  '1-T5 5()  -'1-'
'' *-'  1-. *''' '''' ''
ËB*-'50'
''''''/5  0'
'
''''''/  '1-  R'  -'
1-T5    5 -'  1- ' ' '
. '
 ''  R-0'
''''''''/  '1-T5    5     -'1- '
 -'  1- '.'D 2'  '' '.' ) '
-'50'
''''''''/  '1-T5    5ËB*  -'
1-  '  ' ''ËB*-'  1-. *' '.'
 ' ' R'.'  ' ''' ''ËB*-'50'
''''''''/  '1-T5    5B*  -'
1-  '  ' ''B*-''  1-. *' '.'
 ' ' R'.'  ' ''' ''B*-'50'
''''''''/  '1-T5    5 B* -'1-  '
 ' '' B*-''  1-!' '' B*' ''
..)' R'9*-'50'
''''''''/  '1-T5    5)!   -'
1-)'' ' -'  1- )')'' '.'
O' -'50'''''''''
''''''/5  0'
'
''/5  0'
'
/5 0'
The site map defines the website's navigational structure, which is a hierarchy that describes the
various sections of the site. Each /  0 element in
 represents a section
in the site's navigational structure.

!  +"c ` (0   4  #)  `   >% 
 

ASP.NET exposes the site map's structure through the .NET Framework's SiteMap
class!href(http://msdn2.microsoft.com/en-us/library/system.web.sitemap.aspx). This class has a
CurrentNode property, which returns information about the section the user is currently visiting;
the RootNode property returns the root of the site map (Home, in our site map). Both the
CurrentNode and RootNode properties return SiteMapNode!href(http://msdn2.microsoft.com/en-
us/library/system.web.sitemapnode.aspx) instances, which have properties like ParentNode,
ChildNo,  , #9  , and so on, that allow for the site map hierarchy
to be walked.

` /  ( '   ` (


Accessing data in ASP.NET 2.0 can be accomplished programmatically, like in ASP.NET 1.x, or
declaratively, through the new data source controls. There are several built-in data source
controls such as the SqlDataSource control, for accessing relational database data, the
ObjectDataSource control, for accessing data from classes, and others. You can even create your
own custom data source controls.

The data source controls serve as a proxy between your ASP.NET page and the underlying data.
In order to display a data source control's retrieved data, we'll typically add another Web control
to the page and bind it to the data source control. To bind a Web control to a data source control,
simply set the Web control's  ! property to the value of the data source control's !
property.

To aid in working with the site map's data, ASP.NET includes the SiteMapDataSource control,
which allows us to bind a Web control against our website's site map. Two Web controls ± the
TreeView and Menu ± are commonly used to provide a navigation user interface. To bind the
site map data to one of these two controls, simply add a SiteMapDataSource to the page along
with a TreeView or Menu control whose  ! property is set accordingly. For
example, we could add a Menu control to the master page using the following markup:

/9'1- 9 -0'


''''/ 3 '!1- ?-' 1-9-' !1-  ?-0'
''''/5 3 0'
'''''
''''/ 3  '!1-  ?-' 1-9-'50'
/590'

For a finer degree of control over the emitted HTML, we can bind the SiteMapDataSource
control to the Repeater control, like so:

/9'1- 9 -0'


''''/0'
''''''''/0/ 3 )   C' 1-9-'!1- C -'
9"1-T5 -0 /5 3 )   C0/50'
'''''''''
''''''''/ 3 ' 1-9-'!1- -'
 !1-  ?-0'
''''''''''''/! 0'
''''''''''''''''/0'
''''''''''''''''''''/ 3 )   C' 1-9-'9"1O/68'
9$-"-%'60O0/68'9$--%'60/5 3 )   C0'
''''''''''''''''/50'
''''''''''''/5! 0'
''''''''/5 3 0'
''''/50'
'''''
''''/ 3  '!1-  ?-' 1-9-'
. *  1--'50'
/590'

The SiteMapDataSource control returns the site map hierarchy one level at a time, starting with
the root site map node (Home, in our site map), then the next level (Basic Reporting, Filtering
Reports, and Customized Formatting), and so on. When

Vous aimerez peut-être aussi