Web services B2B implementation demonstration: Components, exception handling, and logging

Kevin Koch implements the requirements established in his series' first article around an n-tier framework by creating the application components and developing exception handling and logging capabilities.

In the first article of this series, "Web services B2B implementation demonstration: Setting the scenario," we set out the road map for designing and implementing two ASP.NET applications that will communicate business transactions with each other using Web services. Now that we have our use cases defined for both John's and Tom's applications, we can begin molding these requirements around our n-tier framework foundation.

Web services B2B implementation demonstration
Welcome to the second 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 members to comment on the progress of the application. Do you have a better implementation? Do you have a question about our B2B solution? Let us hear from you, and we'll do our best to address the issue. Looking to find the next article in the series?
In the third article of this series, Kevin Koch implements the necessary schema and scripts for SQL Server 2000, and the appropriate data tier classes for database connectivity and stored procedure invocations. Click here to read part three.

Getting started
Because we're considered the developer for both John's and Tom's application, we need to set up and implement both applications on a single development environment. If you happen to have access to more than one computer, you may choose to implement each application on a different machine to get a more realistic sense of collaboration.

Begin by starting Internet Information Services (IIS) and creating two new virtual directories: one with the alias WSB2BTom and the other with the alias WSB2BJohn. Configure these aliases to point to a directory of your liking and leave the remaining steps of the New Virtual Directory Wizard with default settings.

After creating both virtual directories, right-click the WSB2BJohn node in IIS and select Properties. Click the Documents tab and remove all the documents in the list by selecting them and clicking the Remove button. Click the Add button and enter HomePage.aspx. Figure A illustrates how this dialog should look when complete. This will set John's application to load a page called HomePage.aspx when it is executed.

Figure A
John's IIS properties

Repeat the process for Tom's application, but add a default document named SearchBooks.aspx. Figure B shows the end result for Tom's application.

Figure B
Tom's IIS properties

With IIS configured, you can now create the actual ASP.NET projects with Visual Studio .NET. Start VS.NET and create a new ASP.NET application at http://localhost/WSB2BJohn. Save this project, open another instance of VS.NET, and create a new ASP.NET application at http://localhost/WSB2BTom.

We know from our requirements that Tom needs both a public and secured area. To accomplish this and keep things organized, add two folders to Tom's application. Right-click the WSB2BTom project node from the Solution Explorer and select Add | New Folder. Enter public as the folder name. Repeat the process and add another folder named private.

All files in the private folder should be secured from unauthorized users. You need to add a customized Web.config file in this folder to accomplish this requirement. Right-click the private folder and select Add | Add New Item. In the New Item dialog, select Web Configuration File. Open the newly created Web.config file and replace its entire contents with the code from Listing A.

The XML in this Web.config file tells IIS to deny any unauthorized users from accessing the files in the private folder. We have another requirement for Tom's application which dictates that we must provide a login form for customers. We'll use the built-in forms authentication in ASP.NET to accomplish this with ease. In the Web.config file in the main application root, replace the <authentication> node with the XML in Listing B.

The XML in Listing B configures the application to use forms authentication and to redirect unauthorized users to a page called LoginForm.aspx upon requesting a secured page. We'll create this page when we implement the login customer use case at a later date.

Tom has also requested that we display user-friendly error messages to his customers. Again, we'll leverage a built-in ASP.NET feature to accomplish this. In the root Web.config file, replace the <customErrors> tag with this XML:
<customErrorsmode="RemoteOnly" defaultRedirect="Error.aspx" />

By setting the mode to RemoteOnly, we enable the default error displays for any users accessing the application locally; all other users will be redirected to a page called Error.aspx. Next, we need to add the error page itself. Right-click the main WSB2BTom node and select Add | Add Web Form. To create the error page, type Error.aspx in the Create Web Form dialog that pops up. Next, add the HTML in Listing C to the HTML view of the newly created error page.

Notice that we have a reference to a custom NavBar component that will not render correctly. Ignore this tag for the time being; we'll implement it later. The page simply displays a label indicating to the user that an error has occurred. John's application requires no additional modification beyond the defaults because he'll be the only user and will access the application from his local network.

Creating the components
With both application foundations in place, we can begin expanding each one to match our n-tier framework model outlined in the first article. We know from our n-tier model that we must build separate database and business components. Because both applications require this n-tier separation, perform the steps below on both John's and Tom's applications.
  1. Select the topmost Solution node in the Solution Explorer and right-click, selecting Add | New Project.
  2. In the dialog that appears, select Class Library as the application type.
  3. In the Location text box, browse to the main application directory of the application you're adding the component to.
  4. Append a Components folder name to the end of the application path. This will create a subfolder below our main application root folder and will store all of our components.
  5. In the Name text box, type DbTier as the application name and click OK.
  6. Repeat steps 1 through 5 and enter BizTier as the project name in step 5.
  7. Repeat steps 1 through 5 and enter WSB2BUtil as the project name in step 5.
  8. Repeat all steps above for the other application to create the same components in both applications.

By creating this separation between the components, we have the flexibility to deploy each component on different physical machines should the need arise in high transaction volume situations. WSB2BUtil is the only component that does not represent a physical tier in our model. This component sits within our ASPX layer and simply provides access to common functionality. Consider this component as a utility library that can be reused in other applications in the future.

For now, we'll leave each component as an empty shell that we'll expand on as we implement the requirements for each application. The important thing to note at this point in our development is that the foundation structure we've created thus far can be applied to any ASP.NET n-tier application.

Finally, we must create the required references among the different components. Add the following references to each component in both applications:
  1. DbTier—Requires reference to WSB2BUtil project
  2. BizTier—Requires references to DbTier and WSB2BUtil projects
  3. WSB2BTom and WSB2BJohn ASP.NET projects—Require references to WSB2BUtil, BizTier, and DbTier projects

Exception handling
One of the most important aspects of proper system design is exception handling. Exception flow and handling is often a topic of confusion among developers. The key to understanding exception flow is application layering. Application layering is an object-oriented (OO) design principle known as encapsulation. By implementing correct encapsulation between classes and components, your exception handling becomes almost automatic.

In our scenario, our layering is fairly straightforward. Each layer is represented by a tier in our framework. This means we have a database layer, a business layer, an ASPX layer, and a Web service layer. By implementing a custom exception class for each layer, we can easily identify and trace our exceptions at any point in our application. Figure C diagrams the exception flow in both of our applications.

Figure C
Exception flow

Our layers are best described by our project references. Certain components may interact only with other specific components. This ensures our program logic follows a predictable hierarchical top-to-bottom flow pattern. The result of this design pattern is that we can be assured, for example, that a System.Exception generated from our ASPX layer will never be raised in our database component or business component. By enforcing encapsulation rules, we dictate exactly what classes and methods will be invoked and from where, allowing us to create a logical exception flow pattern.

Let's create the classes for our custom exceptions. Create the following exception classes for both applications. Right-click the BizTier component and select Add | Add Class. Type BizTierException.vb as the class name, and paste the code from Listing D into this new class.

Perform the same steps as the BizTier component on the DbTier component, and name the new classDbTierException.vb. Add the code from Listing E to this new class.

Both classes have the same implementation. By using an OO technique called inheritance, we extend the System.ApplicationException class and provide two constructors, which simply pass the parameters through to the base class by calling the parent constructors. This allows us to create exceptions with just a message string, or exceptions coupled with an inner exception.

Each component is also responsible for logging exceptions that originate within themselves. For example, a business component will never log a database tier exception. The table in Figure D illustrates our exception and logging pattern.
Figure D
Exception Origin Actions Taken
DbTier Component Exception is logged and DbTierException is thrown to the invoking source
BizTier Component Exception is logged and BizTierException is thrown to invoking source. DbTierExceptions are caught, not logged, and thrown back up to invoking source.
ASPX layer No direct exception handling required except in specific scenarios. Global.asax handles and logs all exceptions thrown by the ASPX layer, as well as all exceptions thrown back from the business and database components.
Web service layer Exception is logged and WSException is raised to consumer. Both BizTierExceptions and DbTierExceptions are caught, but not logged, and thrown back to the consumer.
WSB2BUtil Component No direct exception handling required. This ensures that exceptions appear to originate from invoking source and are logged accordingly.
Exception and logging pattern

For the purpose of our application, our logging mechanism will only log exceptions. Logging is a fairly generic operation and is therefore implemented in our WSB2BUtil component, allowing us to reuse this functionality for other projects. Right-click the WSB2BUtil component and select Add | Add Class. Type Log.vb as the class name, and paste the code in Listing F into the new log class.

We provide two basic methods that accept two different types of exception classes. This allows us to log regular system exceptions and our custom component exceptions. Notice that the filename of our log file is referenced through the AppSettings property of the ConfigurationSettings class. We need to add a key to our Web.config file to enable this functionality. For Tom's application, add the following XML element to the <appSettings> node in the root Web.config file:
<addkey="LogFileName" value="path_to_toms_app\WSB2BTom\logs\Log.log" />

And for John's application:
<addkey="LogFileName" value="path_to_johns_app\WSB2BJohn\logs\Log.log" />

Finally, we need to modify the Global.asax in both applications to log regular exceptions thrown by our ASPX layer. Add the code in Listing G to the Application_Error event.

This event is fired automatically whenever an exception is thrown within an ASP.NET application. Because we know our components handle logging their own errors, we only need to log exceptions not of this type.

Implementing custom logging may seem like overkill since there are many built-in features to facilitate this functionality. However, with some modifications, it gives greater flexibility for more complex applications, such as customized output for XML, e-mail notifications, or severity threshold levels, and it allows you to switch the name of the log file at run time.

The class shown in Listing H is responsible for any miscellaneous functions that don't fit into any OO definition—typically data type conversions, manipulations, validation, etc.

In the next installment, we'll implement the necessary database connections and invocations for our applications.

Next in the series
In the third installment of Kevin Koch's Web services B2B implementation demonstration, he'll implement the necessary schema and scripts for SQL Server 2000, and the appropriate data tier classes for database connectivity and stored procedure invocations.


Editor's Picks