Elements Defined in the SOAP Standard


We’ll now talk about each of the main elements generically and point out any differences between SOAP 1.1 (which you’ll be using if you’re using the .NET Framework version 1.0 or 1.1) and SOAP 1.2 where they crop up. We’ll start with the topmost element of a SOAP message, the SOAP envelope.

The SOAP Envelope

The SOAP envelope must always be the top-level element in a SOAP message. It must also contain a fully qualified namespace. A typical SOAP envelope element, prefixed by the soap namespace (the envelope namespace in the 1.1 standard), looks like this:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope"> .... SOAP Header and Body here ... </soap:Envelope>

In either version of the SOAP standard, the SOAP header (if there is one) must come before the SOAP body. The SOAP header is optional and contains extra information about the message being sent. The SOAP body contains the XML payload—the information that is intended for the Web service or that is sent back by the service. We’ll look at it first.

The SOAP Body

We’ve pointed out that SOAP is used to convey method calls to a Web service and method responses from the Web service. Indeed, the SOAP body is typically used to transmit the name of the method to be called, along with associated parameters and a response from the receiving application. The body can contain the information requested by the calling application if the response was successful or a fault message if a problem occurred.

The SOAP body request is the request document that is sent to the server. Here’s a quick example Web method in C#, which, given a percentage value and a number, calculates what value is a percentage of the number and returns that to the client. The Web method to provide the functionality might look like this:

[WebMethod(Description="Calculates the percentage value "     + "given a percentage and a number")] public double CalcPercent(int Percent, double Number) {     double Value = Number / 100 * Percent;     return Value; }

SOAP Body Request

The SOAP message generated by a client of the Web service is as follows:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body>         <CalcPercent xmlns="http://www.notashop.com/wscr">             <Percent>25</Percent>              <Number>4</Number>          </CalcPercent>     </soap:Body> </soap:Envelope>

You can see in the SOAP body that the <Envelope> element is prefixed by the soap: namespace. This is the default used by .NET. The <Envelope> element contains the <Body> element, and inside the body is our CalculatePercent document. The document contains three elements, two of which represent the parameters supplied in the Web method, <Percent> and <Number>. These are both enclosed by the <CalcPercent> element, which represents our method. The document also has two autogenerated namespaces that aren’t actually used: xsi and xsd. For the time being, just ignore them; they’ll be used in later examples.

Note that both Percent and Number are actually strings in the SOAP. This string data is translated from the XML document into method parameters, which map to particular data types. Nothing in the SOAP provided here can stop textual data being passed, but the code would of course break when it got to the Web method. This translation is handled transparently by the code.

SOAP Body Response

A successful response document to our previous request looks like the following. (We won’t consider faults until later in this chapter.)

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body>         <CalcPercentResponse xmlns="http://www.notashop.com/wscr">             <CalcPercentResult>1</CalcPercentResult>          </CalcPercentResponse>     </soap:Body> </soap:Envelope>

Just as our Web method returns only one value as a response, the SOAP body now has only one element to contain the response. This element, <CalcPercentResult>, contains the corresponding response to our request (What is 25 percent of 4?).

Capturing SOAP Messages

The SOAP messages are transmitted between the client and the Web service, so if you want to capture and view them, you can choose between a couple of approaches. One is to use the SOAP extensions to create a log and write the SOAP messages to a log. (We’ll cover SOAP extensions in Chapter 10.) The second approach is to use the SOAP trace utility provided in the Microsoft SOAP Toolkit (whose latest version is 3.0). This toolkit allows developers to add XML Web service functionality to their existing COM applications, but it also contains the useful utility mssoapt3.exe, which allows you to listen to a port for incoming and outgoing messages and displays whatever is sent to the port. To get the utility to work, you need to set it up to listen on a port and get it to forward all requests it receives on that port to a specified server address.

Note

Trace utilities aren’t unique to SOAP; you can also use them to listen to HTTP requests and responses. Indeed, if you don’t want to use the SOAP Toolkit, you can find equally effective monitors such as TcpTrace (found at http://www.pocketsoap.com/tcptrace/).

We suggest pointing the utility at port 8080 of the server and then sending traffic to destination port 80. Responses returned from the server are then forwarded back to the client that made the original request. All requests and responses that go through the monitor are displayed on the trace utility screen. This works because to the client the monitor looks exactly like the server and to the server the monitor looks exactly like the client. The only thing is that you have to tell the client to talk to the monitor. This means you can no longer call your own server (for example, http://localhost) directly; instead, you must append the suffix :8080 to wherever the Web service is called.

Once you’ve done this, you need to ensure that your application sends the messages via SOAP and not via HTTP GET, which is what it does by default if you use a browser to view the Web service. You must set up a Windows application using Visual Studio .NET to act as a client to call the Web method. The Windows application then has to interact with a client proxy class (that you’ve generated) to exchange details with the Web service.

You can autogenerate a proxy class from the WSDL created by the Web service by using wsdl.exe, a tool provided with the .NET Framework SDK. Here’s the syntax you need. (A full discussion of how this tool works can be found in Chapter 1.)

wsdl /l:language /o:nameOfProxyClass http://urlOfWSDL /n:serviceName 

For this example, you can use the following to generate a proxy class for our previous Web method in C#:

wsdl /l:cs /o:PercentProxy.cs Π    http://localhost/Percent1/service1.asmx?WSDL /n:PercentService

or in Visual Basic .NET:

wsdl /l:vb /o:PercentProxy.vb Π    http://localhost/Percent1/service1.asmx?WSDL /n:PercentService

Next you need to open the proxy class and alter the reference this.url (or me.url in Visual Basic .NET) to point from http://localhost to http://localhost:8080:

this.Url = "http://localhost:8080/wscr/02/Percent1/service1.asmx";

You then need to compile the class created by wsdl.exe into a DLL using the C# compiler and include a reference to that DLL in your Windows application. You can then make calls to the Web service and the Web method via the names you supplied to wsdl.exe.

Last, you need to create the Windows application to interact with the Web service via the proxy.

Simple Types in a SOAP Body

Now that you have an idea of how to use a monitor to view the SOAP messages passed by the Web method, let’s look at how to pass simple types, such as an integer and a double, to a Web service and in what format the answer is returned.

Start by creating a Windows application with a form containing a single button and two text box controls for the input and a label control for the answer, as shown in Figure 2-2. Call the application CalcPercentClient.


Figure 2-2: Form for sample Windows application that uses the CalcPercent Web service

Add references to System.Web.Services and to the WebPercentProxy.dll assembly you created in the previous section. Then add the following event handler code to return information from our CalcPercent Web service:

private void button1_Click(object sender, System.EventArgs e) {     PercentService.Service1 wsp = new PercentService.Service1();     double Total = wsp.CalcPercent(Convert.ToInt32(textBox1.Text),            Convert.ToDouble(textBox2.Text));     label1.Text = Total.ToString(); }

If you have the SOAP trace utility turned on, you can view the same SOAP messages between your application and Web service that we looked at earlier.

How .NET Handles Structs in the SOAP Body

The first example passed only simple types in our Web method, but if we alter our Web method to use a structure (the C# type struct), we can see how more complex types are treated in the SOAP body. The main difference here is that .NET looks after the serialization of the data types. This is not specified in the SOAP standard.

Here’s a struct containing three data types:

public struct DataTypes {     public Boolean Smoking;     public String Name;     public Int32 Age;  }

We can assign values to each of these fields within the body of our Web method:

[WebMethod(Description="sends structs")] public DataTypes ReturnValues() {     DataTypes MyStruct = new DataTypes();     MyStruct.Smoking = true;     MyStruct.Name = "Shane DeSeranno";     MyStruct.Age = 56;     return MyStruct; }

If we create a small Windows application to fetch this data much as we did in the previous application, the SOAP response we can receive in the SOAP trace utility looks like this:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body>         <ReturnValuesResponse xmlns="http://www.notashop.com/wscr">             <ReturnValuesResult>                 <Smoking>true</Smoking>                  <Name>Shane DeSeranno</Name>                  <Age>56</Age>              </ReturnValuesResult>         </ReturnValuesResponse>     </soap:Body> </soap:Envelope>

The difference between this structure and the simple types we looked at previously is that an extra set of elements has been added within the SOAP body. The <ReturnValuesResult> element contains three elements that contain our values. The elements correspond to the three types we passed as part of the struct and are treated just like separate data types, with an element for the Boolean, String, and Int32 variables, respectively. Apart from the extra elements, there are no other differences in the body of the message.

How .NET Handles Arrays in the SOAP Body

Structures are sequences made up of different types; you can use them to collect these types in one structure. You use arrays, on the other hand, when you need to represent several instances of the same data type. You can create an array of structures and then read data into them using a Web method.

Start by declaring a struct, this one containing one string and two integers:

public struct Order {     public String Name;     public Int32 OrderNum;     public Int32 Price;  }

The Web method creates an array of structs and then uses a loop to read a string and two integers into each struct. Here’s the code:

[WebMethod(Description="sends arrays")] public Order[] ReturnValues() {     Order [] Array = new Order[3];     for (int i=0; i<=2; i++)     {         Order Field = new Order();         Field.Name = "Shane DeSeranno";         Field.OrderNum = i+1;         Field.Price = 10;         Array[i] = Field;     }     return Array; }

The SOAP message that this method creates looks like this:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body>         <ReturnValuesResponse xmlns="http://www.notashop.com/wscr">             <ReturnValuesResult>                 <Order>                     <Name>Shane DeSeranno</Name>                      <OrderNum>1</OrderNum>                      <Price>10</Price>                  </Order>                 <Order>                     <Name>Shane DeSeranno</Name>                      <OrderNum>2</OrderNum>                      <Price>10</Price>                  </Order>                 <Order>                     <Name>Shane DeSeranno</Name>                      <OrderNum>3</OrderNum>                      <Price>10</Price>                  </Order>             </ReturnValuesResult>         </ReturnValuesResponse>     </soap:Body> </soap:Envelope>

The SOAP body created by the array differs from the one created for the structure. We’ve created an array of structures, and inside the containing <ReturnValuesResponse> and <ReturnValuesResult> elements, the struct is repeated three times, one for each index number. However, the SOAP message makes no effort to distinguish between the different structures in the array, other than to represent their data. So each structure is represented by an identical <DataType> element, which in turn contains elements for the string and two integers. The element names are identical, and the only reason you’d notice that the information isn’t just being repeated is that we read the loop counter from the array into one of the numbers. Even if the same element were included 3000 times, this wouldn’t just mean the information was repeated; it would mean that the content was an array of 300 identical values. The important thing to notice here is that XML treats the order of elements as a part of the infoset, and it preserves the infoset.

This is straightforward enough, but we’ll start to look behind the scenes at what’s actually going on. For starters, there is no indication of any data types being retained by the SOAP. This isn’t the job of SOAP; instead, it is defined by the WSDL that accompanies the SOAP and the Web service. However, we’ve only looked at one type of SOAP message formatting; a second type provides data type information in the message body as well.

SOAP Message Validation

Before you process a message sent to your Web service, you might want to validate it against some kind of definition. XML provides validation against a DTD or a schema, so this should be easy, shouldn’t it? As it turns out, it’s not: references to a schema or a DTD are not allowed inside the <Body> element. But you can define the message schema outside of SOAP—for example, in a WSDL file. This approach is called the document-literal style, or doc-lit, after the corresponding names in WSDL.

You won’t find these names in the SOAP specification. Using doc-lit means using SOAP as a trusted postman to deliver the message, and SOAP won’t look inside. This is the default approach taken by the .NET Framework Web service implementation.

SOAP 1.1 was designed before the advent of WSDL and the XML Schema, so it offers its own serialization standard, known today as SOAP encoding or section 5 encoding. In this case, a message is valid only if it uses the constructs and type names offered by the SOAP encoding specification.

For special kinds of applications, the SOAP standard has some extra validity rules. If you’re using SOAP in a remote method invocation or RPC scenario, you can use so- called RPC representation. RPC representation makes the use of encoding mandatory and defines additional structural requirements for when you use .NET Remoting. However, you don’t have to use SOAP in RPC mode to be able to make RPC-like method calls. This is an important point—you can do this just as easily in the document format.

Most of the time, you’ll find yourself using the document format because the RPC format is a bit of a throwback to the early days of SOAP. The document format, despite its name, can handle both XML documents and RPC calls, and in .NET it does just about everything that the RPC format can. The main thing the RPC format does is handle the different XML data types. With the document format, you can leave it up to .NET to handle the process of serializing each data type correctly. As a result, strong signs are emerging that the RPC style might be on its way out. The Web Services Interoperability organization (WS-I) has removed RPC-encoded format from its first draft. One reason for this is the document format’s better interoperability; the SOAP section 5 encoding poses some insurmountable problems. Also, you can do more in the document format. For example, the RPC style doesn’t work well with asynchronous services and can’t be validated against an XML schema.

We’ll take a look at both programming models, but we’ll primarily concentrate on the document format.

Document Format

In the document format, the message structure inside the Body element is not defined by SOAP—it’s simply not the SOAP protocol stack’s business. As you saw in our example, the document format is preferable; it is described in WSDL files instead by Web services and is the default type in most development kits. It is also the default in .NET.

The document format is meant to deal only with one-way transactions, but it is also possible to use it to model two-way request-response messages. You’ve already seen an example of how the document format parcels up the SOAP body of our CalcPercent Web method:

  <soap:Body>     <CalculatePercent xmlns="http://www.notashop.com/wscr">         <Percent>25</Percent>          <Number>4</Number>      </CalculatePercent> </soap:Body>  

Each subelement is known as a part, and it must be contained within the SOAP body. The part corresponds to a part element within the WSDL document. Other than the SOAP document being well formed, the structure has no other restrictions.

In .NET, you can specify that you want to use the document format by using the SoapDocumentMethod attribute, which you can specify after the WebMethod attribute in the .NET class as follows, if you include the Systems.Web.Services.Protocols namespace:

using System.Web.Services.Protocols  [WebMethod(Description="Calculates the percentage value "     + "given a percentage and a number")] [SoapDocumentMethod] public double CalcPercent(int Percent, double Number) {      }

This is already the default format, so it makes no difference to the SOAP messages that are produced unless you set the accompanying attributes.

The SoapDocumentMethod attribute also has a set of properties, all but one of which can be set by using the [SoapDocumentMethod] specifier:

  • Action Sets the SOAP Action header. (More about this later.)

  • Binding Sets the SOAP binding.

  • OneWay Specifies whether the client should wait for the server to finish processing the method.

  • ParameterStyle Specifies how the parameters are formatted in a SOAP message. Can be set to either Wrapped (sent in a single XML element in the body) or Bare (sent in several XML elements in the body). By default, in ASP.NET it is set to wrapped. ParameterStyle can apply only to document-literal style messages.

  • RequestElementName Specifies the XML element for an XML service method that is used in the SOAP request.

  • RequestNamespace Specifies the namespace for the SOAP request.

  • ResponseElementName Specifies the XML element for an XML service method that is used in the SOAP response.

  • ResponseNamespace Specifies the namespace for the SOAP response.

  • TypeId Gets a unique identifier for the attribute.

  • Use Determines whether literal or encoded serialization style is used.

These properties are passed as initialization parameters of the attribute. In C#, they are set in the [SoapDocumentMethod] body within separate parentheses, for example:

[SoapDocumentMethod(     RequestNamespace="http://www.notashop.com/wscr"     ResponseNamespace="http://www.notashop.com/wscr"      Use=SoapBindingUse.Encoded)] 

RPC Representation

In the RPC representation, each message part is a parameter of the operation being called. Each message part is placed inside an element that shares the name of the operation being called. This element is contained within the SOAP body. In practice, the first difference you’ll see is that this adds an extra element to the SOAP message for the method name. This is closer to the HTTP-GET/HTTP-POST format because it also uses a similar request/response-type structure. Unlike the document format, it’s possible with RPC to include type information for each argument of the call. The RPC format uses the encoding style, which means it uses SOAP encoding to serialize the information according to the different data types.

If we change the previous CalcPercent Web service example to read as follows:

[WebMethod(Description="Calculates the percentage value "     + "given a percentage and a number")] [SoapRpcMethod] public double CalcPercent(int Percent, double Number) {      }

and then re-create the WSDL, compile the client and service, and run the service while listening on SOAP trace, we get back the following SOAP message for the request:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"      xmlns:tns="http://www.notashop/wscr"      xmlns:types="http://www.notashop/wscr/encodedTypes"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body            soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">         <tns:CalcPercent>             <Percent xsi:type="xsd:int">3</Percent>              <Number xsi:type="xsd:double">52</Number>          </tns:CalcPercent>     </soap:Body>    </soap:Envelope>

and the following SOAP message for the response:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"      xmlns:tns="http://www.notashop.com/wscr"      xmlns:types="http://www.notashop.com/wscr/encodedTypes"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">         <tns:CalcPercentResponse>             <CalcPercentResult xsi:type="xsd:double">1.56             </CalcPercentResult>          </tns:CalcPercentResponse>     </soap:Body> </soap:Envelope>

There are several important points to note. The first is the addition of some extra namespace definitions to the <Envelope> element. The tns namespace is defined and it applies to your own target namespace. The second is the addition of types to the SOAP body. This information isn’t actually omitted in the document format; it’s included separately inside the WSDL document. The type information is specified according to the rules of the XML Schema types rather than the .NET types. This means the Percent variable has the type int rather than Int32. The type name belongs to the XML schema namespace, and the type attribute belongs to the XML schema instance namespace. This information is used in the serializing of the different types. The third and final point is the inclusion of an encodingStyle attribute in the SOAP body. This attribute specifies what encoding rules should be used to serialize a SOAP message. By default, the method marked [SoapRpcMethod] can use only the encoding style (as opposed to the literal style).

The SoapRpcMethod attribute has all the same properties as its SoapDocumentMethod relative, with a couple of notable exceptions. The first is that the ParameterStyle and Use properties are missing. ParameterStyle can apply only to the document format and is therefore missing; also, as we discussed earlier, only the encoding style can be specified for the RPC style, so there is no need to have a property that explicitly sets this.

Overall, the document format is preferable for sending SOAP messages. You have more control over the contents of the XML payload, and you can also make it conform to a schema. RPC/encoding style can be useful, in theory, for more specialized situations, such as when you serialize items that need to use RPC (object graphs, for example). Before we move on, let’s investigate SOAP encoding a bit more.

SOAP Encoding

SOAP encoding, also known as section 5 encoding, is a hot topic of debate. It is seen in some quarters as a “shadow from the past”—a legacy of the original SOAP specification, when SOAP was seen as a better way to integrate with object technologies such as DCOM and CORBA rather than as a means of calling functionality of other applications remotely. Because SOAP is now interlinked with WSDL, which describes the behavior that a Web service supports and is, along with UDDI, an essential building block of Web services, problems can occur when you try to serialize data to XML and apply it to XML Schema definitions. In fact, the WS-I considers these problems insurmountable in some places.

The SOAP encoding rules contain details on serializing application data into arrays and objects, but they don’t specify any particular interpretation of the data. This is left up to the receiving application, which itself doesn’t always specify a particular interpretation. We’ll take a brief look at how the different types are treated in SOAP encoding.

Simple Types

The simple types are types such as strings and enumerations. You saw in the previous section how integers and doubles are encoded—with a type attribute that .NET includes (although not all SOAP implementations include this attribute):

<soap:Body        soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">     <tns:CalcPercent>         <Percent xsi:type="xsd:int">3</Percent>          <Number xsi:type="xsd:double">52</Number>      </tns:CalcPercent> </soap:Body>

SOAP encoding allows you to use all of the XSD data types that are contained in the XML Schema specification. Little changes in the process when you encode other simple types, such as strings. For example, if we change our Percent variable to a string as follows, information specified in the type attribute actually changes:

<Percent xsi:type="xsd:string">Three</Percent> 

Compound Types

The compound types are arrays and structures (C# struct). We’ve looked at how both of these types are handled by .NET serialization in the document format, so now let’s see how they can be encoded according to the SOAP 1.1 specification.

Structures

Structures consist of a collection of independent types, so it makes sense that each type specified within a struct should be serialized according to the rules of its own particular type. For example, if we amend our previous example of a struct as follows

[WebMethod(Description="sends structs")] [SoapRpcMethod] public DataTypes ReturnValues() {     DataTypes MyStruct = new DataTypes();     MyStruct.Smoking = true;     MyStruct.Name = "Shane DeSeranno";     MyStruct.Age = 56;     return MyStruct; }

and then we send the SOAP message, the following is returned:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"      xmlns:tns="http://www.notashop.com/wscr"      xmlns:types="http://www.notashop.com/wscr/encodedTypes"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body          soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">         <tns:ReturnValuesResponse>             <ReturnValuesResult href="#id1" />          </tns:ReturnValuesResponse>         <types:DataTypes  xsi:type="types:DataTypes">             <Smoking xsi:type="xsd:boolean">true</Smoking>              <Name xsi:type="xsd:string">Shane DeSeranno</Name>              <Age xsi:type="xsd:int">56</Age>          </types:DataTypes>     </soap:Body> </soap:Envelope>

You can see that each type within the struct has its own type attribute, but each type is also collected in a DataTypes element. As before, there is also a separate Response element for ReturnValues.

Array Handling

SOAP RPC-style messages also allow for encoding of arrays. We can’t change our previous array example to use [SoapRpcMethod] because an array of structs cannot be serialized using RPC-encoded SOAP. Instead, here’s a simplified example of an array that just defines the integers 1 to 5:

[WebMethod(Description="sends array")] [SoapRpcMethod] public int [] ReturnArray() {     int [] Numbers = {1, 2, 3, 4, 5};     return Numbers; }

When sent, this yields the following SOAP response:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"      xmlns:tns="http://www.notashop.com/wscr"      xmlns:types="http://www.notashop.com/wscr/encodedTypes"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Body          soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">         <tns:ReturnArrayResponse>             <ReturnArrayResult href="#id1" />          </tns:ReturnArrayResponse>         <soapenc:Array  soapenc:arrayType="xsd:int[5]">             <Item>1</Item>              <Item>2</Item>              <Item>3</Item>              <Item>4</Item>              <Item>5</Item>          </soapenc:Array>     </soap:Body> </soap:Envelope>

The array is serialized with five parts, which all share the name <Item>. These parts are contained within a soapenc:Array element, which has an attribute specifying the array type, in this case int, and also the number of elements within the type. There is also still the issue of mapping our data types to XML, but this is dealt with in the WSDL document, so we’ll leave that discussion until the next chapter.

Because SOAP encoding seems to be an endangered species these days and the most you’ll use it for will probably be debugging and troubleshooting, this coverage should be enough.

Sending SOAP over HTTP

One major advantage of SOAP is that it isn’t tightly coupled to any protocol. SOAP messages are just XML documents, so you can bind SOAP to many other protocols—in fact, any protocol capable of transporting XML. However, with ASP.NET Web services, SOAP by default can be bound only to the HTTP protocol. If you call a Web service using an ASP.NET client, you get SOAP bound to HTTP.

So far, we’ve discussed only the contents of the SOAP message, but that’s not all that’s contained in the SOAP request and SOAP response when you send messages. When you use HTTP to send SOAP, the SOAP message is sent as part of an HTTP request or response; also included in the SOAP message are the HTTP headers.

Sending Information via HTTP-POST

The HTTP protocol commonly uses two formats to send information, but when you send SOAP envelopes via HTTP, you’ll find that in .NET 1.0, according to the SOAP 1.1 standard, this information is sent via HTTP-POST. The protocol binding allows for extensions that allow SOAP messages bound to HTTP to pass through firewalls.

When you test a Web service using a browser, an example version of the HTTP header information is automatically generated:

POST /recordstore1/service1.asmx HTTP/1.1 Host: chrisuhome Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://chrisuhome:8080/GetRecord"

Five headers are typically sent when you test a Web service method using the browser (although potentially far more can be used). HTTP itself has a similar structure to SOAP in that it, too, breaks down into a header and a body. The HTTP header contains details about the client’s request, such as the length of the content in the request, the type of content, its intended destination, and the method of its carriage. The POST method indicates that the data of the request is transported in the message body.

SOAP Content Type

When you send SOAP over HTTP, some rules always apply. First, you must set the Content-type header. This is commonly set to type text/xml, although the SOAP 1.2 standard suggests that this should be application/soap+xml in the future. Also, in SOAP 1.1 you can send a SOAPAction header along with the request; this header can indicate the intent of the SOAP HTTP request, which contains an endpoint. The SOAP action does not need to be the address of the endpoint or any other resolvable address. It can even be empty. However, not setting the SOAPAction header in .NET might cause problems.

SOAPAction Header

The idea of the SOAPAction header is that it can define richer types of messages, such as those with multiple actions. The firewall that deals with the message can then use the SOAPAction header to help discern the message’s content and decide whether to let it through. As we’ve mentioned, a SOAPAction header must be present to make the message valid, and indeed we’ll look at what happens in the SOAP fault section if we don’t send one. It’s possible, however, to specify a blank SOAP header, as in the following:

POST /wscr/02/RecordFinder/service1.asmx HTTP/1.1 Host: www.notashop.com Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction:

Indeed, no strict rules govern the format of the SOAPAction header; it is set to whatever the Web service wants it to be. As a result, meaningful use of it has been minimal and its usefulness is questionable; it has been removed in version 1.2 of the standard. The problem is that even if you do get to send a message and communicate its intent via a SOAPAction header, it is possible to mask its intentions, so as a security check it becomes fairly useless.

Changes to Bindings in SOAP 1.2

As we’ve discussed, in SOAP 1.1 when you bind SOAP across HTTP you’re by default using HTTP-POST; the SOAP envelope is the payload to the request, and the body of the response is the response SOAP envelope. To complicate matters, SOAP 1.2 defines both HTTP-GET and HTTP-POST as being allowed for the underlying transport.

HTTP-GET

The HTTP-GET method is used exclusively with the SOAP Response Message Exchange Pattern. This message pattern describes a SOAP-formatted response that is returned in reply to a non-SOAP request. This abstract definition simply means

  • No HTTP-GET request can carry a SOAP-formatted message. The GET request uses normal url encoding, and only the response is SOAP.

  • Any HTTP-GET request without a reply is outside the scope of the SOAP standard. There simply is nothing on the wire to check against the standard.

  • An ordinary HTTP-GET request that returns a SOAP-formatted message is covered by the SOAP 1.2 standard.

HTTP-POST

The HTTP-POST method is used exclusively with the SOAP Request-Response Message Exchange Pattern. This pattern mirrors the default (and only) pattern that exists in SOAP 1.1.

Binding to Other Protocols

You can also bind SOAP over a number of other protocols and formats: SMTP, FTP, and even floppy disk, to name a few. But because Web services work over HTTP, we won’t consider how SOAP is bound to these other protocols.

Sending Datasets via an Application

We’ve examined the basic contents of the SOAP message and the HTTP header that accompanies it when it’s sent over HTTP, and we’ve demonstrated how SOAP can encode basic and compound types, but you’ll find that even the simplest applications need to send more complex types, such as datasets. We’ll take this opportunity to not only show how datasets are sent, processed, and received in SOAP, but also to build a small Web client that uses ASP.NET to send the SOAP message and return a SOAP message back to the page rather than requiring us to use SOAP trace to snoop on it.

This application provides a search on a hypothetical music store with a simple query interface (by artist or by record title), and then it returns either a single album or multiple albums in the form of a SOAP message. Because the ASP.NET ISAPI filters ensure that you can send messages only via HTTP and not via SOAP, we’ve had to circumvent the automatically generated proxy client that wsdl.exe builds and build one ourselves to be able to send a SOAP message. If you were to view the message with a monitor such as the one provided in the SOAP Toolkit, you wouldn’t get a SOAP message because there wouldn’t be one to view. You’d only get to see the HTTP message. Instead, we’ll have to construct and send the SOAP message manually.

To do this, we’ll create a proxy client that physically constructs the SOAP request and sends it as a stream of bytes and then receives the results back from the database in the same way. We won’t spend a lot of time discussing how the code does this; we’ll keep it to a brief overview and focus on just the SOAP part of the service.

Our example is in ASP.NET C#, and the hypothetical music store uses Microsoft SQL Server to keep its stock details. The SQL database is then exposed to the Web via a couple of Web methods. This sample database and the code are available with this book’s sample files. Our example takes a query from a user in HTML form and creates a SOAP request that the user doesn’t get to see. Then the example’s code loads the user-supplied data from the form into the request. This request is then sent to a server, which runs the contents of the query/SOAP request against an SQL database and returns a response to the user as a SOAP message. Our application has four separate pieces of logic—the example database (which has already been created), the Web service, the communication layer, and the ASP.NET client. We’ll create them in that order.

The Web Service

The Web service is called RecordFinder, which is the name of our namespace; the service itself is called Service1. Here’s our code for the two Web methods in the RecordFinder Web service:

  using System.Web.Services.Protocols; using System.Data.OleDb;  [WebMethod(Description="Service that gets the records "     + "using the artist name", MessageName="GetArtist")] [SoapDocumentMethod (Action="/GetArtist")] public DataSet GetArtistRecords(string Artist) {     OleDbConnection objConnection;     OleDbDataAdapter objCommand;     string strConnect;     string strCommand;     DataSet DataSet1 = new DataSet();     strConnect = "Provider=SQLOLEDB.1;Password=a12s;"         + "Persist Security Info=True;User ID=ASPNET;"         + "Initial Catalog=dansrecordsSQL;Data Source=(local)";     strCommand = "SELECT Title From Records WHERE Artist='"          + Artist + "'";     objConnection = new OleDbConnection(strConnect);     objCommand = new OleDbDataAdapter(strCommand, objConnection);     objCommand.Fill(DataSet1,"Records");     return DataSet1; } [WebMethod(BufferResponse=true, CacheDuration=20,          Description="Service that get a record "     + "using the title name", MessageName="GetTitle")] [SoapDocumentMethod (Action="/GetTitle")] public DataSet GetTitleDetails(string Title) {     OleDbConnection objConnection;     OleDbDataAdapter objCommand;     string strConnect;     string strCommand;     DataSet DataSet1 = new DataSet();     strConnect = "Provider=SQLOLEDB.1;Password=a12s;"         + "Persist Security Info=True;User ID=ASPNET;"         + "Initial Catalog=dansrecordsSQL;Data Source=(local)";         strCommand = "SELECT Artist From Records WHERE Title='"          + Title + "'";     objConnection = new OleDbConnection(strConnect);     objCommand = new OleDbDataAdapter(strCommand, objConnection);     objCommand.Fill(DataSet1,"Records");     return DataSet1; }

The two Web methods are nearly identical in terms of code. Both open connections to a SQL database and create a dataset. The only difference is the SQL used to create the dataset. The GetArtist method uses SQL to extract the relevant titles from the database, and the GetTitle method uses SQL to extract the relevant artists. Ideally, a full application would return more than just the title or the artist name, but the SOAP generated would be quite extensive and we don’t want to further complicate the example. We’re more interested in the SOAP that’s generated.

The Web Service Communication Class

Wsdl.exe is of course capable of autogenerating the code needed for the proxy class, but because we’re creating the SOAP messages as strings in the Web page, we need to construct a communication class that’s capable of picking up the SOAP message string and sending it to the Web service properly packaged and also receiving the response in the DataSet format. We can add a class ASPNETclient.cs to our project as follows:

  using System.IO; using System.Text; using System.Net;  public class ASPNETclient {     public string soapResult= "Empty";         public string serviceURL =          "http://localhost/wscr/02/RecordFinder/Service1.asmx";     public string serviceNamespace = "http://www.notashop.com/wscr";     public string SOAPResult     {         get         {             return soapResult;         }     }     public void SendSOAPRequest(string actionName, string soapBody)     {         WebRequest servRequest;         servRequest = WebRequest.Create(serviceURL);         servRequest.Headers.Add("SOAPAction",actionName);         HttpWebRequest serviceRequest = (HttpWebRequest)servRequest;         serviceRequest.ContentType = "text/xml";         AddSOAP(serviceRequest, soapBody);         _soapResult = ReturnSOAPResponse(serviceRequest);     }          private void AddSOAP(HttpWebRequest serviceRequest, string body)     {         serviceRequest.Method = "POST";         UTF8Encoding encoding = new UTF8Encoding();         byte[] bodyBytes = encoding.GetBytes(body);         serviceRequest.ContentLength = bodyBytes.Length;         Stream serviceRequestBodyStream =              serviceRequest.GetRequestStream();         serviceRequestBodyStream.Write(bodyBytes, 0, bodyBytes.Length);         serviceRequestBodyStream.Close();     }          private string ReturnSOAPResponse(HttpWebRequest serviceRequest)     {         StreamReader serviceResponseStream;         try         {             WebResponse servResponse;             servResponse = serviceRequest.GetResponse();             HttpWebResponse serviceResponse =                  (HttpWebResponse)servResponse;             serviceResponseStream = new StreamReader(                 serviceResponse.GetResponseStream(),                  System.Text.Encoding.ASCII);         }         catch (WebException e)         {             serviceResponseStream = new StreamReader(                 e.Response.GetResponseStream(),                  System.Text.Encoding.ASCII);         }         catch (Exception e)         {             return e.Message.ToString();         }         string serviceResponseBody;         serviceResponseBody = serviceResponseStream.ReadToEnd();         serviceResponseStream.Close();         return (serviceResponseBody);     } }

The ASPNETClient.cs class is composed of three functions and a single read-only property. The first function, SendSOAPRequest, creates the HTTP header that is necessary for the binding to take place. It starts by creating a Web request for our Web service URL and then takes the header that is generated and adds a SOAPAction header and a Content-type header. It then calls the section function AddSOAP from within its body, supplying the HTTP header details and the SOAP body (which is supplied by our ASP.NET client). AddSOAP then writes our complete SOAP request (HTTP header and SOAP body) to a stream that’s received by the Web service implemented by the Web methods defined above. We then use ReturnSOAPResponse to get the response returned by the Web service.

The ASP.NET Client

Next we can create the ASP.NET Web form, which we’ve named SOAPSender.aspx. This Web form can call the communication class to access our Web methods with one drop-down list containing the entries Artist and Title (corresponding to the two searches we can do), one text box for the search, and a button to instigate it:

<%@ Page language="c#" Codebehind="SOAPSender.aspx.cs"      AutoEventWireup="false" Inherits="RecordFinder.SOAPSender" %>      <body MS_POSITIONING="GridLayout">         <form  method="post" runat="server">             <asp:ListBox                  Runat=server SelectionMode=Single Rows=1>             <asp:ListItem>Artist</asp:ListItem>             <asp:ListItem>Title</asp:ListItem>             </asp:ListBox>             <asp:Label  Runat="server">                 Type artist to find here:             </asp:Label>             <asp:TextBox  runat="server"></asp:TextBox><br>             <br>             <asp:Button  Runat="server"                  Text="Click here to search" OnClick="RetrieveRecords">             </asp:Button>         </form>     </body> </HTML>

The code-behind is as follows:

using System.Xml; using System.Data.OleDb; using System.Data;  public void RetrieveRecords(object sender, EventArgs e) {     string lbs = listBox1.SelectedItem.Text;     string actionName = "/Get" + lbs;     string soapBody = "<?xml version = \"1.0\" "         + "encoding = \"utf-8\" ?>"         + "<soap:Envelope "         + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""         + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "         + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"         + "<soap:Body><Get" + lbs + "  xmlns=\"http://tempuri.org/\">"         + "<"+lbs+">" + txtUserInput.Text + "</" + lbs + "></Get"          + lbs + ">" + "</soap:Body></soap:Envelope>";     RecordFinder.ASPNETclient wsp = new RecordFinder.ASPNETclient();     wsp.SendSOAPRequest(actionName, soapBody);     XmlDocument doc = new XmlDocument();      doc.LoadXml(wsp.SOAPResult);      doc.Save("C:\\inetpub\\wwwroot\\wscr\\02\\RecordFinder\\SOAPResponse.xml");     Response.Redirect("SOAPResponse.xml");     }

The ASP.NET client creates a SOAPAction header based on the method name to be called. The client determines the method to be called by deriving it from the drop- down list. The SOAPAction is important because if you don’t supply a SOAPAction header that matches up with the one specified in the Web service, the .NET Framework generates a “SOAP did not recognize the value of the HTTP Header: SOAPAction” error. If you omit the SOAPAction header completely, you generate a “Request format not recognized” error because in SOAP 1.1 a SOAPAction header is mandatory for a SOAP message.

The ASP.NET client also creates the SOAP envelope and a body for a request, and it substitutes the elements in the SOAP body depending on whether the client wants the artist name or the artist’s titles. We then create an instance of the ASPNETClient class called wsp and call the SendSOAPRequest method with a value for the SOAPAction header and the SOAP message text. We load the result as an XML document object model (DOM) object to avoid problems such as an error being generated because the URI of the different namespace is not recognized. Finally, we take the slightly clumsy action of saving this XML document and redirecting the browser to the saved document.

When we run the query shown in Figure 2-3 against the database, the SOAP in Figure 2-4 is returned.

click to expand
Figure 2-3: Using the SOAPSender ASP.NET application to search for an artist

click to expand
Figure 2-4: The SOAP document that the SOAPSender application returns

The SOAP that is returned is quite lengthy. It divides roughly into two sections: the XSD schema information, which denotes the correct format for the XML data, and a dataset that contains the data returned by the SQL database. We won’t go into much detail here, but it’s interesting to note that because we’re using the document format, the SOAP message doesn’t contain type information. However, the SOAP message contains a schema because a DataSet object needs a set of type information to be reconstituted correctly.

The DataSet object is returned in the <NewDataSet> element, which contains a repeating set of records. The <NewDataSet> element is itself contained within a <diffgr> element. Diffgrams are the .NET Framework way to transport dataset updates as XML. This is the default format the .NET Framework uses when sending database information via SOAP. (We’ll consider this in greater detail in Chapter 9.)

If we dig within the <NewDataSet> element, we’ll see that we’ve actually returned three records that match our query “Supergrass” in the record store database. Each record is returned as a <title> element contained within a <Records> element, along with an identifying rowOrder attribute that reflects the order in which they were found in the database (unsorted). The diffgrams are of type Insert because the client—not knowing anything yet—has to insert the data returned from the server into whatever local representation is required.

The SOAP Header

Having considered the major data types that can be returned in the SOAP body, it’s time to turn our attention to a less used, but still very important, part of the SOAP message: the header. The SOAP header is used to extend the message by adding metadata outside the data in the SOAP body. As mentioned earlier, SOAP information isn’t always sent straight from client to server—it can also pass through a set of intermediate points. Moving tasks to specialized systems makes the overall system more scalable and flexible. SOAP headers can be used to convey information such as security keys and digital signatures that might be dealt with by a security service. Other headers might be used to route the SOAP message to other intermediaries or to declare the message as part of a distributed transaction.

SOAP headers are analogous to proxy headers in the HTTP protocol, but they are much more flexible because the SOAP specification provides only the general header mechanism and doesn’t limit the type or number of headers.

In our record store example, each customer who has registered and bought an item from the store will be allocated a separate account. However, to get details about the user’s account, the application asks the user to supply a login ID and password. To be able to return the account details, some sort of authentication must be performed, and for security and scalability reasons we might want to perform authentication as a separate service on a separate machine.

To provide this functionality for our example, we can specify the user ID in a <loginid> element, which is passed in the SOAP header along with the user password:

<soap:Header>     <loginid>CPU01</loginid>      <password>raz0rf!sh</password> </soap:Header>

A security portal could use this header for authentication and return a fault to the client if the credentials are not valid. In this case, our database Web service would not even see the request because the message would not reach its intended destination.

Soap Header Block

If a SOAP header is present, the SOAP header block must be namespace qualified. In our example, say that we need to submit our identifier every time we query the database. An example SOAP message complete with header block might look like this:

<?xml version="1.0" encoding="utf-8" ?>  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <soap:Header>         <Credentials xmlns="http://testserver/">              <loginid>CPU01</loginid>              <password>raz0rf!sh</password>      </Credentials>     </soap:Header>     <soap:Body>         <GetArtist xmlns="http://www.notashop.com/wscr">             <Artist>Nine Inch Nails</Artist>          </GetArtist>     </soap:Body> </soap:Envelope>

In addition to containing text, the SOAP header can also take a couple of attributes, mustUnderstand and Role.

SOAP mustUnderstand Attribute

The SOAP mustUnderstand attribute is used to indicate whether the SOAP header must be processed. If it is set to 1, the header must be processed or the message must fail. By default it is set to 0. The mustUnderstand attribute conveys the importance and, implicitly, the type of information contained in the SOAP header. If the attribute is set to 0 (optional), this means the information contained is advisory. If the attribute is set to 1, this means the header contains critical information that must be understood by the endpoint.

<SOAPHeader1 xmlns="http://testserver/"      soap:mustUnderstand="1">      <loginid>CPU01</loginid>     <password>raz0rf!sh</password>  </SOAPHeader1> 

SOAP actor or role Attribute

The actor attribute was present in the SOAP 1.1 standard and was used to target the header to a specific type of endpoint. It didn’t really apply to Web services, where the endpoint was the Web service being targeted, and it came into use only when the Web service went via intermediaries. In SOAP 1.2, it has been replaced by the role attribute, which broadly fulfills the same purpose.

When processing a message, a SOAP node is said to act in one or more roles. The roles defined in the standard are

  • next Each SOAP intermediary and the final receiver must act on the role.

  • none SOAP nodes must not act on this role.

  • ultimateReceiver Only the ultimate receiver should act on this role; intermediaries must not.

By default, all headers are targeted at the ultimateReceiver node. The SOAP role attribute is used to determine a specific node or the first node to receive the message and act on this header. It does this by specifying a URI indicating the node a SOAP header has been targeted at. The role attribute helps divide the application protocols from the data-encoding and binding sections of the protocol—something that plays a big part in the Microsoft Global XML Web Services Architecture (GXA).

The role attribute then prefixes our SOAP elements as follows:

<soap:Header>     <rs:Credentials xmlns="http://testserver/"            xmlns:rs="http://testserver/profiles"          soap:role="http://testserver/profiles"         soap:mustUnderstand="1">          <loginid>CPU01</loginid>          <password>raz0rf!sh</password>     </rs:Credentials> </soap:Header> 

Processing and Building a SOAP Header with .NET

Thus far we’ve talked about SOAP headers only in an abstract, non-platform-specific way. Now it’s time to use some of the classes provided with .NET to access this functionality.

You can create a custom SOAP header by deriving a class from the SoapHeader class (System.Web.Services.Protocols.SoapHeader). This class can be associated with the WebService method. The SoapHeader class can be defined in a Web service .asmx file. In our record store example, we can create a custom SoapHeader class to define our login ID as follows:

public class Credentials : System.Web.Services.Protocols.SoapHeader {     public string loginid;     public string password; }

We then need to create a corresponding field in the Web service to store the instance of the SOAP header:

public class Service1: System.Web.Services.WebService {          public Credentials UserEntryIn;      }

The Web service can now receive any method call within this SOAP header.

The next step is to apply a SoapHeader attribute to each Web service method that will need to process the SOAP header. This attribute should share a name with the field created earlier in the Web service.

Our Web method needs to have the following attribute specified:

[WebMethod] [SoapHeader("UserEntryIn")]  

If our Web method receives a request with this SOAP header set, the UserEntryIn variable will contain a Credentials object that matches the provided header.

The SoapHeader Attribute’s Attributes

The SoapHeader attribute has several other properties of interest. We’ll consider only two in this chapter. The first, Direction, can be set to SoapHeaderDirection.In, SoapHeaderDirection.Out, or SoapHeaderDirection.InOut, depending on whether you want the header to convey information to the Web service, from the Web service, or to and from the Web service, respectively. By default, it is set to InOut.

The second attribute, Required, can be set to true or false, depending on whether the header should receive or send any information within the SOAP header, respectively.

Sending Information via SOAP Headers

Let’s augment our record store example so it requires a user to log in with an ID and a password before accessing the functionality of our Web service. The user supplies credentials, which are then passed in the SOAP header. The login is checked against the database, and if a match is found, the corresponding account number is returned as a SOAP header. If no match is found, the information is still returned as a SOAP header and the user is denied access.

We start by creating our SOAP header classes, both derived from SoapHeader. One class will have two members, one for the login name and password the user will send, and the other class will have one member for the login ID (proof of authentication) that will be returned by our Web service:

public class Credentials1: System.Web.Services.Protocols.SoapHeader {     public string loginname;     public string password; } public class Credentials2: System.Web.Services.Protocols.SoapHeader {     public int loginid; }

Next we add two member variables that implement the types created in our SOAP header classes:

public class Service1 : System.Web.Services.WebService {     public Credentials1 userEntryIn;     public Credentials2 userEntryOut;      

We then add two Web methods to our service1.asmx file to define our inbound and outbound SOAP headers:

[WebMethod(Description="Service that returns the login id")] [SoapHeader("userEntryIn")] public int GetLoginID()  {         int userIdent;     int numberOfMatches;     string strConnect = "Provider=SQLOLEDB;"         + "Persist Security Info=False;User ID=ASPNET;"         + "Initial Catalog=dansrecordsSQL;Data Source=CHRISUHOME";     OleDbConnection dataConn = new OleDbConnection(strConnect);                     string dataComm1 = "SELECT Count(*) AS NumberOfMatches "         + "From Users WHERE UserName='" + userEntryIn.loginname          + "' AND Password = '" + userEntryIn.password + "'";     string dataComm2 = "SELECT UserId FROM Users WHERE UserName='"          + userEntryIn.loginname + "'";     OleDbCommand cmd1 = new OleDbCommand(dataComm1,dataConn);                     OleDbCommand cmd2 = new OleDbCommand(dataComm2,dataConn);          dataConn.Open();     numberOfMatches = (int)cmd1.ExecuteScalar();     if (numberOfMatches != 0)     {         userIdent = (int)cmd2.ExecuteScalar();     }     else      {         userIdent=0;     }     dataConn.Close();     return userIdent; } [WebMethod(Description="Service that returns the login id")] [SoapHeader("userEntryOut")] [SoapDocumentMethod (Action="/GetLoginReturn")] public void GetLoginReturn(int UserId)  {         userEntryOut = new Credentials2();     userEntryOut.loginid = UserId; }

The first method takes a name and a password, checks them against the database via a SQL query, and returns the corresponding ID. To prevent an empty set being returned if there is no result, we first run a SQL query that counts the number of records that match. If this is zero, we know to assign a value of 0 to our user identifier. Our ASP.NET client will reject entry if access is denied (if 0 is returned). Our second method just returns the login ID in the loginid member of our UserEntryOut SoapHeader class.

We also need to add references in our Web service to the following namespaces:

using System.Web.Services.Protocols; using System.Data.OleDb;

We then autogenerate our proxy class using wsdl.exe because the SOAP headers that travel back and forth from the client are pretty straightforward and we don’t want to get into the physical details of specifying them ourselves.

The ASP.NET client changes slightly in that we now have two buttons and two sets of event handling code, and we use two panels to display the login ID and then hide it and display the Web service functionality instead:

<body MS_POSITIONING="GridLayout">     <form  method="post" runat="server">         <asp:Label  Runat="server"></asp:Label>         <asp:Panel  Runat="server" Visible="True">             Enter Login Id:              <asp:TextBox  Runat="server"></asp:TextBox>             <BR><BR>Password:              <asp:TextBox  Runat="server"></asp:TextBox>             <BR><BR>             <asp:Button  onclick="RetrieveRecords"                  Runat="server" Text="Click here to search"></asp:Button>         </asp:Panel>         <asp:Panel  Runat="server" Visible="False">             <asp:ListBox  Runat="server" Rows="1"                  SelectionMode="Single">             <asp:ListItem>Artist</asp:ListItem>             <asp:ListItem>Title</asp:ListItem>             </asp:ListBox>Type artist to find here:              <asp:TextBox  runat="server"></asp:TextBox>             <BR><BR><BR>             <asp:Button  onclick="SendSoap" Runat="server"                  Text="Click here to search"></asp:Button>         </asp:Panel>     </form> </body>

The bit that looks after the login process is as follows:

public void RetrieveRecords(object sender, EventArgs e) {     SOAPCredentials.Credentials1 header1 = new SOAPCredentials.Credentials1();     header1.loginname = UserLogin.Text;     header1.password = Passwording.Text;     SOAPCredentials.Service1 chk = new SOAPCredentials.Service1();     chk.userEntryIn = header1;     chk.GetLoginReturn(chk.GetLoginID());     int Result = chk.userEntryOut.loginid;     if (Result != 0)     {         label1.Text = "Welcome Account No: " + Result.ToString();         PanelLogin.Visible = false;         PanelMusic.Visible = true;     }     else     {         label1.Text = "Login Denied - Details Incorrect";     } }   

This code creates an instance of our header and assigns it the contents of the text box in which the user supplied login information. It then reads the whole header into our SoapHeader class and calls our two Web methods, supplying one as a parameter of the other. In other words, we take the ID returned by the GetLoginID method and supply it to the GetLoginReturn method. We could of course achieve the functionality within only one Web method, but then we’d have to send both an ID and a name to our SOAP header class and return both an ID and a name from it. This way is cleaner, with just the string containing the name being sent in and the integer containing the ID being sent out.

If we run the program, it works as follows. We have two logins in our database, one for Chris and one for DeSeranno. Figure 2-5 shows what happens if we log in as Chris.

click to expand
Figure 2-5: Using the SOAPSender ASP.NET form to log in as Chris

There’s no direct way of viewing the SOAP sent here except with the monitor again, but if you quickly view the service (.asmx file) directly, it generates an example version of the SOAP that’s used. For the header used to supply our GetLoginID method, the SOAP is as follows:

<soap:Header>     <SoapHeader1 xmlns="http://www.notashop.com/wscr">         <loginname>string</loginname>     </SoapHeader1> </soap:Header>

For the header returned by our GetLoginReturn method, the SOAP is as follows:

<soap:Header>     <SoapHeader2 xmlns="http://www.notashop.com/wscr">         <loginid>int</loginid>     </SoapHeader2> </soap:Header>

Our ASP.NET client can now act on the information it has sent and has had returned in the form of SOAP headers.

Note that using your own SOAP headers to perform authentication tasks probably isn’t the most desirable approach. You should consider using the new WS-Security standard instead. We’ve taken the SOAP header approach here because it provides a simple and quick demonstration of how SOAP headers can be sent and acted on by an endpoint.

Unknown Headers

Web service clients can also send headers to the service that haven’t been defined and that the Web service is not expecting to receive. The Web service will probably ignore these headers, but if you process them, you can use them to call Web services and potentially set up a chain of Web services.

SOAP 1.2 Relay Header

In SOAP 1.2, a SOAP header can carry a relay attribute that can be set to false or true. When it’s set to true, you can forward the SOAP message to a SOAP node other than the one that received it for processing, although it must follow the same set of rules for processing.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

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