In this section, we will recreate the example server shown in Chapter 1. This will involve a bit more work than what we saw to build the example using ASP.NET. The SOAP Toolkit does not know how to serialize or deserialize properties contained in an object. Instead, you need to handle this transformation yourself. For passing arrays of complex types, you will need to handle the complete transformation yourself. We will look at this in detail. If you pass simple data back and forth, things are quite a bit easier.
The code to handle the first three functions (HelloWorld, GetRandomNumbers, and GetPerson) is pretty basic, and is shown in Listing 10.1. The infrastructure included with the SOAP Toolkit handles these three with minimal interaction from you.
The Person and Name objects follow the same general layout as they did in the first chapter. Person contains a Name and a Date. Name contains three strings. Both use Property Get/Let to change the values. Things get a lot more complicated with GetAuthorNames. As a result of this, this section will have more text focused on handling the difficult case and little text that handles the simple cases. For reference, to the client, it needs to look like the GetAuthorNames function has the following signature:
Public Function GetAuthorNames() as Name()
This Web Service is being built under the assumption that it is intended to be a Web Service and not just another COM object. Using the SOAP Toolkit, you can choose to simply expose a COM object as a Web Service. This statement is important when looking at the code that makes GetPerson and GetAuthorNames work.
The SOAP Toolkit includes a utility, wsdlgen.exe, that allows you to create the WSDL and WSML files. We covered WSDL in Chapter 3, “SOAP, WSDL, and UDDI Explained.” WSML stands for Web Services Meta Language. It is a proprietary XML vocabulary that the SOAP toolkit uses to map WSDL information to its COM counterpart. We will look at the details of the WSML and WSDL files as we progress. They will need to be edited by hand before we are done.
Before we go much further, make sure you have the Ch10STKExSvr project from the companion Web site for this book. You will find it helpful to have this project on your machine so that you can follow along with this discussion.
wsdlgen.exe, also known as the WSDL Generator, will get everything started. For two of the operations, GetRandomNumbers and HelloWorld, the WSDL Generator will be all we need to expose these operations as Web Services. The tool has both a command line and GUI. We will focus on the GUI. The command-line use comes in handy when adding creation of the WSDL and WSML files to the end of any build scripts you may create. All features available from the GUI are available through the command line.
When using the SOAP Toolkit to create your Web Service, you will first want to do the following things in the Visual Basic 6.0 project:
The last step is very important. The SOAP Toolkit uses the object's IDispatch interface to call the functions exposed by the COM object. It stores the ID of each function in the WSML file. If you do not set Binary Compatibility and choose a file that contains a type library, the IDs of the functions will change every time you rebuild the library. If you want to make things even more stable, you have the option of writing an IDL file and compiling it into a type library. If you have written IDL files in the past, this option may be better because it locks the interface. The downside to this is that IDL files are harder to create than just updating the WSDL and WSML files as needed.
After you have the stubbed out version in place and a stable type library (either as a .tlb or .dll), you will be ready to run the WSDL Generator. To handle the intended deployment, you should go ahead and create a virtual directory named Ch10STKExSvr on your local machine. You can do this by using inetmgr.exe and mapping the virtual directory to the same spot in which the DLL is sitting. Alternatively, you can create the directory in the inetpubwwwroot directory. To mark the directory as a Web application, open up inetmgr and navigate to (local computer) Web SitesDefault Web SiteCh10STKExSvr and right-click the node. Select the Properties option from the pop-up menu. On the Directory tab under Application Settings, click the Create button to mark the directory as a Web application. Then click OK to save the change. Assuming that you have installed the SOAP Toolkit (available at http://msdn.microsoft.com), select Start, Programs, Microsoft SOAP Toolkit, WSDL Generator. This will bring up the dialog shown in Figure 10.1.
From this dialog, press Next. Name the service Ch10STKExSvr. For the local path, select the Ch10STKEx.dll file. When you are done, this next screen should be set up as shown in Figure 10.2.
Press Next and continue to the next screen. This is where you will select which objects and methods you want to expose on your object. By selecting a parent in the tree, all child nodes are automatically selected. Because the Web Service exposes all items, select the top node. The selection should look as shown in Figure 10.3.
Now, you need to tell the WSDL Generator the name of the Web site that will be used to expose the WSDL and WSML files. You can also choose to expose the Web Service using an ISAPI or ASP listener. What's the difference?
The ISAPI listener assumes that you will completely hook into the default behavior of the SOAP Toolkit. All the types you use must be able to serialize and deserialize themselves using the built-in tools or using a fairly simple serializer/deserializer that you build yourself.
If you need to do any special processing or if you need to be able to read and write SOAP messages yourself, you need to use the ASP listener. This listener can be used to handle the default dispatch mechanisms, or you can use it to do your own dispatching. How you do things depends on what you need to do.
You should always create XSD information using the 2001 namespace. This namespace maps to the final XSD specification and is understood by all of the more popular open-source and commercial SOAP toolkits. By the time we are done, we will use both the ISAPI and ASP listeners to illustrate their uses. Figure 10.4 shows the selections for the initial WSDL file.
Next, select the default character set and location to place the generated files. The default setting for the WSDL character set, UTF-8, is just fine. You also need to pick a place for the generated files to be placed. I had the toolkit place them in the directory mapped to the Ch10STKExSvr virtual directory, as shown in Figure 10.5.
Now press Next, and read any warning messages. You are now done. You should have received a warning message indicating that the SOAP Toolkit did not understand how to serialize some data types and that they will be marked with question marks (?) in the WSDL file. Now that we have generated the WSDL file, we will move on and take a look at some edits that you will need to make to it.
If you commonly pass complex types over your Web Service, get used to editing the WSDL and WSML files by hand. After you get things working, you will not want to use the tool to regenerate the files and lose any of your changes. The common edits include the following:
Changing the endpoint
Adding complex types to the types section
Changing return types
Adding portTypes, bindings, and service endpoints
Mapping complex types to custom type mappers
The easiest of these items is changing the endpoint. When you initially deploy a Web Service for development on the local machine, it may make sense to set the service endpoint to point at localhost. Doing so allows for easy redeployment to other machines for single machine testing where the client and server run locally. Later, you will want to have the WSDL declare the path to the machine from either the intranet or Internet. Often, clients will keep a copy of the WSDL file locally to avoid an expensive round trip just to load the WSDL file. It will also be helpful for new clients to have a copy of the WSDL with the Web Service's actual endpoint for future reference. Regardless of the use, the edit is fairly easy to do. Open up the WSDL file and scroll to the end. There, you will find the service section. It will look something like the following:
<service name='Ch10STKExSvr' > <port name='TheServiceSoapPort' binding='wsdlns:TheServiceSoapBinding' > <soap:address location='http://localhost/Ch10STKExSvr/Ch10STKExSvr.WSDL' /> </port> </service>
Just go into the soap:address element and change the value of the location attribute to point at the correct endpoint. You can also alter this value to point to a trace utility such as the Microsoft SOAP Toolkit's Trace Utility. (This application was discussed in Chapter 5, “Troubleshooting Web Services/Consumers.”)
We have complex types that declare the Person, Name, and an array of Name objects. These need to be added to the WSDL file. Listing 10.2 shows the types as they are declared by the wizard.
This part does not even contain the question mark defined types. That part, shown in Listing 10.3, appears in the two message definitions that make use of the “unknown” types:
To handle these items, we need to include type definitions. These definitions are not explicitly used by the SOAP Toolkit. Instead, we include them so that clients using the Web Service know how the data will be presented in the returned XML. This information allows the people creating clients to successfully interpret the returned data. Listings 10.4 and 10.5 contain the type definitions and message updates needed to make it easier for clients. This information is added to the types section of the Ch10STKExSvr.WSDL file.
You will notice that the types in the message now reflect the data needed for any clients. The SOAP Toolkit will also use this information to find custom type mappers in the WSML file. You will notice a radical change to the messages used for GetAuthorNames and GetAuthorNamesResponse. The reason is that the user needs to look at the message as though it was produced with no parts in the request and an array of names in the response. The actual signature of the GetAuthorNames function is as follows:
Public Sub GetAuthorNames(ByVal Request As ASPTypeLibrary.Request, _ ByVal Response As ASPTypeLibrary.Response)
The two arguments, Request and Response, allow the GetAuthorNames function to directly read the HTTP message and write the HTTP response. Before looking at the hardest example of type mapping, we will look at the easy case. By the way, the two simple functions, GetRandomNumbers and HelloWorld, already work for the Web Service. Now, we are working on hooking up the other two.
We will first focus on adding the custom type mapper for the Person type. A custom type mapper implements the ISoapTypeMapper interface. This allows the type to read and write itself as XML. ISoapTypeMapper defines four functions—Init, varType, read, and write. Init gets called whenever the class gets loaded. In this function, you should create any objects you might need or capture any information tied to the object schema. The varType function has you return the type of variable the serializer handles. Typically, this will be vbObject. The read and write functions simply read and write the object as XML. We will spend our time looking at the write function (in the client side we will investigate the read portion).
The project contains a class called PersonMapper. PersonMapper only knows how to write a Person and the Name contained within. Listing 10.6 contains the code from the PersonMapper class that writes out a Person object.
To tell the SOAP Toolkit what to do when it needs to convert a Person from XML, a few entries need to be made in the WSML file. First, add the types section. Then, map the type declared there to the PROGID used to instantiate the type mapper. The SOAP Toolkit will handle everything else. Listing 10.7 shows the lines used to map the PersonMapper object to the Person XSD type.
<using PROGID='Ch10STKEx.PersonMapper' cachable='0' ID='PersonMapperObject' /> <types> <type name='Person' targetNamespace='http://tempuri.org/type' uses='PersonMapperObject'/> </types> |
Now we have three of the four functions callable through our Web Service. We have one more left to go—GetAuthorNames.
The test case for this deployment is this: ASP.NET must be able to create proxy using the WSDL and it must be able to consume the Web Service without requiring the client application developer to write any code. To serialize an array of Name objects, the Web Service had to handle serialization of the objects on its own. For this reason, ASP is used to handle SOAP requests for the Web Service.
To handle any requests, the ASP code will read in the message, check to make sure that the caller wants the GetAuthorNames Web Method, and then hand control over to GetAuthorNames. Otherwise, the request will be handled by the SOAP Toolkit.
When the request comes through, the SOAP Toolkit does not have the intelligence to serialize an array of objects. It will not do this even if it knows how to serialize individual items of the same type. Instead, the server has to do this itself. The ASP code, shown in Listing 10.8, handles this by passing the Request and Response objects to the Visual Basic GetAuthorNames function.
The ASP code should handle any errors and send SOAP Fault back if needed. To see how this is done, look at the Ch10STKExSvr.asp file in the Ch10STKExSvr project or generate an ASP listener using the WSDL Generator. My code is no different.
When this has been done, all you need to do is serialize the list of names to XML. When serializing arrays using SOAP-encoding, ASP.NET does best when arrays get serialized as separate top-level elements within the SOAP body. To do this, the code writes out the complete SOAP message. The SOAP Toolkit refers to this as the low-level SOAP interface. This means the code must write out the Envelope as well as any required XML namespaces, SOAP headers, or body elements. Listing 10.9 shows a complete listing of the GetAuthorNames function. The first part of the function initializes the array, followed by code that writes out the SOAP response.
Listing 10.10 shows the SOAP message this code creates. Looking at this message, you should be able to see what all the previous methods do. SoapNamespace adds the namespace and identifier to the currently active element. SoapAttribute adds the attribute to the currently active element. Besides being able to correctly create and close a header, body, and envelope, the toolkit also can create arbitrary elements.
So, did all this work pay off? You bet. Ch10STKExTest contains the Visual Basic .NET console application that consumes the Web Service. The developer did not have to write any custom code to transform the XML into an array of Name. Listing 10.11 shows all the code I had to write to use every method the Web Service exposes.
Now that the Web Service has been written, let's take a look at how to deploy it.
When it comes time to deploy the Web Service, you need to do a few simple things. First, create a virtual directory on the Web Server and map that directory to the location of the WSDL, WSML, and (if you are using ASP) the ASP files. You then need to install the SOAP Toolkit on the server. Finally, install the COM object and register it using regsvr32.exe. The DLL containing the COM object needs to live in a directory that any clients will appear as. If you use some form of authentication, you will need to make sure that those users have access to the directory. Likewise, if the default IIS user will be accessing the file, he or she needs access to the directory in which the COM object resides. Most issues with accessing the Web Service through the SOAP Toolkit involve improperly set permissions.
Other than the previously mentioned items, you do not have to do anything special to deploy a COM object as a Web Service. Now that the server side is covered, let's look at the client side.