In part three of this series, we reviewed the design principles behind database modeling and implemented some common practices to create the physical database structures for both John's and Tom's applications. We also generated our data access tier and created database entrance points for each entity in our database by creating classes for each component.
With the database defined and our data connectivity tier ready, we can now begin implementing the business logic and Web services. In this article, we will implement only John's business tier classes and Web services.
Web services B2B implementation demonstration
Welcome to the fourth 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 will do our best to address the issue. Want to catch up on the series? Check out the first three 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"
In theory, the business tier acts as a middleware component that performs specific functionality, which applies neither to the GUI layer nor to the data access layer. In practice however, you may often find your business tier acting mainly as a facade entry point to your data access tier, especially with less complex applications.
Because John's application is relatively straightforward, our business tier classes simply pass method calls through to our data access classes. This is not to say that the business component should be left out of an application in these scenarios. By removing the business component, you severely cripple the scalability of your application.
We created the shell component for the business logic in part two, and now we'll add the classes required for this component in John's application. Right-click the BizTier component in John's application, and select Add | Add Class. Create the following classes one by one for John's business component:
- AvailabilityServices.vb (shown in Listing A)
- BookServices.vb (shown in Listing B)
- ClientServices.vb (shown in Listing C)
- OrderServices.vb (shown in Listing D)
You'll notice each data access class has a corresponding business tier class and will contain the same methods and method signatures as the data access methods. This creates the facade layer. All data transmissions must pass through the business tier—a GUI layer should never communicate directly with a data access class because the data is still raw and unprocessed by the business logic in the business tier. In this case, there is no data massaging, but, nonetheless, this is a critical fundamental design aspect in software applications.
Web service security
Security is probably the biggest technology-related buzzword these days, especially in light of the recent RPC exploit worms. When dealing with Web services, however, your security responsibilities as a developer are quite different. There are two main aspects of application security: authorization/authentication and data transmission. You basically want to keep out the people who shouldn't be accessing your application and also prevent data from being captured in transit.
ASP.NET provides a few different methods to cover the authentication portion of security, and data transmission security needs can be met by invoking Web services over SSL. Implementing both of these security measures in unison ensures a very secure environment. In our example, we'll bypass the SSL portion due to the complexity of installation and configuration and the cost involved. You can, however, download trial certificates for your server if you're interested in testing out your Web services over SSL.
To secure John's Web services, we're going to implement a custom SOAP header, which is essentially an inner class definition with some public properties that get wrapped into the SOAP envelope during a Web method call. By defining properties of this SOAP header class, we can set properties that will allow the Web service to determine if the consumer is in fact authorized to access the application.
The second security measure we'll implement is the lock down of specific protocols, thus preventing the Web service from being accessed in certain ways. Figure A depicts the protocol policies for John's application.
|Web service protocol policy|
By removing the HttpPost, HttpGet, and Documentation protocols, we prevent the invocation of our Web services via Post and Get, and also remove the ability to generate WSDL documents, which would allow unauthorized users to generate Web references and proxy classes for our Web services.
By using both security measures in conjunction, we accomplish the following:
- We prevent the discovery and the ability to invoke our Web service through any means other than HttpSoap, which essentially means the consumers must have a valid proxy class prior to the Web service lock down. This allows the developer to be the only distributor of Web service access.
- If, somehow, a user managed to discover the Web service, he or she would not be able to generate the WSDL file for the service because the Documentation protocol is removed.
- If, somehow, an unauthorized user managed to acquire a proxy class or WSDL file by some extraneous means, he or she would not pass the authentication checks because the user wouldn't have valid ClientIds or WSTokens, which must be present in our custom SOAP header.
We have a nearly impenetrable security mechanism in place. An unauthorized user must break through three strict tiers of security before he or she would be able to compromise our application. Adding SSL to the equation only further increases the security level.
Creating the Web services
With a clear understanding of how we're going to secure our Web services, we can start putting everything together. Let's first examine the structure of our custom SOAP header (shown in Listing E).
All custom SOAP headers must inherit the SoapHeader class, allowing it to be serialized and transported within a SOAP envelope. We define two properties, ClientId and WSToken, which hold the data we use to perform the authentication. Valid security credentials must of course be given to any consumers we wish to grant access to. For example, Tom's Books is a valid Web service consumer for John, so he would tell Tom what his ClientId and WSToken were. Tom would then pass this information to John's Web services in a custom SOAP header, allowing authentication of it.
We also need a generic means to perform the authentication routine itself. Let's begin by creating a separate folder for our Web services. We need to generate this separate folder so that we can store a new Web.config file, which will dictate the security measures described above. These security measures will apply only to files within our Web services directory.
Right-click the main WSB2BJohn application in the solution explorer and add a new folder named ws. Next, right-click the newly created folder and add a new class named WSUtil.vb. Copy the code from Listing F into this new class.
We use an object-oriented technique called polymorphism to create two authenticate methods. This allows us to authenticate two different types of custom SOAP headers. Because authentication occurs on every Web method call, we save some database hits by caching our CLIENT table into an Application variable at startup. Add the code in Listing G to your Application_Start event in the Global.asax file.
The code in Listing G simply places a dataset into an application variable, which holds a reference to all valid Web service clients. By examining and searching this dataset in our WebUtil class' Authenticate() method, we can determine whether the consumer is authorized. Because the CLIENT table contains only one row, we do not adversely affect server performance and we relieve some stress from the database.
By referencing our original requirements, we determine that we need Web services for the following entities: Order and Book. Keep in mind that John's requirements dictate that only certain functionality should be provided to outside users, so we need only to provide access to the following functions: Create Order, Get Orders, and Search Books.
Right-click the ws folder and add two Web services named OrderWS.asmx (Listing H) and BookWS.asmx (Listing I). Copy the code from Listing H into the OrderWS Web service and the code from Listing I into the BookWS Web service.
Understanding the Web services
You'll notice our custom SOAP header class definition near the very top of both Web services. Each Web service must define its own SOAP header class, hence the need for the polymorphic Authenticate() methods in our WSUtil class.
You'll also notice a public property defined as an instance of the SOAP header class. This property will be set to the values of the SOAP header passed from consumers when they invoke a Web method. To create this linkage, your Web method must have a distinct signature as follows:
<WebMethod(), SoapHeader("BookSecurityCtx", Required:=True)>
The SoapHeader attribute constructor takes a parameter, "BookSecurityCtx" in this case, and must match the name of the property defined within the Web service class itself. When the Web method receives this SOAP header, it automatically sets the class property to the values from the SOAP header, giving you access to the data. The Required parameter tells consumers that this SOAP header must be present or the Web service will fail.
As you work your way through the code, you'll notice that instead of duplicating authentication code in each Web method, we simply pass the security context variable to the Authenticate() method, which takes care of the authentication process for us.
The BookWS Web service has two additional methods that do not map to requirements: GetBooksByIds and GetAvailabilityDs. These are utility methods that act as helpers for the Web service consumers. GetBooksByIds returns a set of books based on an array list of Book ID primary keys. GetAvailabilityDs returns a dataset containing all possible availability options for a book.
With good design foresight, you can usually identify which utility methods will be required by a client. The need for these methods will become more apparent when we link Tom's application into John's Web services.
Web service lockdown
Finally, we must generate our Web service proxy classes and then remove the protocols as discussed earlier. Navigate a Web browser to each of the Web services we've created and save the WSDL description somewhere on your hard drive. The path will probably look something like this:
Next, open a Visual Studio command prompt and create a proxy class from the WSDL description by using the wsdl.exe command line utility. Begin by navigating to the directory where we saved the WSDL description file and entering the following commands for each Web service WSDL file respectively:
wsdl /l:VB /o:BookWSProxy.vb BookWS.wsdl
wsdl /l:VB /o:OrderWSProxy.vb OrderWS.wsdl
With the proxy classes created, we can now distribute them to any consumer we wish to give access to. We can lock down the Web services by adding a new Web.config file to the ws directory that contains our Web services. Right-click the ws folder and add a new Web.config file to the folder. Replace the entire contents of this new file with the code in Listing J.
By traversing down the XML node structure, we arrive at the protocols node for Web services. Then it's simply a matter of indicating which protocols we want to remove, which are HttpGet, HttpPost, and Documentation in our case. After performing this modification, we'll no longer be able to retrieve a WSDL description for the Web services. Consumers, however, are still able to invoke the Web services by using the proxy classes we generated.
Next in the series
In the fifth installment of Kevin Koch's Web services B2B implementation demonstration, he will cover the design and security principle for Tom's Web service layer, identify the methods required from the use cases and requirements, implement the SOAP Header authentication context, and implement the Web methods required, and tie it into the Data tier component.