Tip 4: Baker's Dozen Spotlight: Building the Client Piece to Connect via Web Services or Remoting
The client piece must communicate with business objects through either a .NET Web service or a remoting service. I'll show you how to construct a set of classes to build and utilize these services, where the server address is not known until run time. For remoting, you'll construct a basic but functional solution for the client to invoke and execute code on a server machine.
Now that you've constructed the back-end, you need to add components to the client piece to connect to the server. Let's start with the Web service.
First, in the project where you intend to call the Web service, you must add a reference to Masonry.Interfaces. Remember that this contains the public interface IUserObject: while the client piece doesn't have the code for the user object, it knows what functions exist through the interface. This will become very important over the next several steps.
Next, you must add a Web reference to the Web service you created in Tip 3. You can right-click and select Add Web Reference..., and select the Web service you created earlier. Note that .NET adds the Web service with a default name of localhost: you'll need to change the name to wUserObject. (Remember that all Web services are named after their corresponding business object, plus a "w" prefix.)
Normally you only need to add the Web service reference. However, because you declared earlier that the Web service implements the IUserObject interface, you must implement that interface in the client Web reference. Why? Because .NET "drops" the interface implementation when the Web reference proxy is created; however, you can add it back in by selecting "show all files" in Solution Explorer, and navigating all the way down the Web reference until you see the file Reference.cs. You'll see the declaration of public class wUserObject that derives from the SoapHttpClientProtocol class, but does not implement IUserObject. Simply add a reference to the Masonry.Interfaces namespace, and add the reference to IUserObject at the end of the class declaration.
|Author's Note: Important! If you modify the Web service project, you must update the Web reference. Doing so will overwrite the manual change you made to Reference.cs, so you'll need to add it back in. (Some developers may choose to bypass the .NET step of adding a Web reference, and instead opt to generate the Web service proxy with their own custom tool.)
Finally, you can make the connection to the outside world to validate the user ID and password. Because the user may connect via Web services or remoting, you want to abstract out that behavior to a function that returns a generic connection object that you can use for either purpose. Listing 7
demonstrates the heart of the CG Framework: a class that returns a generic connection object. It contains the mechanics of most connection attempts that occur in the client piece. This is an example of the factory design pattern, which creates an object that subsequent classes will instantiate.
Listing 8 provides a full example that uses this connection class. Listing 7 and Listing 8 represent the most critical pieces of code in the client piece.
- Declare an object (oUserObject) of the interface being used for the particular module (IUserObject).
- Create an instance of the ClientRemoteAccess class (see Listing 8) as oRemoteAccess. This class creates an object reference to either the Web service or the remoting server object, depending on which connection the user chose at startup.
- Set the appropriate connection properties for either the remoting interface and/or the Web service reference.
- Call the function GetAccessObject in ClientRemoteAccess. The function will create a new instance of a connection object, using the URL or TCP address that's associated with the connection that the user selected at login.
- Cast the return object to the interface. You can now use the return object to directly communicate with the back-end business objects.
Again, you can use these four steps for most connections to the server: this represents the mechanics of communicating with the back-end, and demonstrates the value of interfaces.
Tip 5: Building a Data Access Layer
The application will use stored procedures to retrieve, add, and update data. You must develop a function to build a connection associated with the requested database, and build necessary functions to execute stored procedures. This ties together Tip 2, Tip 3, and Tip 4 to show a complete "wire-to-wire, and back" process.
The CG Framework provides a base data access class (CGS.DataAccess.cgsDataAccess) for many common database tasks. These tasks include building connection strings, executing stored procedures with parameters, managing transactions, and supporting multiple databases. As stated in Tip 1, developers can build their data access layer by inheriting from cgsDataAccess.
As a basic introduction, the ValidateUserID function in the Masonry.DataAccess.UserDA class (see Listing 9) provides an example of using methods that the base data access class exposes. The primary base method you'll use in ValidateUserID is SPRetrieveData. Let's take a few minutes and walk through the parameters for this method.
The first parameter (required) is the database key. The base data access class utilizes the database key (sent by the client piece) to build the appropriate connection string. It does this by reading an XML file (Database.xml, see Listing 10) on the server that contains the connection information for each possible database key. As stated earlier, this functionality allows a company to add database connections without the need for a software update.
The second parameter is the name of the stored procedure, and it is also a required parameter. The third and fourth parameters are optional. The third parameter is an ArrayList of the parameters to be passed to the stored procedure. The fourth parameter is the timeout factor. The developer should only pass the collection of parameters if necessary, and they only need to pass a timeout factor if the specified stored procedure is intensive and requires more time than the default command timeout provides.
While not included in the listing example, SPRetrieveData contains an additional parameter for typed datasets, so that result sets can be specifically named. By default, result sets are named "table," "Table1," "Table2," etc. Developers who have constructed typed datasets and wish to use specific names for each table in the corresponding result set will find the default names for the result sets frustrating. Fortunately, the CG Framework deals with this by permitting the developer to pass a reference to the typed dataset as a parameter. SPRetrieveData will utilize the TableMappings function to name each result set based on the corresponding table name in the typed dataset.
|Author's Note: Developers must keep stored procedure result sets in sync with the corresponding tables in the typed dataset!