Using SOAP RPC Messages

SOAP Encoding

SOAP Encoding defines the way data can be serialized within a SOAP message. SOAP Encoding builds on the types defined in the XML specification, which defines a standard way of encoding data within an XML document. SOAP Encoding clarifies how data should be encoded and covers items not explicitly covered in the XML specification, such as arrays and how to properly encode references.

Simple Types

Simple types include strings, integers, date/time, Booleans, and so on. The SOAP specification defers to the “Built-in datatypes” section of the “XML Schema Part 2: Datatypes” specification. I will talk about the XML built-in data types in the next chapter.

An instance of a data type is encoded as an XML element. For example, an integer called Age would be encoded as follows:

<Age>31</Age>

Note that for RPC messages, the name of the element must correlate with the name of the parameter.

Compound Types

Often, it is not sufficient to pass simple types such as integers and strings as parameters; you need to pass compound types such as structures or arrays. In this section, I explain how SOAP Encoding handles compound types.

Structures

A structure is a collection of types that serve as a template for logically grouping data. For example, let's say you need to create a function that calculates the volume of a rectangular solid. Instead of passing the length, the width, and the height of the cube as separate parameters, you can logically group the dimensional data into a RectSolid structure. Then the method that calculates the volume of the solid can accept an instance of the RectSolid structure. Here is an example:

public struct RectSolid {     public int length;     public int width;     public int height; } public int CalcVolume(RectSolid r) {     return (r.length * r.width * r.height); }

First I define a structure that contains the dimensions of a solid. Then I define the area. A request to calculate the volume of a rectangular solid that has a length of 2, a width of 3, and a height of 1 can be encoded as follows:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">   <soap:Body>     <CalcVolume>       <r>         <length>2</length>         <width>3</width>         <height>1</height>       </r>     </CalcVolume>   </soap:Body> </soap:Envelope>

As you can see, structures map nicely to XML. Each of the variables contained within the instance of the RectSolid structure is serialized as a child element of r. As you will see in the next chapter, this follows the method of encoding structures defined in Part 1 of the XML specification.

Arrays

Another common compound data type is the array. As of this writing, the XML specification does not specify how an array should be encoded. The SOAP 1.1 specification fills in the gaps. Here is an example:

public int AddArray(int[] numbers) {     int total = 0;     foreach(int number in numbers)     {         total += number;     }     return total; }

The AddArray method accepts an array of integers and returns the total. Here is how a client can call the AddArray function:

int[] a = {1, 2, 3}; int total; total = AddArray(a);

The call to AddArray produces the following request message:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <AddArray>       <a soap-enc:arrayType="xsi:int[3]">         <int>1</int>         <int>2</int>         <int>3</int>       </a>     </AddArray>   </soap:Body> </soap:Envelope>

The array is represented by a single element within the body tag. The element must contain the soap-enc:arrayType attribute. The value of the attribute describes the contents of the array and its dimensions. In the preceding example, xsi:int[3] specifies that the array contains three integers. In the next chapter, I will describe XML Schema and type definitions in more detail.

Each value in the array is listed as a subelement. The names of the subelements are not relevant, but often the names of the elements within the array will correlate with the type of data they contain.

SOAP-encoded arrays can contain different elements of different types. The following code returns an array containing an integer, a float, and a string:

object[] stuff = new object[3]; stuff[0] = (int)100; stuff[1] = (float)2.456; stuff[2] = (string)"Kitchen Sink"; CollectThings(stuff); public void CollectThings(object[] things) {     // ... }

An array of objects called stuffis created, and then values of three different types are assigned to each of its three elements. The resulting response SOAP message is encoded as follows:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <CollectThings>       <things soap-enc:arrayType="xsi:ur-type[3]">         <object>100</object>         <object>2.456</object>         <object>Kitchen Sink</object>       </things>     </CollectThings>   </soap:Body> </soap:Envelope>

The thingsarray is defined as type xsi:ur-type, which means that the elements can contain data of any type. In the next chapter, you will learn how to declare the type of data in each element.

The final two array types I will cover are multidimensional and jagged arrays. Multidimensional arrays are rectangular by nature. You can think of a jagged array as an array contained within an array. SOAP defines a method for encoding both types of arrays. This example creates a multidimensional array:

// Create a block of seats 3 rows deep and 4 seats wide. string[,] seats = new string[3, 4]; for(int i = 0; i < 2; i++) {     for(int j = 0; i < 2; j++)     {         seats[i, j] = string.Format("row {0}, seat {1}");     } } PrintSeatLabels(seats); public void PrintSeatLabels(string[,] labels) {     // ... }

A multidimensional array of labels is created, and then the array is passed to PrintSeatLabels. The resulting message is encoded as follows:.

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <PrintSeatLabels soap-enc:arrayType="xsi:string[3,4]">       <seats>         <string>row 1, seat 1</string>         <string>row 1, seat 2</string>         <string>row 1, seat 3</string>         <string>row 1, seat 4</string>         <string>row 2, seat 1</string>         <string>row 2, seat 2</string>         <string>row 2, seat 3</string>         <string>row 2, seat 4</string>         <string>row 3, seat 1</string>         <string>row 3, seat 2</string>         <string>row 3, seat 3</string>         <string>row 3, seat 4</string>       </seats>     </PrintSeatLabels>   </soap:Body> </soap:Envelope>

As you can see, the values of the right-side element change more rapidly than those of the left-side element. Because the seat is the rightmost element, all of the seats for a particular row are encoded before the loop moves on to the next row.

In a jagged array, which you can think of as an array of arrays, each element can contain an array of varying lengths. Here is an example:

string[][] teams = new string[3][]; teams[0] = new string[3]; teams[0][0] = "Bob"; teams[0][1] = "Sue"; teams[0][2] = "Mike"; teams[1] = new string[2]; teams[1][0] = "Jane"; teams[1][1] = "Mark"; teams[2] = new String[4]; teams[2][0] = "Mary"; teams[2][1] = "Jill"; teams[2][2] = "Jim"; teams[2][3] = "Tom"; RegisterTeams(teams); public void RegisterTeams(string[][] teams) {     // ... }

The RegisterTeams function accepts a list of teams. Because teams can vary in the number of players, a two-dimensional jagged array of strings is passed to the function. Each element of the array represents a team and contains an array of player names on that team. Here is how the jagged array is encoded:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <RegisterTeams>       <teams soap-enc:arrayType="xsi:string[3]">         <team soap-enc:arrayType="xsi:string[3]">           <player>Bob</player>           <player>Sue</player>           <player>Mike</player>         </team>         <team soap-enc:arrayType="xsi:string[2]">           <player>Jane</player>           <player>Mark</player>         </team>         <team soap-enc:arrayType="xsi:string[4]">           <player>Mary</player>           <player>Jill</player>           <player>Jim</player>           <player>Tom</player>         </team>       </teams>     </RegisterTeams>   </soap:Body> </soap:Envelope>

Consistent with the array encoding rules I discussed earlier, the name of the individual elements is not important. For clarity, I named each element in the teamsarray team and named each element in the teamarray player. In jagged arrays, not only does the teamselement contain a soap-enc:arrayType attribute, but each of the elements within the array of teams contains a soap-enc:arrayType attribute as well.

Optimization

In some cases, you might not want to encode the entire array in the body of the message. The SOAP 1.1 specification describes two ways to encode part of an array: partial arrays and sparse arrays. A partial array encodes a select range of elements in the array. A sparse array encodes select elements scattered throughout the array.

Let's say you create an array that can hold the names of up to 1000 registrants for an upcoming event. Periodically, the list of attendees needs to be sent to various interested parties. Soon after the event has been announced, there might be only 5 people registered. If you send the list of registrants, it is not very efficient to encode all 1000 elements because only the first 5 will contain values:

// Create an array of attendees, record the first five, // and then pass the array to RegisteredAttendees. string[] attendees[1000]; attendees[0] = "Bill Clinton"; attendees[1] = "Jimmy Carter"; attendees[2] = "Ronald Reagan"; attendees[3] = "George Bush"; attendees[4] = "Al Gore"; RegisteredAttendees(attendees); public void RegisteredAttendees(string[] attendees) {     // ... } <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <RegisteredAttendees>       <attendees soap-enc:arrayType="xsi:string[1000]">         <string>Bill Clinton</string>         <string>Jimmy Carter</string>         <string>Ronald Reagan</string>         <string>George Bush</string>         <string>Al Gore</string>       </attendees>     </RegisteredAttendees>   </soap:Body> </soap:Envelope>

As you can see in the resulting message, the soap-enc:arrayType attribute indicates that the array contains 1000 elements even though only the first 5 were encoded. If you want to encode a portion of the array that does not start with the first element, you can specify the starting element by using the soap-enc:offset attribute. For example, if you want to encode the next five attendees that registered for the event, the resulting message would be as follows:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <RegisteredAttendees>       <attendees soap-enc:arrayType="xsi:string[1000]"        soap-enc:offset="[5]">         <string>Gerald Ford</string>         <string>George W. Bush</string>         <string>Dick Cheney</string>         <string>Walter Mondale</string>         <string>Dan Quayle</string>       </attendees>     </RegisteredAttendees>   </soap:Body> </soap:Envelope>

As you can see, the soap-enc:offset element specifies that the array has been offset by five. Therefore, the contents of the array contain the sixth through the tenth elements.

What if the elements to be encoded within the array are not adjacent to each other? Another means of partially encoding arrays is to use the sparse array syntax. For example, say you want to create a message that contains the names of all the registered attendees that did not show up for the event. The ordinal of each attendee has significance, so you are once again creating 1000 elements in an array and populating only a subset of the elements with data.

This time, the data will not be located in a sequential set of elements. Instead, it will be contained in elements throughout the array. You can solve this problem by encoding an array of no-shows by using the sparse array syntax. Here is the resulting message:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance">   <soap:Body>     <NoShows>       <registrants soap-enc:arrayType="xsi:string[1000]">         <string soap-enc:position="[10]">Dan Quayle</string>         <string soap-enc:position="[231]">Newt Gingrich</string>         <string soap-enc:position="[357]">Trent Lott</string>         <string soap-enc:position="[842]">Hillary Rodham Clinton         </string>       </registrants>     </NoShows>   </soap:Body> </soap:Envelope>

Once again, the soap-enc:arrayType attribute is used to specify that the array contains a total of 1000 elements. However, the elements that contain data are the only ones encoded within the SOAP message. Because the position of the element within the array is relevant, the soap-enc:position attribute is used to indicate where the element resides within the array.

Passing Parameters by Reference

Up to this point, I have been explaining how to encode parameters that are passed by value to a Web service. But it is often necessary to pass parameters by reference. For example, a client might pass information about a customer to the server so that the server can update the information on behalf of the client. If the client structure were passed by value, changes made to the client's information would not be visible to the client.

Let's take a look at how parameters that are passed by reference are encoded in a SOAP message. In the first example, I create a series of Fibonacci numbers. A number in a Fibonacci series is determined by adding the two numbers directly preceding it. For example, if n1 = 1 and n2 = 1, then n3 = 1 + 1 = 2 and n4 = 1 + 2 = 3. Here is the method I use to output a series of Fibonacci numbers:

public void FibonacciIncrement(ref int n1, ref int n2) {     int temp = n2;     // Set n1 and n2 to the next two Fibonacci numbers.     n1 += n2;     n2 = temp + n1; } // The following code prints the following output: // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, int x = 1; int y = 1; for(int i = 1, i < 11, i += 2) {     Console.Write("{0}, {1}", x, y);     FibonacciIncrement(x, y); }

FibonacciIncrement accepts the last two numbers and then returns the next two numbers in the series. Here are the request and response messages for the first call to FibonacciIncrement:

<!-- Request Message --> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">   <soap:Body>     <FibonacciIncrement>       <n1>1</n1>       <n2>1</n2>     </FibonacciIncrement>   </soap:Body> </soap:Envelope> <!-- Request Message --> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">   <soap:Body>     <FibonacciIncrementResponse>       <n1>2</n1>       <n2>3</n2>     </FibonacciIncrementResponse>   </soap:Body> </soap:Envelope>

There is nothing surprising about the first message. The two parameters are encoded as usual. What distinguishes a parameter passed by reference from one that is passed by value is that the client needs to be notified of any changes to the value. Therefore, the new values of n1and n2 are encoded in the response message. Notice that I also follow the convention of appending Response to the method element within the body.

Another reason for passing parameters by reference is to maintain the identity of the variable being passed. Consider the following example:

// Server Code: public struct Person {     public double    Height;     public int       Weight;     public int       Age;     public string    Hobby; } public string Introduce(ref Person p1, ref Person p2) {     string result = "";     // Do p1 and p2 reference the same variable?     if(p1.ReferenceEquals(p2))     {         throw new Exception("Can't introduce to self.");     }     // Are p1 and p2 equal in value?     if(p1.Equals(p2))     {         result = "We have a lot in common!";     }     else     {         result = "Nice to meet you.";     }     return result; } // Client Code: Person p = new Person(); p.Height = 5.7; p.Weight = 150; p.Age = 31; p.Hobby = "Skiing"; // Attempt to introduce a person to himself. Introduce(ref p, ref p);

The Introduce method accepts two references to variables of type Person. This is similar to passing two integers by reference to FibonacciIncrement. However, unlike FibonacciIncrement, the Introduce method behaves differently depending on whether the two parameters are equal or identical (point to the same instance of Person).

The way I encode the parameters passed by reference in the Fibonacci example is not sufficient for the Introduce method because it does not maintain the identity of the parameters. SOAP provides the id/href pattern for maintaining the identity of the parameters. Here is how the call to Introduce would be encoded:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/">   <soap:Body>     <Introduce>       <p1 soap-enc:href="#ref1"/>       <p2 soap-enc:href="#ref1"/>     </Introduce>     <Person soap-enc:>       <Height>5.7</Height>       <Weight>150</Weight>       <Age>31</Age>       <Hobby>Skiing</Hobby>     </Person>   </soap:Body> </soap:Envelope>

The encoded parameters do not contain any data. Instead, because both parameters reference the same instance of the Person type, the data is encoded once within the body of the message. The root element of the parameter's data is given a unique ID via the id attribute.

Instead of containing data themselves, the parameters each refer to the element containing the actual data. This is done by setting the parameter element's href attribute equal to the ID of the element containing the data.

As you will see in later chapters, different .NET technologies have varying degrees of support in the way they encode reference parameters. Hopefully, this section has shown why you need to understand the degree to which encoding references are supported by the technology underlying your application. This understanding can help you avoid unexpected behavior within your application.

root Attribute

Sometimes, the root of a serialized object graph is not readily apparent within the resulting SOAP message. Suppose you want to serialize the following object graph that shows the relationships between Kevin Bacon and other actors:

The graph represents two paths from Kevin Bacon to Rebecca De Mornay. Rebecca De Mornay was in the motion picture Risky Business (1983) with Tom Cruise, and Tom Cruise was in A Few Good Men (1992) with Kevin Bacon. Rebecca De Mornay was also in Backdraft (1991) with William Baldwin, and William Baldwin was in Flatliners (1990) with Kevin Bacon. The next step is to serialize this data into the body of a SOAP message.

Once this object graph is serialized, you will no longer be able to distinguish which element is the root element. For such cases, SOAP Encoding defines the root attribute. You can use this attribute to distinguish serialization roots from other elements that are present in a serialization but are not roots of a serialized value graph. The preceding object graph would be serialized as you see here:

<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"  xmlns:hw="urn:hollywood>   <soap:Body>     <RelationsToKevinBaconResult>       <objectGraph soap-enc:href="#actor1"/>     </RelationsToKevinBaconResult>     <Actor soap-enc: soap-enc:root="1">       <Name>Kevin Bacon</Name>       <Relationships soap-enc:arrayType="hw:Actor[2]">         <Actor soap-enc:href="actor2"/>         <Actor soap-enc:href="actor3"/>       </Relationships>     </Actor>     <Actor soap-enc:>       <Name>Tom Cruise</Name>       <Relationships soap-enc:arrayType="hw:Actor[2]">         <Actor soap-enc:href="actor1"/>         <Actor soap-enc:href="actor4"/>       </Relationships>     </Actor>     <Actor soap-enc:>       <Name>William Baldwin</Name>       <Relationships soap-enc:arrayType="hw:Actor[2]">         <Actor soap-enc:href="actor1"/>         <Actor soap-enc:href="actor4"/>       </Relationships>     </Actor>     <Actor soap-enc:>       <Name>Rebecca De Mornay</Name>       <Relationships soap-enc:arrayType="hw:Actor[2]">         <Actor soap-enc:href="actor2"/>         <Actor soap-enc:href="actor3"/>       </Relationships>     </Actor>   </soap:Body> </soap:Envelope>

The root attribute identifies Kevin Bacon as the root in the object graph of actors. I could also have optionally decorated nonroot objects with the root attribute and set it to 0.



Building XML Web Services for the Microsoft  .NET Platform
Building XML Web Services for the Microsoft .NET Platform
ISBN: 0735614067
EAN: 2147483647
Year: 2002
Pages: 94
Authors: Scott Short

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net