asp:feature
TECHNOLOGIES: VS .NET | C# | VB .NET
ASP.NET VERSIONS: 1.0 | 1.1
Explore the Microsoft Application Blocks
Avoid coding infrastructure requirements by using the
Microsoft Application Blocks.
By Brian Noyes
Most real-world applications share
common requirements at the infrastructure level. Your apps need to access data,
handle exceptions, access configuration data, and so on. Before the .NET
Framework came along, a lot of people spent a lot of time writing little
utility classes and subroutines for common coding tasks, such as parsing
strings, doing type conversions, and manipulating paths. The classes in the
.NET Framework save you from doing a lot of low-level coding for which you used
to have to spin your own solutions.
But you still end up writing a lot
of repetitive and mostly uninteresting code to address those infrastructure
requirements because they exist at a higher level of abstraction than even the
individual classes in the .NET Framework were designed to address. Microsoft
has seen the need to address these infrastructure requirements, as well, and
has begun providing mini-frameworks, called Application Blocks, to address
common requirements most organizations have for their applications. The
Application Blocks are quasi-products from Microsoft. They are developed and
delivered as products by Microsoft and are even supported through tech support,
but they are free, so you can't beat the price.
The first two Application Blocks
were released last year and addressed data access and exception management. A
new version of the Data Access Application Block (DAAB) has been released, as
have a slew of new Application Blocks to add to the mix. In this article, I
will introduce the Application Blocks as a whole, and I will dive deeper into two
of the blocks: the DAAB and the User Interface Process (UIP) Application Block.
In a series of follow-up articles online, I will discuss each of the other
blocks in turn.
Pick from the Menu
I'll skip over the DAAB and UIP for
now because I will cover them in more detail later. Let me quickly introduce
you to the other selections on the menu before I cover today's specials.
First off, the Application Blocks
all share some common goals and capabilities. The biggest goal is to make it so
that you can write less code and have consistent and clean interfaces for
satisfying the infrastructure requirements of your applications, allowing you
to focus on designing and coding your core logic. Obviously, they need to be
reliable and to perform well. To be truly reusable, they also need to be highly
extensible - which the Application Blocks are.
Almost all the Application Blocks
are designed to deliver a usable capability as soon as you download the code,
but they have numerous extensibility points so you can use them as designed
while adding to their capabilities without having to modify the code delivered
by Microsoft. They also are configuration-driven, which means that extending
their underlying capabilities often requires no new coding in the consuming
application. So, for example, you can add a new publisher of exception
information without changing any of the code that actually makes calls to the
exception manager to publish. Each Application Block comes with full source
code, documentation, and samples to help you get started using and extending
them.
The Application Blocks I will be
covering in future articles include:
- The
Exception Management Application Block, which includes capabilities for
publishing exception context information to various forms of storage.
- The
Configuration Management Application Block, which allows you to read and write
application-configuration data and settings to and from multiple forms of
storage providers.
- The
Updater Application Block, which implements a pull mechanism to update
applications over distributed networks easily and automatically.
- The
last three blocks are focused on Service Oriented Architecture (SOA) and allow
you to make calls to services in an asynchronous manner and to cache and
aggregate the data those services return.
Smart Data Access
for the Masses
ADO.NET is a powerful and flexible
object-oriented API for data access. However, for most business applications,
the ADO.NET API is a little too fine-grained for concise, maintainable code.
The DAAB gives you a simplified API for data access that minimizes the amount
of repetitive code you need to write for a typical data-access layer. Version 2
of the DAAB was released recently and adds some great new capabilities to what
was released in version 1 more than a year ago. The DAAB classes are designed
to work exclusively with the SQL Server managed provider, but if you need to
work with other databases, you could implement a custom version of the DAAB
targeting other providers. An Oracle implementation based on version 1 of the
DAAB is included with the Pet Shop 3 sample application.
The DAAB is composed of only two
classes, the SqlHelper and SqlHelperParameterCache classes. SqlHelper has a
bunch of overloaded ExecuteXXX methods (see Figure 1). The parameters for these
methods include connection, command, and parameter information. The various
overloads of each method give you a lot of options in terms of how the
connections are constructed, commands are executed, and parameters are passed.
Basically, the calling patterns of these methods minimize the amount of
construction and population of individual objects that you need to do just to
execute a single command against the database.
|
Method
|
Result
|
|
ExecuteDataset
|
Executes a query and returns a
new DataSet object containing the result.
|
|
ExecuteReader
|
Executes a query and returns an
open SqlDataReader ready for use in accessing the contained rows.
|
|
ExecuteNonQuery
|
Executes a query that is not
expected to return any result set. The method returns the number of rows affected
by the query. You can provide SqlParameters that are out or return value
parameters and harvest those after completion of the method calls.
|
|
ExecuteScalar
|
Executes a query that is expected
to return a single-row, single-column type of result set and returns that
value as the return value from the function. This saves you from having to
reach into a returned result set to extract a single value when you know that
is all that will be returned.
|
|
ExecuteXmlReader
|
Executes a query that is expected
to return an XML node set as the result set. The DAAB and ADO.NET wrap those
return results in an XmlReader that you can use to iterate through the nodes.
|
|
FillDataset
|
Executes a query to add a table
to an existing data set that you pass in as an argument. This allows you to
construct multi-table data sets or work with strongly typed data sets using
the DAAB.
|
|
UpdateDataset
|
Executes an update query to
perform updates, insertions, and deletions based on a data set containing
modified records.
|
|
CreateCommand
|
Automatically constructs a
command object for performing updates by querying the database for the
parameter set for a stored procedure.
|
Figure 1. These are the
families of query methods of the SqlHelper class. Each of these families has
multiple overloads to allow variations in the construction of the connection,
command, and parameter information required to execute the query.
Version 2 of the DAAB includes
several significant enhancements compared to what was in version 1. These
include the abilities to work with strongly typed data sets, populate a data
set with multiple tables, and update the database using data sets. There are
also some additional discovery methods available for automatically creating
commands, and there are some additional ExecuteXXXTypedParams methods that let
you pass your parameters in the form of a populated DataRow. Using those
methods, if you have parameters for a query already in the form of a DataRow,
you don't need to push the parameters into individual SqlParameter objects but
can just pass the DataRow containing the parameter values instead.
The SqlHelperParameterCache class
primarily is used internally by the SqlHelper class, but it has several public
methods that will allow you to discover and cache parameter sets to reduce the
cost of repeatedly reconstructing those parameter sets. You need to be careful
in the way you use these methods, though, because they can introduce a coupling
between your data-access code and the underlying queries or stored procedures,
based on the specific order of the parameters that are returned. Your code will
be more maintainable and easier to understand if parameter matching is based on
name rather than on the order in which the parameters appear, because then
parameters can be inserted or removed from the underlying query without
breaking the data-access code.
The download code for this article
includes a sample application that uses the DAAB to perform some queries
against a custom database that contains two tables: Files and Folders. See the
download ReadMe.htm file for details on how to construct and populate this
database on your machine. The FileSystemDataAccess sample application performs
a bunch of different query results and presents them in a Windows user
interface to let you experiment with performing queries and updates using the
DAAB. For example, to use the DAAB to get all the files as a data set from the
database using a stored procedure, and to name the table that is created in the
data set, all that is required are two lines of code:
DataSet ds = SqlHelper.ExecuteDataset(
DataGlobals.ConnectionString,"GetFiles",
null);
ds.Tables[0].TableName
= "Files";
Tables that are created by the
ExecuteDataset method use the default ADO.NET naming, so you will have to name
them after they are returned if you desire. You also could pre-create the data
set and pass it into the FillDataset method, which allows you to specify the
table names as arguments. Using this latter method, you also can populate typed
data sets and multi-table data sets.
The SqlHelper class handles
construction of the connection object as well as opening it, creating a command
object for the specified stored procedure, executing the query through a data
adapter to fill a data set, closing the connection, and returning the result.
If you were using raw ADO.NET, you would have to do all those things yourself
for every query you made to the database. By using the DAAB, all those
repetitive and error-prone actions are neatly encapsulated in the SqlHelper
class, which allows you to write much less code and focus on the business logic
of your application rather than data-query goo.
As of version 2 of the DAAB, you
also can perform updates using data sets containing modified, inserted, or
deleted rows. Figure 2 shows an example that uses the new CreateCommand method
to construct the command objects for the updates automatically and then
performs the updates with the UpdateDataset method. The code in Figure 2 also
uses the CreateCommand method to construct the update commands using a stored
procedure name and the names of the parameters to pass to that stored
procedure. CreateCommand will locate those parameters and their types with a
round-trip to the database and will pre-create the parameters collections for
those commands. Then these parameter collections will be used for each update,
insert, or delete operation that is required based on the current records in
the data set that is passed to UpdateDataset.
public static
void UpdateFiles(DataSet ds)
{
// Create the connection
SqlConnection conn =
new
SqlConnection(DataGlobals.ConnectionString);
// Construct the update commands - all
three are req'd
SqlCommand insertCmd =
SqlHelper.CreateCommand
(conn,"AddNewFile","Name","Size","ParentFolderID");
SqlCommand
updateCmd = SqlHelper.CreateCommand
(conn,"UpdateFileName","FileID", "Name");
SqlCommand
deleteCmd = SqlHelper.CreateCommand
(conn,"DeleteFile","FileID");
// perform the update
try
{
SqlHelper.UpdateDataset
(insertCmd, deleteCmd,
updateCmd,ds,"Files");
}
finally
{
conn.Close();
}
}
Figure 2. Like other
data-access operations with the DAAB, performing an update with a data set
requires minimal code that is easy to write and understand.
Decouple Your
Presentation Tier
The UIP Application Block targets a
common application requirement for presentation-tier applications - the need to
implement workflow and state management between views, or individual forms, of
your application. The UIP is designed at a much higher level of abstraction
than the DAAB and constitutes a small presentation-tier framework unto itself.
The driving force behind the UIP is that, for maintainability, you want to
minimize coupling between the individual views (think forms) of your
application.
Consider an application that needs
to implement something along the lines of a shopping cart, quiz or
questionnaire, or registration process. All these require that a navigation
flow exist between individual views of the application, as well as state that
gets shared between views and may be passed down into the business tier while
the process is in progress, or at the end when it is complete. You may have a
strictly linear flow or one with multiple paths and branching. The problem is
that if you explicitly code individual views to know about every path into and
out of their place in the process, along with all state-passing requirements
for those transitions, you run into significant maintainability problems as the
complexity of your process grows or when you want to add or remove steps in the
process.
The UIP addresses this by
implementing a model-view-controller pattern that completely decouples the
individual views of your application, allowing you to add or remove steps in
the process or factor individual views into multiple views without having to
touch any of the other views' code.
To use the UIP in your
applications, you basically need to do several things. First, you need to
define the "use" case that composes the process you are implementing. By doing
this, you can lay out the navigation graph that satisfies that "use" case,
including the views that compose it and what the transitions are between views.
Next, you need to implement a controller class. This is a class you derive from
the ControllerBase class in the UIP. This class will contain the navigation
methods called by the views in your application and will be the single point of
contact for your individual views. Next, you need to design the views of your
application and derive them from either WinFormView or WebFormView, depending
on the type of application. Finally, you need to add a bunch of information to
your config file to tell the UIP where to find everything it needs at run time.
The ControllerBase class contains a
state bag that the views can place state values into so that they can be shared
across views without having to pass that state as part of a view transition
explicitly (see Figure 3). That state also can be persisted through a number of
configurable storage providers, allowing you to resume the task wherever it was
abandoned, at a future time.
Figure 3. The UIP lets individual views of a process be ignorant of one
another. Each view just interacts with the controller, placing information in
its state collection and calling methods on the controller that result in the
UIP manager displaying the next appropriate view based on configuration data.
The state can be persisted to different providers, just by changing config-file
entries.
An example of what the code ends up
looking like from the perspective of the view is shown in Figure 4. You access
the controller as a protected member of that base class and cast it to the
derived controller to access the specific navigation methods that you implement
in that derived class. Views also store the ongoing results of the process in
the state collection contained by the controller. Note that there are no
references to other views, nor is there any explicit passing of state to
another view.
public class
WhatIsQuest : WebFormView
{
// Details omitted...
private QuizState GetQuizState()
{
return
(QuizState)Controller.State["QuizState"];
}
private void btnNext_Click
(object sender, System.EventArgs e)
{
GetQuizState().Quest = txtQuest.Text;
OnlineQuizController ctrl =
(OnlineQuizController)Controller;
ctrl.MoveNext();
}
private void btnBack_Click
(object sender, System.EventArgs e)
{
GetQuizState().Quest = txtQuest.Text;
OnlineQuizController ctrl =
(OnlineQuizController)Controller;
ctrl.MovePrevious();
}
private void btnSelect_Click
(object sender, System.EventArgs e)
{
GetQuizState().Quest = txtQuest.Text;
OnlineQuizController ctrl =
(OnlineQuizController)Controller;
ctrl.BranchSelectQuest();
}
}
Figure 4. Views in a UIP
application simply call navigation methods on their controller class and store
the results of their portion of the process in a state collection.
Methods in the derived controller
class that you implement are usually as simple as setting the NavigateValue on
the state object and calling Navigate on the base class, as shown here:
public void MoveNext()
{
this.State.NavigateValue =
"next";
Navigate();
}
This results in the base class
determining the corresponding view for the NavigateValue, based on which view
was current and based on what the configuration settings for that NavigateValue
are for that view in the config file. This effectively adds a layer of
abstraction between what the view sees as a navigation command on the
controller class and how the UIP manager interprets that command at run time,
all based on config-file entries that can be changed and reorganized without
requiring any changes to source code.
There is a lot of complexity going
on under the covers with the UIP to give you a rich, flexible, configurable,
and maintainable model for process-oriented user interfaces. The download code
for this article includes an online quiz sample that demonstrates the steps for
using the UIP. The questions in the quiz may look a bit familiar to fans of
Monty Python. See the ReadMe.htm for more details of what the steps were to
implement this sample using the UIP.
The sample code in this
article is available for download.
Brian Noyes is a software
architect with IDesign Inc. (http://www.idesign.net),
a .NET-focused architecture and design consulting firm. Brian specializes in
designing and building data-driven distributed Windows and Web applications. He
has more than 12 years of experience in programming, engineering, and project
management and is a contributing editor and writer for C#PRO, asp.netPRO, and other publications. Contact
him at mailto:brian.noyes@idesign.net.