Wrox Professional C - Hash Design Patterns Applied PDF
Wrox Professional C - Hash Design Patterns Applied PDF
Wrox Professional C - Hash Design Patterns Applied PDF
The following is the list of recommended system requirements for running the code in this book:
❑ Familiarity with the C# language structure and syntax, and therefore a basic understanding of
the principles of object-oriented programming (OOP)
❑ Good working knowledge of the .NET Framework
❑ A grasp of the Visual Studio .NET IDE
Summary of Contents
Introduction 1
Chapter 1: Introduction to Design Patterns 9
Chapter 2: Design Patterns in the Data Tier 67
Chapter 3: Design Patterns in the Middle Tier 117
Chapter 4: Design Patterns in the Presentation Tier 177
Chapter 5: Between the Tiers: Design Patterns and .NET Remoting 257
Chapter 6: What Next? 299
Appendix A: UML Primer 309
Index 339
Design Patterns in the Middle Tier
Design patterns and the middle tier were made for each other. Why? Think of it this way. The very
nature of the middle tier is to execute business rules. Business rules change as often and quickly as
businesses change. Design patterns exist to encapsulate variability and change. When we put these three
assertions together, the conclusion is hard to ignore – design patterns can help developers manage those
never-ending changes to a middle tier application.
In this chapter, we will build an application that processes orders. To help us get the job done we will
use two types of patterns:
❑ The first type is the now-familiar design pattern that has been best illustrated using UML class
diagrams. In fact, our application will utilize some of the basic GoF patterns that we discussed
earlier in the book. But, unlike most instances of design patterns that we have talked about so
far, here we will use them in combination to enhance our solution. We will use the Decorator
pattern to manage the overall processing of orders; but hiding underneath it will be the
Façade and Strategy patterns.
❑ The second type of pattern we will see is one that constitutes a common architectural approach
to a common problem. Our application supports a business-to-business web-based presentation
tier – a requirement that demands performance and reliability. Fortunately, developers have
already solved this problem with a pattern that we will call Store and Forward (SaF).
Interestingly, such high-level patterns are not necessarily best illustrated using UML class
diagrams (like the GoF design patterns). In fact, in this case, the essence of the SaF pattern is
much better captured by an activity sequence diagram.
Chapter 3
Before we ramp up, there is an interesting side note about the SaF pattern worth mentioning. Even
though the SaF pattern lives on a higher conceptual plane than the GoF design patterns, we will need a
lowly "bread and butter" design pattern to give it life. Specifically, the Singleton pattern will play a big
part in making SaF work.
❑ Decorator
❑ Façade
❑ Strategy
❑ Store and Forward (SaF)
❑ Singleton
First, we'll look at our business requirements. Then we'll do our analysis, and at that point we'll try to
identify the patterns that will best suit our needs. In doing so, we'll examine a few options before finally
settling on the approach we've outlined above.
Handling Orders
Before we delve into the detailed analysis and design phase, let's set up our business requirements and
"set the scene" for the application and its related technologies. That should help us get a feel for where
our application needs to go and how we will get it there.
Business Requirements
Our client is the company called Northwind Traders (that perennial favorite of all Microsoft
developers). Currently, Northwind's sales representatives take customer orders over the phone. Sam
Spade, Northwind's controller, just finished an order handling benchmarking study and fears that the
company's cost per transaction is too high. He has also read about how other companies have lowered
their business-to-business (B2B) transaction costs via Internet-based technologies. He thinks that maybe
Northwind could, too. With this idea (lowering order transaction costs via the Internet) in mind, Sam
starts meeting with the company's Technology Manager, Sally Server, and the Inside Sales Coordinator,
Laura Callahan.
During the course of these meetings, it quickly became obvious why order transaction costs were high.
Laura informed everyone that while revenues were increasing, customers were placing a higher volume of
smaller orders. She said that as a consequence of this change in customers' behavior, Northwind would
probably be forced to hire two more sales representatives. And to make matters worse, sales representative
costs were increasing since they were now receiving continuous specialized product training.
Since it seemed unlikely that customers would change their ordering behavior, everyone agreed that
Northwind needed a way to reduce the labor costs associated with placing orders. At this point, Sam asked
Sally whether or not some Internet-based B2B solution might automate the process. She thought this was a
good idea, but needed a little more information about how the sales representatives processed orders.
118
Design Patterns in the Middle Tier
Laura explained that the ordering process was simple, but setting up new customers and answering
product questions was complicated! Most existing customers already knew the product line and simply
called in their orders to a representative, who then reviewed the order and entered it into Northwind's
Order system. This review involved verifying product identification numbers and prices, checking
product stock levels, and stopping orders with past delivery dates.
Based on all this information, Sally believed that Sam's initial hunch was a good one. Order processing
costs could be lowered if the current manual order processing system was replaced with an Internet-based
application. The new application could receive customer orders over the Internet, review them, and enter
them into the order system. Laura was also enthusiastic; such a system would enable her staff to focus on
activities that improve sales, rather than wasting time pushing paper into a computer!
As a result, of these meetings, Jack Spratt was hired by Northwind to design and build an order
processing application.
Technology Requirements
In this chapter, we'll consider Northwind as a "Microsoft shop". All computer hardware runs on either
Windows 2000 Server or Windows 2000 Professional/Windows XP. All business applications are
written in Visual Basic. The corporate database is SQL Server 2000.
Sally stated that the new system must utilize these existing technologies. The only exception to the rule
would be the development language. She specified C# as the target language for the new application
(citing Northwind's recent decision to move onto the .NET platform).
While Jack had not yet started the analysis and design phase for the application, he was nonetheless
forming a few ideas about what technologies he might employ for the application. For example, some
combination of MSMQ queuing services and multithreading promised a reliable and responsible order
processing application capable of meeting the needs of web-based clients. Also, .NET's COM Interop
capability might ease communication with the legacy Microsoft-based order system.
If you're unfamiliar with the UML and its notation, you may find the UML Primer (located in
Appendix A) to be a helpful resource. The primer is also a great refresher for anyone who has not
worked with the UML for a while.
119
Chapter 3
NOP
Order Processing
Inventory
uses
Customer uses
Status Validate User
Order
In the simplest terms, NOP identifies a Customer actor and allows that actor to perform one of two actions:
❑ Submit an order. The Order Processing use case handles this action.
❑ Retrieve order status. The Status use case handles this action.
The third use case in this diagram, Validate User, does exactly what the name implies: it checks a user's
right to perform a particular activity. The remaining actors, Inventory and Order, represent other
Northwind systems external to NOP.
If you're new to UML, you might be surprised to see a computer application referred to as an
"actor" in a use case diagram. Nonetheless, those cute little stick figures can represent almost
anything that interacts with the main use case – human or non-human.
Laura believed that this preliminary version of the primary use case captured the spirit of the
application. She subsequently provided the details needed to construct the business rules for the Order
Processing use case. (Later, we'll see some activity diagrams that relate much of this information.)
Discussion with Sally cleared up a few key issues suggested by the preliminary Primary Use Case draft:
❑ The web page responsible for accepting customer orders would handle user authentication
and authorization. Her team was already building an ASP.NET application utilizing its latest
security widgets. Therefore, NOP did not need to validate users.
❑ The Inventory actor amounted to a legacy application built with Visual Basic 6. (Jack laughed
to himself at hearing a VB6 application described as a "legacy" system.) This system managed
Northwind's product price and quantity data.
❑ The Order actor provided the big surprise. This actor equaled Northwind's recently-built
Order system that incorporated XML and MSMQ. In this system, orders are initiated by
dropping an XML-based order document into a specific message queue.
120
Design Patterns in the Middle Tier
Jack added this newly acquired information to the diagram, and thus finalized the Primary Use Case:
NOP
Activity Diagrams
Laura reviewed the activity diagrams that describe the processing that each order is submitted to before it
is passed to Northwind's Order system. The preliminary and final diagrams did not vary too much – Jack
had captured the business rules quite well! But, once again, the analysis produced a surprise. It turned out
that the Order system handles two different types of order documents – and they use different XML
document validation specifications! One type (the "Special Order") worked with an older XML validation
standard – DTD documents. The other ("Order") adhered to the latest standard from the W3C – XSD.
There was some similarity in the processes required for the two different types of order: both required a
check against the inventory and both resulted in the placement of an order into Northwind's Order
system. However, differing business rules complicated things. "Special Order" did not check order
credit limits or deal with required ship dates. "Order" did check credit limits and ship dates. These
different business rules, combined with the different XML document validation specifications, made the
order process confusing!
Jack summarized these differences with a simple table. The first column contains a brief description of
the processing action. The second and third columns contain how that action might be effected in NOP.
He saw three choices: Not Required, XML Validation, or Code:
121
Chapter 3
Based on this table, Jack produced the following Order Processing and Validate activity diagrams:
In the Validate Order activity diagram, note that Order does not explicitly need to inspect the
ProductID and Quantity, because this is done in the XSD schema.
It was at this point that Jack started thinking in terms of design patterns. Two ideas came to mind. First,
the Validate Special Order and Validate Order activities hinted that the Strategy pattern might be
appropriate when coding the validation process. Second, the similarities of the two order types (outlined
above in the Order Processing activity diagram) far outweighed their differences. One only needed to drop
a few behaviors from one to realize the other. This characteristic suggested a Decorator pattern to Jack.
Homing in on Patterns
Of course, it would be naive to assume that Jack selected these two patterns without some serious
thought. In fact, he worked though several possibilities before making these selections. Let's examine
Jack's decision-making process in greater detail.
Application of a Decorator pattern wasn't immediately obvious. But Jack looked at the different action
states of the Order Processing activity diagram, and realized that he could think of them as responsibilities
that could dynamically be added to (or dropped from) any order. Then he looked again at the
Decorator pattern's intent:
This was enough to convince him that a Decorator pattern could do the job.
122
Design Patterns in the Middle Tier
If the activity states had been richer, more complex objects, then the Decorator would have looked less
appealing (because the Decorator assumes a certain "likeness" between the supporting objects, which
simply "decorate" the common object). In that case another structural pattern, the Composite, would
have been more promising. The Composite pattern's intent shows the difference:
❑ Intent of GoF Composite: Compose objects into tree structures to represent part-whole hierarchies.
Composite lets clients treat individual objects and compositions of objects uniformly.
(Chapter 1 contains descriptions and demonstrations of both of these patterns. If the Decorator pattern
seems a little hazy, you could turn back to Chapter 1 and reacquaint yourself with it. The Decorator
plays a central role in our application!)
Finding the most suitable pattern for Validate Special Order and Validate Order activity states proved a
more daunting task. The reason was simple: at first it was unclear to Jack what type of pattern best
suited the problem! He took a few minutes to examine the three different GoF design pattern types in
turn, remind himself of the high-level purpose of each, and eliminate some pattern types from the hunt:
❑ Creational patterns abstract object instantiation. While this functionality might be required to
implement validation, flexible object creation was not really an issue here. (Scratch
creational patterns.)
❑ Structural patterns help build large, complex objects. Validation objects did not look like they
would end up as multi-faceted behemoths. (Scratch structural patterns.)
Behavioral patterns play with algorithms and relationships between objects. Judging from the
Validate Special Order and Validate Order activity diagrams, managing algorithms seemed to
hit the mark best.
By deciding upon a behavioral pattern, Jack reduced the problem to picking the best pattern from a
smaller pot. Judging from its intent, the Command had some initial appeal:
❑ Intent of GoF Command: Encapsulate a request as an object, thereby letting you parameterize
clients with different requests, queue or log requests, and support undoable operations.
But this looked like overkill. Well, what about the Strategy and Template Method patterns? Both intents
looked promising:
Jack pondered this one for a while, and even looked at some code samples to help get a better take. It
was looking at the code that helped him differentiate between these seemingly similar patterns. The
Template Method pattern enforced the same algorithms. The validation algorithms suggested in the
Order and Validate Order activity diagrams were not so common as to merit a straitjacket approach. Jack
felt that the Strategy pattern captured the requirements of the validation activity states much more
successfully – they looked like a "family" that needed to be "interchangeable" when it called for it.
123
Chapter 3
Sequence Diagrams
Early on, Jack decided that he would deal with business rules and performance requirements as two
different issues. To avoid mixing "plumbing code" with "business code", he decided to split NOP into
two applications, ReceiveDocument and ProcessDocument. ReceiveDocument would focus on
responding reliably and quickly to web clients; ProcessDocument could process order documents. But
the decision to split up NOP begs the obvious question – how will all these parts work together?
The UML's sequence diagrams love to answer questions like this. However, they do more than just map
out the dynamic exchange of messages between objects. Sequence diagrams also give the architect a
sense of the chronological ordering of messages. In our case, that's more than a trivial concern, since
NOP must support a B2B web-based client. Order processing will most likely be a time-consuming
exercise, and clients will not want to wait around for it to finish computing before they get a response!
Our sequence diagram will need to demonstrate how the ReceiveDocument application will get its job
done while keeping client responses timely. Oh, and don't forget, we'd better not lose any orders either.
Fortunately, Jack already knew how to handle this problem.
❑ First, safely store the end user's request and as quickly as possible return an informative response.
❑ Next, forward the request for processing, and when finished, allow the end user to retrieve the result.
Strangely, the pattern is so familiar to developers that build B2B Internet solutions that it doesn't even have a
name! Therefore, for the sake of simplicity we will refer to it as the Store and Forward (SaF) pattern.
No doubt, some readers are probably wondering why the Store and Forward pattern is considered a
"true" pattern. After all, there isn't a bunch of classes around to demonstrate it. However, a pattern
is more than just a class diagram; it is a time-tested solution intended to solve a recurring problem.
The Store and Forward pattern meets this criterion; it just turns out that a sequence diagram
communicates SaF's intent better than a class diagram.
As we can see in the following sequence diagram, the ReceiveDocument application embodies the SaF
pattern. It is responsible for storing the order (Send Message) and forwarding it to ProcessDocument
(Load). The last function it performs (Document ID) will allow the end user to get a Document ID, so
they can trace the order in the system.
:Receive :Pickup
Persist
Send Message
Execute
Document ID Load
ReceiveDocument
124
Design Patterns in the Middle Tier
The second sequence diagram shows the whole process, and how ReceiveDocument fits into the
bigger picture:
PickupAndProcess
Place Order
X
X
Note that in the first sequence diagram, the Load message was drawn as a solid line with a one-sided
arrowhead. This notation represents a subtlety of the SaF pattern that is crucial if the pattern is to work
properly – namely, that the Load method will be an asynchronous method call without a return value.
Class Diagrams
With some solid use case, activity, and sequence analysis in hand, Jack was ready to design the class
structure. It also helped that he had envisioned the appropriate design patterns! Based on all analysis he
knew that ProcessDocument was the only application involving a complex web of classes. Therefore,
that was where Jack focused his class design energies.
Some readers may consider this miserly approach to class diagrams to be rather cavalier. We could
certainly diagram every class in NOP. However, would all these additional artifacts make us any
smarter? Probably not. So why bother generating many near-worthless diagrams?
Next, Jack decided what methods the concrete Document class should contain. This constituted a critical
design task since these methods would be available to every class in the pattern! He knew to avoid the error
of including any optional or specialized methods, since they rightly belonged to decorators. He rightly
kept the list focused on those methods most likely required by all order documents:
125
Chapter 3
Method/Property Description
Load() Loads the order document into a private XML document variable via the
private function LoadXMLDoc
DocID Returns the document's assigned processing reference ID (not to be
confused with an Order ID!)
DocXML Returns a reference to the private XML document
DocType Returns the document type (Special Order or Order)
DocStatus Returns the current status of the document ("processing", "errors",
or "complete")
DocErrors Returns a listing of any errors encountered while processing a document
With the above list of methods, the task of writing the Decorator's interface class (the
ProcessDocument class) was an academic exercise. Likewise, writing the Decorator pattern's decorator
class (the Processes class) proved an equally easy task since it just implemented all of the Document's
public methods.
Method/Property Description
IsValid() Returns a Boolean value indicating whether or not the document is valid
ValidationErrors() Returns a listing of any errors encountered while validating an order
The Strategy pattern had effectively transferred the "hard work" to the coding of the validation
algorithms. And that did not surprise Jack, given the Strategy pattern's intent – Define a family of
algorithms, encapsulate each one, and make them interchangeable…
126
Design Patterns in the Middle Tier
interface
ProcessDocument
OrderManager +Load()
+Load() +DocID()
-SetStatus +DocXML()
+DocType()
+DocStatus()
+DocErrors()
Document Processes
+Load() +Load()
+DocXML() +DocXML()
+DocType() +DocType()
+DocStatus() +DocStatus()
+DocErrors() +DocErrors()
-LoadXMLDoc() +New()
+XMLValidationEventHandler()
Decorator Pattern
interface
OrderValidationStrategy
+IsValid()
+ValidationErrors()
ValidateSpecialOrder ValidateOrder
+IsValid() +IsValid()
+ValidationErrors() +ValidationErrors() Strategy Pattern
+New() +New()
Jack has done enough plotting and planning. Let's move on to the code!
127
Chapter 3
❑ Set up the infrastructure (for example, create the database storing document status messages).
❑ Build the debugging and test harness (TestHarness) application.
❑ Build the projects for the supporting applications (LegacyWrapper and OrderManagement).
❑ Build the two NOP applications (ReceiveDocument and ProcessDocument).
As a side note, you may have noticed that the test harness application is listed before the NOP
application. This is deliberate. Writing the test harness first helps ensure that we don't "lose sight" of our
goal when we actually get coding.
This methodology may sound familiar to you. It constitutes a significant part of the "Extreme
Programming" practice. One of its tenets holds that developers always build their application test
harness first. This forces the developer to be sure of exactly what features the application needs to
support, and also facilitates unit testing during the coding phase. There's a lot more to Extreme
Programming than this particular tenet. If you wish to learn more about Extreme Programming,
Kent Beck's book Extreme Programming Explained: Embrace Change (Addison-Wesley, ISBN: 0-
201616-41-6) is a good place to start.
There are a few miscellaneous points worth noting before we start coding:
❑ While NOP will someday support a web service, we're building a fat client for debugging and
testing. Bringing web services into the picture during the construction phase only adds an
unnecessary layer of complication.
❑ Just because we test with a fat client does not mean we can ignore the requirements of a web
service. For example, one of those requirements concerns acceptable parameter data types.
The current specification does not allow for passing an XML document as a parameter.
Therefore, we used a string value representation of an XML document.
❑ We're not worrying about where our XML validation files are physically located since all of
our applications run under one solution. Once loaded into memory by the test client, they
remain loaded. In reality, NOP would need to look in its own cache for the necessary DTD
and XSD validation documents.
128
Design Patterns in the Middle Tier
3. Ensure that our two SQL Server 2000 databases are available.
Registering Inventory.dll
In spite of all of the new toys and widgets contained in Visual Studio .NET, you still need to make sure
that your computer's registry knows about any COM components it might utilize. In this demonstration,
the NOP application will make use of the Inventory.dll component library.
Save the file Inventory.dll to a folder on your hard disk, then navigate a command prompt to that
folder and execute the following command:
regsrv32 Inventory.dll
This will register the component library, and allow us to access it via COM Interop technologies.
We will examine the source code of Inventory.dll later in this chapter. The source code and
DLL are supplied along with the rest of the code for this book, at http://www.wrox.com.
Setting up MSMQ
Microsoft Message Queue (MSMQ) provides the private message queue services that NOP relies on to
persist order documents. It is a Windows 2000 component that may need to be installed locally. You
can check to see whether MSMQ services are installed on your machine, by activating the
Computer Management MMC via the Administrative Tools menu. The figure below indicates that
MSMQ is installed:
If your computer does not have Message Queuing listed under the Services and Applications node,
then install MSMQ via the Control Panel's Add/Remove Windows Components feature.
129
Chapter 3
130
Design Patterns in the Middle Tier
Note that the T-SQL CREATE DATABASE command will fail if the FILENAME folders do
not exist!
Create-NWindOrderStatus-Database.sql also creates a few other SQL Server objects that our
document processing application relies upon. The most important of these, of course, is the
DocumentStatus table – this is the home of the status records. The other object is a SQL Server-based user
identity called DocWrox, which has the necessary permissions to access the NWindOrderStatus database.
If your instance of SQL Server 2000 only supports Windows authentication, then you will need to
change this option to support SQL Server authentication too. This may be accomplished through the
SQL Server Enterprise Manager MMC – select the SQL Server instance's Properties page and
from there select the Security tab. After changing the Authentication property, SQL Server will
need to be restarted.
The Inventory
Before delving into Northwind Order Processing (NOP), it's worth taking a moment to inspect the
applications that provide product data via Northwind's Inventory application. Accessing this "legacy"
application is accomplished via a simple .NET application entitled LegacyWrapper.
For once we have a really meaningful (albeit boring) application name – LegacyWrapper! OK,
maybe this name is only meaningful to hardcore object-oriented developers. These guys regularly apply
the term "wrapper" when they use either the Adapter or Façade patterns to protect the innocent from a
legacy application. In our case, our application is using a Façade to "wrap" a dangerous and scary
VB6 component! To highlight an extra source of ambiguity, we should note here that the GoF also
listed the term "wrapper" as an alternative name for a Decorator design pattern.
131
Chapter 3
The Façade pattern provides us with more than just an exciting lesson in nomenclature. First, it (almost)
spares us from having to read the next few paragraphs since it shields us from the inner workings of
Inventory.dll. Second, it helps keep our architecture simpler. We can get a visual idea of this notion
by augmenting a fragment of the ProcessDocument class diagram with the LegacyWrapper
application's ProductInventory class diagram, as shown overleaf:
Processes
+Load()
+DocXML()
+DocType()
+DocStatus()
Decorator Pattern +DocErrors()
(Partial) +New()
CheckInventory
+New() ProcessDocument
Application
LegacyWrapper
Application
Façade Pattern ProductInventory
+CheckProductPrice()
+UnitsInStock()
❑ Are there any data type conversion issues? Remember that the .NET Common Language
Runtime (CLR) does not work easily with all Visual Basic 6 data types.
❑ Is the Visual Basic class public? In other words, is the class instancing property set to
MultiUse rather than Private?
Let's step through the code in Product.cls, and pick out the key elements. The first thing we notice is
that the class instancing property is set to MultiUse:
132
Design Patterns in the Middle Tier
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "Product"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
Great! This makes the VB6 Product class appear as a normal .NET public class.
There's more good news in the next fragment! Our first function, InstockCount(), has a plain old
integer data type that .NET happily works with. Also, ProductID is passed by value and that
minimizes the number of calls between COM and .NET:
Set rs = cn.Execute(strQuery)
If Not (rs.BOF Or rs.EOF) Then
intResult = rs(0)
Else
intResult = -1
End If
rs.Close
cn.Close
GoTo ExitFunction
LocalError:
Err.Raise Err.Number, "InstockCount", Err.Description
ExitFunction:
InstockCount = intResult
CleanUp:
If Not rs Is Nothing Then
Set rs = Nothing
End If
If Not cn Is Nothing Then
Set cn = Nothing
End If
End Function
133
Chapter 3
The data type of the return value is as important as the function's parameter type, and it is looking good.
The next function, PriceCheck(), appears as clean as InstockCount, because Doubles are as
digestible as Integers:
Set rs = cn.Execute(strQuery)
If Not (rs.BOF Or rs.EOF) Then
intResult = 1
Else
intResult = -1
End If
rs.Close
cn.Close
GoTo ExitFunction
LocalError:
Err.Raise Err.Number, "PriceCheck", Err.Description
ExitFunction:
PriceCheck = intResult
CleanUp:
If Not rs Is Nothing Then
Set rs = Nothing
End If
If Not cn Is Nothing Then
Set cn = Nothing
End If
End Function
The VB6 code does not suggest any potentially vexing COM Interop issues. In particular, all method
parameters are passed ByVal and the parameters' and return values' data types are all "blittable". (In
other words, there are no special data conversion requirements between COM and .NET here. In some
cases, these requirements do exist. The cost can range from slight performance degradation to having to
tweak the code in places.) In addition, there are no complex data types – like VB6 user-defined types
(UDTs) or variant arrays (ugh!) – to deal with.
134
Design Patterns in the Middle Tier
The code does contain one potential problem. Namely, the Inventory application requires the non-
.NET version of the ADO libraries. (This problem is so obvious that an experienced developer could
easily miss it.) Fortunately, that probably will not be an issue since the .NET runtime installs the latest
non-.NET version of ADO. Moreover, Product.cls does not appear to be making any "discontinued"
or deprecated ADO method calls.
Many readers of this book will be developers with several ADO libraries installed on their machine,
and to those readers this issue may seem academic. However, be warned! You never know what can
happen when deploying applications to "normal" desktops!
For those curious souls, GetConnectionString() refers to a function in a BAS module (Main.bas)
that loads the required connection information contained in NWind.udl:
Finally, if you have Microsoft's Visual Basic 6 installed on your computer, you can test the Inventory
application using the project contained in the \Legacy\Test folder.
The LegacyWrapper
Building a .NET application that communicates with Inventory.dll is a straightforward exercise.
After creating a new C# class library project, called LegacyWrapper, within our VS.NET solution, we
just reference the COM component and write a class that accesses it.
Pattern alert! We're creating a reference to another class with the sole intention of
merely calling its methods. This is the first step towards implementation of a Façade
pattern, as we'll see in this section.
Here's the Add Reference dialog that is presented by Visual Studio .NET when you add a COM
reference to the project:
135
Chapter 3
If you do not see Inventory.dll in the list of available components (as shown above), then try
registering the component using regsvr32 (as discussed earlier).
Now change the name of the class file created by VS.NET from Class1.cs to
ProductInventory.cs. We're ready to code our class called ProductInventory.
using System;
using Inventory;
namespace LegacyWrapper {
The ProductInventory class will be responsible for accessing the Inventory's Product class, and is
given below. Recall from our object diagrams that this class has two methods, CheckProductPrice()
and UnitsInStock():
int result = 0;
try {
result = m_Inventory.PriceCheck(productId, price);
}
catch {
string msg = "Unable to check price for Product Id = " + productId;
throw new Exception(msg);
}
136
Design Patterns in the Middle Tier
int result = 0;
try {
result = m_Inventory.InstockCount(productId);
}
catch {
string msg = "Unable to retrieve units in stock " +"for Product Id = " +
productId;
throw new Exception(msg);
}
return result;
}
}
}
Our interpretation of the information returned from the VB6 application makes it much easier to use
Inventory.dll. For example, look at the CheckProductPrice() method. It ultimately calls
Inventory.Product's PriceCheck() method, which returns an Integer. But the
CheckProductPrice() method returns a Boolean. That's because clients of the
CheckProductPrice() method just need to know whether the submitted product price was correct.
They don't care to know about a bunch of integers.
Moreover, there is another way in which the Façade patterns may have simplified working with
Inventory.dll. Imagine that Product.cls contained 40 methods, instead of only two, and that the
other 38 of those methods were of no interest to us. We would probably prefer to hide these
nonessential routines from ProcessDocument. Thus, once again, the Façade helps us by being selective –
in other words, by excluding Inventory.dll's superfluous methods.
137
Chapter 3
Once you've added the TestHarness project to the VS.NET solution, change the name of the file
Form1.cs to frmMain.cs. Next, bring up the Properties pane (by hitting F4 and clicking on the form)
and change the Text property of the form frmMain from Form1 to Order Text Processing.
Now we can set about building the workings of the test application. First, we'll use the IDE features to
add some controls, and then we'll visit the code and add some further implementation.
138
Design Patterns in the Middle Tier
Just in case you're wondering why txtCheckResult is a read-only textbox, our intention is that it
should resemble a label. The explanation is simple. After submitting an order document, any error
message we receive will be displayed here. These error messages are read-only. They may also be quite
lengthy – a characteristic that suggests a multi-line feature. Unfortunately a label control lacks a
multi-line feature, so we use a textbox control instead.
using System.Xml;
//using ReceiveDocument;
139
Chapter 3
Note that the lines referring to the ReceiveDocument.Receive class have been commented
out. This is because we have not yet built the ReceiveDocument application, so we can't
reference it yet! By commenting these lines out, we'll be able to test our TestHarness's ability to
load and read XML order documents; and later, when we've built the ReceiveDocument
application, we'll return to the TestHarness code and complete its implementation by adding the
ReceiveDocument.Receive class references.
The radio buttons are used to determine which of the two order documents will be loaded into the big
textbox, txtCheckResult. In a moment, we will add code to make sure that rbSpecialOrder is
selected by default, thereby loading SpecialOrder.xml when the application starts up.
if( rbSpecialOrder.Checked ) {
txtXML.Text += "<!DOCTYPE SpecialOrder SYSTEM 'SpecialOrder.dtd'>" +
Environment.NewLine;
140
Design Patterns in the Middle Tier
while( objXML.Read() )
txtXML.Text += objXML.ReadOuterXml();
txtXML.Focus();
txtXML.Select(0, 0);
txtCheckResult.Text = string.Empty;
lblSubmitResult.Text = string.Empty;
}
Again, we'll comment out the m_DocReceive method calls for now, since we still don't have a
reference to the underlying ReceiveDocument.Receive class. We'll return to the application
to reinstate these lines of code later.
public frmMain() {
InitializeComponent();
rbSpecialOrder.Checked = true;
cmdSubmit.Focus();
//m_DocReceive = ReceiveDocument.Receive.GetInstance();
}
Did this last line of code appear a little unusual? It should have, because we instantiated the
ReceiveDocument.Receive class in an unusual fashion. We avoided the more typical call to the
default constructor method with a custom method entitled GetInstance(). The explanation is quite
simple – ReceiveDocument.Receive implements the Singleton design pattern.
141
Chapter 3
As we described in Chapter 1, the Singleton pattern allows only one instance of the subject class to be
created. It accomplishes this by modifying its default constructor from Public to Private. In addition,
with that one alteration only another public method within the class can deliver an instance of the same
class. In our case, the static method GetInstance() holds the keys to the kingdom.
Note that only a class's static methods can be called when the class is not instantiated.
If you are unfamiliar with XML programming, it may prove to be a bit frustrating at first. For
example, in our code above, despite including the required validation documents, the XML
documents were not fully validated on loading. As we will see later, that requires an entirely
different step!
Once you've built it, you should be able to use it to load either the "Order" or "SpecialOrder" XML
documents (as a string) into the txtXML textbox control by clicking one of the radio buttons. While
you're testing the application, you may notice that txtXML accepts user edits and changes. This feature
will eventually allow us to "break" the order for NOP testing and debugging purposes. (That's what this
application is for, right?)
The two sample test XML documents (Order.xml and SpecialOrder.xml) can be found in
the \TestHarness\bin\Debug folder. If you move them elsewhere, don't forget to grab their
soulmates (Order.xsd and SpecialOrder.dtd), too!
142
Design Patterns in the Middle Tier
This is the kind of situation in which it's useful to use the VS.NET IDE's Task List feature. Bring up the
Task List pane (Ctrl+Alt+K) and right-click on it to change the filter to All. Now right-click on each of
the four commented-out lines that refer to the ReceiveDocument.Receive class, and select
Add Task List Shortcut. This will add four tasks to your task list, like this:
You now have a nice reminder of what to fix patiently awaiting your attention!
❑ First, order documents are temporarily placed into a local queue. As soon as that is
accomplished, it returns document identification to the client. Since this process occurs
promptly, the end user experiences a minimal response time to their HTTP-based request.
❑ Second, almost as quickly as the client gets their response, document processing is initiated.
During the processing the client can request status messages for this recently submitted order.
And when document processing is completed, the client can receive the "official" order
identification via the same status messaging feature.
The application contains three significant classes: QueueManager, Receive, and Pickup. I suspect most
readers will guess what QueueManager does – it creates and provides a reference to a queue. The
functions of the other two classes may not be so obvious. Between them they manage the two parts of the
Store and Forward pattern: Receive manages the "store" part, and Pickup manages the "forward" part.
For those who like to read the end of the story first, here is a snapshot of how the ReceiveDocument
application's Solution Explorer should end up:
143
Chapter 3
After creating the ReceiveDocument project (a C# Class Library project) to the VS.NET solution,
remove the default class file (Class1.cs) and add a new class file called QueueManager.cs. To the
ReceiveDocument project you will need to add a reference to the Microsoft messaging component
(System.Messaging.dll) as shown below:
Finally before we start coding the class itself, we'll add the following using statements (which will help
us to keep the object references short):
using System.Messaging;
using System.Threading;
namespace ReceiveDocument {
144
Design Patterns in the Middle Tier
OK, let's get started. The first thing to notice is that we change the class modifier from public to
internal, to keep the riff-raff from using the QueueManager. Of course, if we had left
QueueManager public our application would not crash if misused (hopefully). But experienced
object-oriented developers might get confused. They expect public classes to be available for
public consumption:
The beauty of a centralized queue manager shows up immediately. While we called our queue
InboundDocuments, you could use any other name here without breaking anything.
The .NET Common Language Runtime (CLR) exposes a wide variety of thread-locking mechanisms.
We're not obliged to use a Mutex object here, but I find it provdes the right mix of flexiblity and effort
compared to the other .NET locking techniques:
It is interesting to note, though, that as we discuss patterns, details such as a language's thread locking
and synchronization methods are not too important.
The class has a single method, GetQueue(). Notice the m_Mutex.WaitOne() call here – we'll discuss
what the Mutex is doing in a moment:
m_Mutex.WaitOne();
If desired queue exists on our computer, we'll grab it. We'll use an If clause for this:
if( MessageQueue.Exists(msgQueueName) {
try {
msgQueue = new MessageQueue(msgQueueName);
}
catch( Exception ex ) {
string msg = "Unable to access the " + QUEUE_NAME + " queue";
throw new Exception(msg, ex);
}
finally {
m_Mutex.ReleaseMutex();
}
}
But if the queue does not exist, then we will create it.
145
Chapter 3
else {
try {
msgQueue = MessageQueue.Create(msgQueueName, true);
}
catch( Exception ex ) {
string msg = "Unable to access the " + QUEUE_NAME + " queue";
throw new Exception(msg, ex);
}
finally {
m_Mutex.ReleaseMutex();
}
}
return msgQueue;
}
}
}
The use of the Mutex object precludes a significant problem that would otherwise come into play in a
multithreaded application such as ReceiveDocument. To understand this problem, consider what
would happen if two threads both reach the following line of code before the queue msgQueueName is
available or created:
if( MessageQueue.Exists(msgQueueName) )
Not surprisingly, in this scenario there would be two efforts to create the same message queue – and this
is a behavior that we want to avoid! By locking things up temporarily (via the m_Mutex.WaitOne()
method call), we prevent that problem. If you're worried about the performance hit associated with such
an approach, note that we avoid that issue by maintaining a reference to this queue in our
ReceiveDocument.Receive object. This avoids the need to make locking calls to check whether or
not the queue exists.
❑ First, clients will not most likely have to wait for an instantiation process with every call. Once
a singleton is instantiated, it becomes available to any and all callers. As we saw in Chapter 1,
it accomplishes this feat by disallowing arbitrary clients from instantiating it via the default
constructor. If a client requests a singleton object, they must acquire it by calling some
cleverly named public method like GetInstance(). That method will return a reference to
the instantiated singleton object.
146
Design Patterns in the Middle Tier
This arrangement also absolves the caller from worries relating to instantiation issues. The
singleton itself figures out the complexities of instantiation, before returning a reference to it.
In other words, the Singleton pattern even abstracts away any creation woes from the caller.
And it does so with minimal cost – the caller only needs to know that magic method that we
called GetInstance() instead of the default New() constructor method.
❑ Second, access to and usage of the application's underlying resources can be optimized. In a
moment, we will see an excellent example of this feature as we work with a message queue object.
Let's build the Receive class. We'll create a new class file, Receive.cs, and start to add the following code:
using System.Messaging;
using System.Data;
using System.Data.SqlClient;
If your SQL Server instance is not hosted on your LocalHost machine, then you may need to
change the server attribute here to reflect that. The uid and database (DocWrox and
NWindOrderStatus) were created by the Create-NWindOrderStatus-Database.sql
script that we ran earlier (in the SQL Server setup section) – you shouldn't need to change those
attributes unless you also changed Create-NWindOrderStatus-Database.sql.
There are three more private member objects that we'll need:
There are a couple of points here. First, in creating m_MsgQueue, we begin the demonstration of how a
Singleton pattern helps resource management. We will ultimately set it to a queue once and only once –
when we instantiate the Receive object. This minimizes the negative performance impact that we incur
by using a Mutex in QueueManager.
Second, note that later on we will need the reference to the Pickup class, but we haven't built that class
yet – so for testing purposes we will comment out this line for now. (Again, you could add a task to your
Task List to remind yourself that we need to return to this!)
The GetInstance() method works in combination with the m_Instance shared variable to provide
instances of Receive:
return m_Instance;
}
147
Chapter 3
The class needs a constructor, but we will set it to Private. If we failed to restrict access to the default
constructor, we could bet someone will call it. That would nullify our efforts to create a more efficient
Receive class:
private Receive() {}
The Persist() method delegates most of its work to two other key methods. The first method,
SendMessage(), we will look at shortly. The other temporarily commented one, the Execute()
method of the Pickup class, will ultimately contribute significantly to the client's perception of
NOP's performance:
try {
docID = SendMessage(xmlText);
}
catch( Exception ex ) {
string msg = "Unable to recieve document";
throw new Exception(msg, ex);
}
m_Pickup.Execute(docID);
return docID;
}
Note that in this method, docID is not just another return string. It provides a reference to the client for
checking the status of their submitted order documents. Since NOP doesn't complete processing before it
sends a response, there needs to be a mechanism that allows clients to get information about their
submitted orders. In our application, we use "status" messages to provide this service. The following
information flow diagram summarizes this process:
Presentation Tier
Submit Document
docID NOP
ReceiveDocument
Document
docID Status NWindOrderStatus
Messages
Document Status ReceiveDocument
148
Design Patterns in the Middle Tier
The message queue assigns a unique value to each successfully received message (or, in our case,
successfully received document). We'll use this identification number for our docID. This identification
number possesses another nice feature, aside from uniqueness: embedded within it is a GUID reference
to the machine that houses the queue.
The value of a reference to a specific machine is really enhanced once NOP is placed on several web
servers. Then, if one of these servers breaks down, it's easy for an administrator to check the
document status log and see which items were left in limbo. (We could write another application to
help manage "lost" messages, but that can be covered in another book all together.)
We'll take a look at the private SendMessage() method in a moment, but first let's complete the list of
public methods by putting down the code for GetStatus(). The GetStatus() method responds to
end user requests for information about a specific, recently submitted order. It utilizes basic ADO.NET
tools and we will not spend too much time discussing them:
try {
cn = new SqlConnection(CONNECT_STRING);
cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd .Connection = cn;
cn.Open();
result = (string)cmd.ExecuteScalar();
catch( Exception ex ) {
string msg = "Unable to retrieve status for DocID = " + docID.ToString();
throw new Exception(msg, ex);
}
finally {
if( cn.State != ConnectionState.Closed )
cn.Close();
}
return result;
}
149
Chapter 3
With the public methods out of the way, it's time to look at the heart of the Receive class – the
SendMessage() method. It is all about one thing – getting our recently received XML order data into
the InboundDocuments queue:
Since we created the InboundDocuments queue as transactional, we need the supporting MSMQ
transaction object to send messages. Later on we will harvest the benefits of this additional work.
Transactional MSMQ queues are not all the same. In our case, the transaction attribute implies an
internal MSMQ-specific transaction. It is not the same type of transaction associated with
COM+ Services.
msgDoc.Label = DateTime.UtcNow.ToString();
msgDoc.Formatter = new BinaryMessageFormatter();
msgDoc.Recoverable = true;
msgDoc.Body = xmlText;
Setting the message's recoverable property to True adds another layer of reliability to our application.
It forces MSMQ to persist the message to disk immediately, rather than keeping it temporarily cached
in memory until a good moment comes along to flush it to disk. While this feature potentially slows
MSMQ a little, it also prevents a crashed web server from losing any messages!
try {
msqTrx.Begin();
m_MsgQueue.Send(msgDoc, msqTrx);
docID = msgDoc.Id;
msqTrx.Commit();
}
catch( Exception ex ) {
msqTrx.Abort();
finally {
m_MsgQueue.Close();
}
return docID;
}
}
150
Design Patterns in the Middle Tier
❑ First, remove the comment quotes on the four lines that alluded to ReceiveDocument
and Receive.
❑ Next, add a reference to the ReceiveDocument application:
❑ Finally, if you added those four tasks to your Task List, you can remove them now – they
have fulfilled their purpose!
After completing these tasks we are ready to rebuild the TestHarness application and use it to submit
a few test orders. After submitting some orders, check your Computer Management MMC. If
ReceiveDocument executed without any errors, your MMC should resemble the following screenshot.
In the sample below, two order messages sit patiently in the inbounddocuments queue:
151
Chapter 3
We can see where the Pickup class comes to the fore, by walking through ReceiveDocument's
sequence diagram:
:Receive :Pickup
1
Persist
2
Send Message
5 Execute 4
Document ID 3 Load
ReceiveDocument
At this stage, the "store" portion of SaF is completed. Pickup initiates the "forward" part:
With Pickup's processing completed, we go back to Receive so it can send the client a response:
5. Document ID. After Step 4 has kicked off, the client is sent a reference to their submitted
Order document for future status queries.
OK, let's build the Pickup class now. Add a new class file, Pickup.cs, to the ReceiveDocument
project. Then, add the following using statements to the file:
using System.Messaging;
using System.Threading;
//using ProcessDocument;
152
Design Patterns in the Middle Tier
Once again, we're commenting out the last of these Import statements because we haven't built the
ProcessDocument application yet. We'll return to this code to instate this line once the
ProcessDocument application is complete; meantime, you might want to add a task to your Task List
as a reminder of this.
Now let's move onto the class itself. We'll modify the class to internal. As we discussed earlier, this
modification helps ensure that other developers using this application do not accidentally call the
Receive class:
thread.IsBackground = false;
thread.Start();
}
By setting the thread's IsBackground property to false, we prevent it from dying prematurely in the
event of the untimely death of a parent thread. Thus, if ReceiveDocument encounters severe
difficulties, ProcessDocument may still complete its task. With a simple line of code we further
enhanced NOP's reliability!
The Execute() method ultimately initiates the all-important ProcessDocument, our yet-to-be-built
order processing application, via a .NET thread object.
We're paying particular attention to how we created this thread object, ensuring each thread "keeps to
itself" throughout its lifetime. (The last thing we need is for an instance of one ProcessDocument to
get mixed up with another instance!) We accomplish this feat by including an inner class within Pickup
that is instantiated with each new thread.
Inner classes are another one of those cool object-oriented features .NET provides developers. By
placing an entire class inside of another class and modifying its accessor to private, only that
outer class can instantiate it. This feature comes in handy when developers need a specialized class
that only the outer knows about – like in our case!
The heart of the Pickup class lies buried deep within its inner class, PickupAndProcess. Aside from
housing the necessary state data (docID), it contains the real Execute method:
153
Chapter 3
msgQueue = QueueManager.GetQueue();
msgQueue.Formatter = new BinaryMessageFormatter();
try {
msqTrx.Begin();
msgDoc = msgQueue.ReceiveById(m_DocID, msqTrx);
xmlText = msgDoc.Body.ToString();
manageOrder.Load(m_DocID, xmlText);
msqTrx.Commit();
}
catch {
msqTrx.Abort();
}
finally {
msgQueue.Close();
}
}
}
The above try...catch makes the dream of reliability come true in NOP. With the assistance of our
transactional queue we are able to ensure the safety of our original order in the event of a mishap within
the ProcessDocument application. For example, if ProcessDocument fails for some reason, the
source order returns to the queue via the msgTrx.Abort method. Otherwise, if all goes well, the queue
happily gives up the message via msgTrx.Commit.
One last noteworthy point about the Execute() method – it does not return any values and the thread
calling it does not wait for any news to come back. This behavior allows NOP to quickly return the
required synchronous HTTP response to end users without waiting for the potentially time-consuming
manageOrder.Load operation to finish.
If you're building the application for yourself as you step through this chapter, and you're feeling
adventurous, you could take a break now and test your work. Note that in this test scenario, if
everything works OK the inbounddocuments queue will be empty. Don't forget to either delete the
queue or purge the messages – otherwise, the earlier message will still be sitting there in the queue.
While we have already seen much of the class diagrams for this application, a quick peek at the
application's eventual references and files may be instructive:
154
Design Patterns in the Middle Tier
To begin the process of building this application, add a new C# class library project called
ProcessDocument to the solution. Don't forget to add the necessary application references, as shown
under the References node in the above screenshot. (Unfortunately, one of them, the OrderManagement
application, remains a twinkle in our eye; we'll omit that one for now and return to add it later.)
Before diving into the class definitions in ProcessDocument.cs, let's add some using statements.
There are few new ones this time around – System.IO and System.Xml.Schema. The IO class helps
when we are manipulating the XML string-formatted data via the StringReader class, while
XML.Schema assists with XML document validation:
155
Chapter 3
using System.IO;
using System.Xml;
using System.Xml.Schema;
Now let's get onto the class definitions. There will be three classes and an interface in
ProcessDocument.cs – ProcessDocument, Document, Processes, and DocumentRoles.
Some readers may find it disconcerting to put all these classes into a single file. It is certainly hard
to argue with the logic of simplicity – one class/interface, one file. Nonetheless, I feel that putting all
of the Decorator pattern's "fixed" code in one file is extremely logical. If you disagree, feel free to put
them anywhere you like!
While most of the following routines work as simply as they appear, the Load() method kicks off the
critical task of sucking up the string formatted XML order document into the application as a certified
.NET XMLDocument object:
156
Design Patterns in the Middle Tier
The DocStatus and DocErrors properties (which follow in a moment) work in combination to ensure
that a "broken" document does not subsequently get "fixed". This type of error is particularly endemic
of Decorator patterns. This becomes apparent when we consider how a "basic" Decorator pattern
manipulates the concrete class:
interface
Document
Concrete Decorator
In the diagram above, suppose the Concrete class creates a docX object. DocumentDecoratorA then works
on docX, followed by DocumentDecoratorB, and DocumentDecoratorC. Suppose you wrote an application
based on this pattern – despite the obvious possibility that any of these classes could break docX, this
wouldn't worry you. (You just wrote them!) But suppose that, after a few months, another developer
comes along and adds a new DocumentDecoratorD class.
Suppose further that the DocumentDecoratorD developer did not really understand your brilliant
application, and that his new class can validate a docX property that DocumentDecoratorB occasionally
invalidates. The application would then sometimes validate a docX object when it is actually invalid. As
a result, the application would be broken.
If the Concrete class had included an irreversible flag in docX, then this sad tragedy could have been
avoided. For once DocumentDecoratorB invalidated docX, DocumentDecoratorD could not undo this fact.
So, there is a moral to this story. Namely, an irreversible flag (or similar safeguard) is highly
recommended with a Decorator, since too many classes have access to the underlying concrete object.
157
Chapter 3
Note that, aside from providing a way to place errors inside a Document object, DocErrors also sets
the "document broken" flag. Why make a developer write two lines of code to register an error?
The next few lines, in conjunction with the XMLValidationEventHandler routine, validate the entire
XML document line by line. Setting the XMLValidator.ValidationType as we do here keeps things
simpler by making schema validation automatic – otherwise, the ValidationType property must be
set manually (and correctly) for each document. This cool feature works well in our application given
that our XML documents are not too complex and only use XMLValidator's built-in validation types:
ValidationEventHandler xmlEventHandler =
new ValidationEventHandler(ValidationCallback);
try {
StringReader stringReader = new StringReader(xmlText);
while (xmlReader.Read()) {}
The while loop seems worthless, but in fact it quietly reads each line of XML, firing the
XMLValidationEventHandler whenever it discovers a validation error. (It could fire for every line in
the document!) If we don't run the XML document through this loop, it will not get fully validated.
Neither the .NET XmlTextReader nor XmlValidatingReader classes automatically validate
documents. (In fact, XmlTextReader cannot validate at all!) And we sure want to take advantage of our
DTDs and XSDs.
158
Design Patterns in the Middle Tier
The while control loop works so well because of the design of the xmlValidator.Read
method. It returns False when there aren't any nodes left to read.
Now, why bother loading our class's XMLDocument object if the validation failed? ProcessDocument will
not be able to do its job without a valid document. So we check for errors before loading the document:
Recording the errors for the client's information is the only reasonable thing left to do:
catch( Exception ex ) {
DocErrors = ex.Message;
}
}
}
The final method serves as XMLValidator's callback defined via xmlEventHandler. It is referenced
via the ValidationEventHandler statement we saw a moment ago in the DocErrors(). It does the
actual job of placing any validation errors discovered while loading the XMLDocument object into a
Document object:
The class varaible modifier, protected, offers just the right amount of visibility for the decorator. It allows
m_Document to be accessible only from within the Processes class or from its decorator subclasses.
The following methods constitute an excellent example of the powers of an object-oriented language.
They exploit containment and inheritance simultaneously! For example, look at the Load() method.
All the decorator subclasses can load an XML document and process it since they can instantiate a
Document object via the Processes class. And the reason Processes.Load() can execute the
Document.Load() method is because it contains a Document object:
159
Chapter 3
160
Design Patterns in the Middle Tier
Before we get going, all of the decorator subclasses rely on .NET XML services, so do not forget the
following statement on the top of each C# file! (Note: OrderValidationStrategy actually performs
ValidateOrder's XML work, so you can skip this line on ValidateOrder.cs.)
using System.Xml;
❑ Order validation business rules are subject to a great deal of ongoing change
❑ The underlying XML documents may use different XML validation rules (DTD, XDR, XSD,
and so on)
The solution to this potentially ugly design problem comes via the Strategy design pattern. It inherently
provides the necessary flexibility to call (and change!) the validation code without a lot of hassle. A
quick review of a portion of the ProcessDocument class diagram will help clarify this point:
ValidateDocument
+New()
interface
OrderValidationStrategy
+IsValid()
+ValidationErrors()
ValidateSpecialOrder ValidateOrder
+IsValid() +IsValid()
+ValidationErrors() +ValidationErrors()
+New() +New()
The interface, OrderValidationStrategy, provides the muscle of the Strategy pattern. It guarantees
that the decorator class, ValidateDocument, does not need to worry about the services
ValidateSpecialOrder and ValidateOrder offer. These two validation subclasses will always offer
the required methods. How they implement them is of little concern to ValidateOrder.
That ValidateOrder may remain oblivious of validation details provides a tremendous failsafe
mechanism as Northwind's order processing business requirements change. Future developers may
successfully break ValidateOrder or ValidateSpecialOrder, but the ProcessDocument
application rests safely! (True, letting bogus orders through the system will not make some folks too happy.)
161
Chapter 3
We'll start with the simplest of the depicted classes – ValidateDocument. Create a new class file,
ValidateDocument.cs, and insert the following code into it:
switch( doc.DocType ) {
case DocumentCodes.Type.SpecialOrder:
valid = new ValidateSpecialOrder(doc.DocID, doc.DocXML);
break;
case DocumentCodes.Type.Order:
valid = new ValidateOrder(doc.DocID, doc.DocXML);
break;
}
The simple switch statement contains the key logic, deciding which OrderValidationStrategy
concrete class is called upon. It is worth noting that if we had not implemented the Strategy pattern, this
piece of decision logic code would have been huge! All of the validation code we are about to write
would have been sitting in it (or in nearby methods).
We'll create a class file called OrderValidationStrategy.cs file, and add the
OrderValidationStrategy interface and the concrete strategy classes to it. The first thing to add is
an Imports statement for the System.Xml namespace:
using System.Xml;
162
Design Patterns in the Middle Tier
try {
productNodes = doc.GetElementsByTagName("Product");
My first crack at writing this algorithm taught me an interesting lesson. I discovered the importance of
making sure that there were some Product nodes to inspect! Under dubious circumstances
GetElementsByTagName("Product") may not return anything. For this reason, we include the
following check on the number of nodes:
if( 0 == productNodes.Count ) {
m_IsValid = false;
m_ValidationErrors = "Order validation failed; " +
"No product lines identified";
}
With the preliminaries out of the way, it's time to start moving through the order line by line:
The next test is an example of a business rule-required validation check that a DTD could not provide
and an XSD could. DTDs are not very adept at checking numeric values, so we need to do the job:
productID = Int32.Parse(productNodes[i].Attributes[0].Value );
163
Chapter 3
}
}
}
catch( InvalidCastException ex ) {
m_IsValid = false;
m_ValidationErrors += "Invalid product data submitted: " + ex.Message;
}
catch( Exception ex ) {
string msg = "Unable to validate order data for DocID = " + docID;
throw new Exception(msg, ex);
}
}
Comparison between this one method and its counterpart in the ValidateOrder class (which follows)
reinforces the decision to implement a Strategy pattern. The method above is really just a big algorithm
for reading and testing a Special Order. The Order's version (as you'll see) does not look much
different. Nonetheless, the algorithms turn out to be sufficiently different to justify their own class, as
opposed to some gigantic and fragile switch statement.
try {
productNodes = doc.GetElementsByTagName("Product");
if( 0 == productNodes.Count ) {
m_IsValid = false;
m_ValidationErrors = "Order validation failed; " +
"No product lines identified";
}
164
Design Patterns in the Middle Tier
Unlike a Special Order, we did not have to verify that the productID was an Integer – the .NET
XmlValidatingReader did that already! So we check it here by making sure it is a positive integer.
Since Order documents do not worry about quantity, we only have to check price for them:
Here's another validation check unique to an Order document. Special Orders do not have a
RequiredDate property to contend with:
catch( InvalidCastException ex ) {
m_IsValid = false;
m_ValidationErrors += "Invalid product data submitted: " + ex.Message;
}
catch( Exception ex ) {
string msg = "Unable to validate order data for DocID = " + docID;
throw new Exception(msg, ex);
}
}
165
Chapter 3
using System.Xml;
using System.Threading;
using LegacyWrapper;
Read on if you are wondering why we imported System.Threading. All will become clear. Here's the
CheckInventory class:
Thread.CurrentThread.ApartmentState = ApartmentState.STA;
Here's why we needed System.Threading. Setting the currently running thread's apartment state to
STA (single-threaded apartment) may help improve performance. Since we know that the underlying
COM application, the VB6-based Inventory.dll, runs as a single-threaded apartment, we prevent
.NET from first trying to run it any other way.
try {
productNodes = doc.DocXML.GetElementsByTagName("Product");
if( 0 == productNodes.Count )
doc.DocErrors = "Can not check inventory; " +
"no product lines identified.";
166
Design Patterns in the Middle Tier
We make our first of two calls to the legacy Inventory application – checking whether or not the
product price submitted by the customer is correct:
catch( Exception ex ) {
string msg = "Unable to check inventory for DocID = " + DocID;
throw new Exception(msg, ex);
}
}
}
using System.Xml;
if( 0 == productNodes.Count )
doc.DocErrors = "Can not check credit; " + "no product lines identified.";
167
Chapter 3
Since the OrderManagement application to do that does not exist yet, we need to comment out its
references for the moment. The OrderManagement application is responsible for actually placing
orders. When we are finished with ProcessDocument, we will finally build the OrderManagement
application, which is responsible for actually placing orders. Then, we'll come back to this application
and reinstate the commented-out lines:
//using OrderManagement
As long as the other business classes did not generate any errors, the order gets placed via the Place()
method call in the constructor:
m_OrderID = place.NewOrder(doc.DocXML);
168
Design Patterns in the Middle Tier
Unlike the other business rule classes, we included a method other than New(). Adding methods and
properties to a Decorator pattern's derived class is normal. One of the reasons that it is so common is
the very same reason we added a property here – we needed to obtain information from the class. In
our case, we wanted to make the newfound m_OrderID available outside of OrderProduct:
There are many other reasons for adding additional routines to a decorator beyond public access to a
variable. Remember what the Decorator aims to accomplish – attaching additional responsibilities, and if
you add additional responsibilities to a class, you will most likely need to give clients access to them.
The official GoF Decorator pattern does not mandate a "managing class". Nonetheless, I have
learned through experience that using one prevents misuse of a decorator's derived classes.
Since our application worries about orders, we only need one of these specialized classes –
OrderManager. Some readers may question why we bother with an OrderManager class. Couldn't we
just bury the code that builds the appropriate decorators somewhere else? Of course, we could but doing
so limits the ease of updating NOP. For example, suppose Northwind decides to allow customers to place
order inquiries and cancellations. Where would the developer responsible for implementing these features
begin? Maybe check the client code? How about the decorator? If the decorator, where? Will I break any
other applications when I add the new derived classes supporting the additional responsibilities?
Like a good manager, our "processing manager" class keeps everyone informed. It accomplishes this
with the same status message system we discussed earlier. Placing the code responsible for inserting
status messages in the NWindOrderStatus database in OrderManager offers another advantage – it
does not "pollute" the business rules classes with unrelated activity.
Create another new class file, called OrderManager.cs, to the ProcessDocument application. Now
we can start to add code to it. First, here are key using references we'll need:
using System.Data;
using System.Data.SqlClient;
Now here's the OrderManager class. Again, if your SQL Server instance is not hosted on your
LocalHost machine, then you may need to change the server attribute here to reflect that:
169
Chapter 3
Here's the class's Load() method. Its first call to SetStatus() allows the curious customer to find out
that their submitted order is being processed:
Instantiating doc, the decorator's concrete object, starts the ball rolling! Then we use some simple
if...else clauses to decide how to decorate doc. Remember that NOP possesses two different order
types, which require slightly different processing by the same components:
There are many ways to decide what XML document is being sent to NOP. Using the document's
referenced validation schema though does have an advantage. It almost guarantees that inbound XML
documents are routed appropriately. We could improve these odds for XSD documents by searching for
a fully qualified URI, such as http://NorthwindTraders.com/Order.xsd:
switch( order.DocStatus ) {
case DocumentCodes.Status.Errors:
statusField = DateTime.Now.ToString() + " Errors:" +
Environment.NewLine + order.DocErrors;
break;
case DocumentCodes.Status.Complete:
statusField = "Order ID: " + order.GetOrderID();
break;
default:
statusField = DateTime.Now.ToString() +
" Processing Incomplete";
break;
}
170
Design Patterns in the Middle Tier
Our last call to SetStatus() hopefully contains good news – a submitted order's OrderID, for the
curious customer:
statusField = statusField.Replace("'","");
SetStatus(docID, statusField);
}
try {
cn = new SqlConnection(CONNECT_STRING);
cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd.Connection = cn;
cn.Open();
cmd.ExecuteNonQuery();
}
catch( Exception ex ) {
string msg = "Unable to save status(" + Status + ") for DocID = " + docID;
throw new Exception(msg, ex);
}
finally {
if( cn.State != ConnectionState.Closed )
cn.Close();
}
}
}
❑ In the TestHarness project, ensure you've reinstated the four lines in frmMain.cs that refer
to ReceiveDocument.Receive. Also make sure TestHarness has a reference to the
ReceiveDocument project.
❑ In the ReceiveDocument project, reinstate the two lines in Receive.cs that refer to
ReceiveDocument.Pickup.
171
Chapter 3
❑ Also, in the ReceiveDocument project, reinstate the three lines in Pickup.cs that refer to
ProcessDocument.OrderManager. Also make sure ReceiveDocument has a reference to
the ProcessDocument project.
Compile the solution, run TestHarness.exe, and submit a few orders. You probably won't see the
messages in the inbounddocuments queue in the MMC, because they pass through too quickly.
However, you should begin to see the order status entries appear in the DocumentStatus table of the
NWindOrderStatus database:
OrderManagement
This little application provides the last service NOP requires before we can finish up. It will just place
orders into the Northwind Order system. This task only requires placing the order in a specific MSMQ
NewOrder queue. We'll add one more application with two classes: SimpleQueueManager and Place.
After adding a C# class library project entitled OrderManagement to the solution, add a reference to
System.Messaging.dll. Also, remove the default class file Class1.cs. Now we're ready to add our
two new classes.
using System.Messaging;
if( MessageQueue.Exists(msgQueueName) ) {
try {
msgQueue = new MessageQueue(msgQueueName);
}
catch( Exception ex ) {
string msg = "Unable to access the " + QUEUE_NAME + " queue";
throw new Exception(msg, ex);
}
}
else {
try {
msgQueue = MessageQueue.Create(msgQueueName);
}
172
Design Patterns in the Middle Tier
catch( Exception ex ) {
string msg = "Unable to create the " + QUEUE_NAME + " queue";
throw new Exception(msg, ex);
}
}
return msgQueue;
}
}
using System.Messaging;
try {
msgQueue.Send(msgDoc);
orderID = msgDoc.Id;
}
catch( Exception ex ) {
string msg = "Unable to send order to the " + msgQueue.QueueName + " queue";
throw new Exception(msg, ex);
}
finally {
msgQueue.Close();
}
return orderID;
}
}
There is one small point worth observing before we move on to system testing. Until now, the returned
identification numbers existed to monitor a document's passage through the Northwind Order
Processing application. It is only here, at the very end of the drill, that the customer obtains a "real"
order identification number!
173
Chapter 3
System Testing
Speaking of testing, now we can test the entire system we've created. There are a couple of outstanding
tasks to complete:
Now compile the solution and run the TestHarness.exe application one last time. If everything ran as
hoped, your Computer Management MMC should contain something resembling the sample below:
This sample shows that two orders were submitted via the TestHarness application sitting in the
neworders queue. (If we inspect the inbounddocuments queue, it should be empty.)
Of course, you do not have to keep checking your queues to know whether orders arrived safely. After
submitting an order, we can use the DocumentID to obtain its status, which is now being recorded in
the DocumentStatus table of the NWindOrderStatus database:
174
Design Patterns in the Middle Tier
By clicking the TestHarness's Check Order Status button, the txtCheckResult Order Status
control should display the order number:
Reference to
submitted
document
Final
Order ID
Summary
We built a middle-tier application capable of supporting a web service in this chapter. Northwind
customers will soon be submitting their orders via the web. Northwind sales representatives won't waste
any more time keying in orders.
Along the way, we incorporated several basic GoF patterns that contributed significantly to our
application's success. And while it is usually difficult to "pin the feature on the pattern", we can list a few
patterns and their contributions:
These design patterns will also facilitate implementing Northwind's inevitable business rule changes. This
is obvious with the ValidateOrder class, where the Strategy pattern eases any validation algorithm
changes. However, even the entire order processing procedures realizes greater flexibility with a design
pattern! The Decorator that it was built with inherently supports additional order responsibilities.
Finally, our application also demonstrated an implementation of the more complex Store and Forward
pattern, a perennial favorite of Internet-based business-to-business applications concerned with
reliability and performance. It also provided a nice demonstration of transactional MSMQ queues and
the .NET threading class.
175
Chapter 3
176