In part four of this series, we implemented John's business tier and Web service layer and created our custom security pattern for locking down the Web services. For this fifth installation of the series, we're going to implement the same functionality for Tom's application. We'll implement Tom's business component classes and Web service layer and create an asynchronous notification class for e-mailing Tom's customers.
Web services B2B implementation demonstration
Welcome to the fifth article in a 10-part series exploring in detail the implementation of a B2B Web service. The series takes you through the development of a complete B2B Web services application. During the course of the series, we encourage Builder.com members to comment on the progress of the application. Do you have a better implementation? Do you have a question about our B2B solution? Start an article discussion and let us hear from you; we'll do our best to address the issue. Want to catch up on the series? Check out the previous articles:
- "Web services B2B implementation demonstration: Setting the scenario"
- "Web services B2B implementation demonstration: Components, exception handling, and logging"
- "Web services B2B implementation demonstration: Database design"
- "Web services B2B implementation demonstration: John's business logic"
As with John's application, Tom's business component will act purely as a facade layer, passing calls through to the database layer. By examining our existing database layer and classes, we can easily create our business component classes. Add the following classes to Tom's BizTier component by right-clicking and selecting Add->Add Class…:
We append the naming convention, "Services," to the entity names to indicate that the classes reside in a business component as a service class. All calls made from the ASPX layer must pass through our business components. This is a critical implementation key in layered service programming and ensures physical separation of components, thus allowing distributed objects should our application ever need to be load balanced.
You'll notice that each method in Tom's database layer has a corresponding method in the business layer. Should a requirement ever arise that specific data be filtered, sorted, organized, or joined, the business layer is where this specific coding would occur. All database methods should simply pass data in and out in its natural raw format.
Web service security
We thoroughly covered Web service security in part four of the series, so, in this article, we'll cover the main principles briefly.
Tom's Web services will implement the same custom SOAP header for authentication purposes. However, the process of authentication is slightly different. The Web service expects a SOAP header containing one single security token key. This security key is stored in Tom's Web.config file through the following XML node, which you'll need to add:
<add key="JohnWsToken" value="abcdefghijklmnop" />
The value of this key is stored both in Tom's application and John's application. Without uniformity of this value across both applications, the Web service will return an authentication error. Tom's security needs aren't quite as stringent as John's, simply because he needs only to expose a mechanism for John's application to confirm orders.
In addition to the security token values matching, Tom's Web service will also be locked down by removing the HttpGet, HttpPost, and Documentation protocols. Access to Tom's Web service will be provided by distributing a proxy class to John's application. See Figure A for a diagram of the entire sequence.
|Web service sequence diagram|
Creating the Web services
To begin piecing together Tom's Web service layer, we create a ws folder beneath the main ASP.NET project, which will store the Web services themselves. Right-click the WSB2BTom application, select Add | New Folder, and name the folder ws. By placing our Web services in a separate folder, we gain the ability to control certain aspects of the files in that folder through a customized Web.config file.
Right-click the ws folder and select Add | Add New Item. Choose Web Configuration File as the type of file to add. By default, it will name this file Web.config for you. Replace the contents of this file with the XML from Listing C.
By removing these protocols, we essentially make our Web services invisible to the public. Next, we need to add to the Web service itself. For Tom, only one Web service is required. Its purpose is to provide a way for John's application to notify Tom's application of completed orders. Right-click the ws folder and select Add | Add Web Service. Type OrderWS.asmx as the name of the file, and paste the contents from Listing D into this new Web service.
Authentication is provided through a separate support class that handles examining the custom SOAP header and determines whether the consumer is valid or not. Right-click the ws folder and add a new class named WSUtil.vb. Place the code from Listing E into this new class. The Authenticate() method compares the security token in the SOAP header to the value stored in the main Web site's Web.config file; if they match, the consumer is considered authenticated.
Two additional methods are provided to easily create instances of the custom SOAP headers required for John's application. For these methods to work, we need to add the proxy classes generated from John's application created in part four of this series. Listing F and Listing G contain the code for the BookProxy.vb class and the OrderProxy.vb class respectively. Save these files to your hard drive and then right-click the WSB2BTom project, select Add | Add Existing Item, and add the two proxy classes to the project.
Finally, we need to create a proxy class for this new Web service. Comment out the removal of the Documentation protocol in the Web.config file, compile the project, and browse to the Web service WSDL file through a Web browser. The path should look something like:
Save the resulting WSDL file to your hard drive, open a Visual Studio .NET command prompt, and navigate to where you saved the file. Enter the following command to generate the proxy class:
wsdl /l:VB /o:OrderWSProxy.vb OrderWS.wsdl
With our basic Web service layer structure in place, we can now add our exception handling class. Right-click the ws folder and add a new class named WSException.vb (shown in Listing H). You'll also need to add this class to John's application.
Let's examine the exception handling block in our single Web method, ConfirmOrder(). Listing I details the catch block of this method.
You'll notice our catch block is layered to handle all the different types of exceptions in our application. Should the exception originate in our business or database component, that component handles the actual logging of the error and passes it up to the previous layer. Once it reaches our Web service layer, we again pass these exceptions through to the consumer.
If the exception originates somewhere within the Web method itself, it will be caught as a regular system exception and logged to our log file. We then instantiate a new WSException class and pass this back up to the consumer.
Granted, all exceptions thrown by a Web service are thrown as SoapExceptions, but there are certain things you can do on the consumer end to handle specific exception types. This allows you to pinpoint accurately exactly which tier originated the exception.
If you look closely at our ConfirmOrder() Web method, you'll notice the use of a few threading classes found in the System.Threading namespace, namely the ThreadPool and AutoResetEvent classes.
First, we need to create our notification mechanism. This class belongs in our utility component. Right-click the WSB2BUtil project, and add a new class named EmailUtil.vb (shown in Listing J).
This class uses some basic SMTP functionality to send e-mail messages. To keep the functionality dynamic, you'll need to add the following two new keys to your Web.config file:
<add key="SMTPServer" value="your.company.smtp" />
<add key="AdminEmail" value="firstname.lastname@example.org" />
These values are read by the e-mail class and used as settings for the MailMessage() class. This class is part of the System.Web.Mail namespace that comes with the .NET framework, providing convenient access to SMTP functions without having to write your own.
The trouble with this type of asynchronous behavior is that there is no way to get parameters to the asynchronous methods. These methods must have a specific signature. They must be "void" or "subs" with no return values, and they must have one parameter defined as type "object." This object parameter is an instance of the AutoResetEvent class, which allows us to generate and release a new thread created within our application.
Let's examine the invocation of our e-mail component in >Listing K.
First, we instantiate an instance of the EmailUtil class and use the constructor to pass all the data which will be part of the e-mail sent out by the classes methods. This data gets set to internal class properties and is later read by our notification methods. The data originates from a dataset in a with block, which is why there are .item() code pieces.
Next, we instantiate an AutoResetEvent class, which is used by the threading mechanism to differentiate between signaled and non-signaled pending threads. Finally, we make a call to the static method QueueUserWorkItem of the ThreadPool class, which places a new method invocation into the thread pool for processing. We pass in the address of the method we wish to invoke, in our case SendCompletedOrderConfirmation and the instance of the AutoResetEvent class.
In the actual notification method, we set up a new SMTP message and send it out as you would in any regular method. However, we need to add a finally block and cast the Object parameter back to an AutoResetEvent class and call the Set() method, which lets the thread pool know that this process is complete. All of this happens asynchronously without pausing execution of the main application thread.
Next in the series
In the sixth installment of our Web services B2B implementation demonstration, Kevin Koch will implement all of the GUI screens required for John’s use cases, set up the required GUI logic such as validation controls, tie code behind files to business tier components, and demonstrate Data binding with Dataset objects.