July 6, 2008, 1:42 a.m.
posted by angryuser
Anatomy of a Web ServiceXML Web Services built with ASP.NET are implemented as ASP.NET handlers. ASP.NET has two very powerful extension mechanisms. In addition to Web pages and Web services, you can build more customized Web applications with HTTP Handlers and HTTP Modules. Although further discussion of these technologies is outside the scope of this book, I highly recommend that you read the ASP.NET documentation for more information. When a request comes into IIS for the test service, it looks something like Listing 3.2. IIS examines the request and determines that it is being sent a file that ends with the extension .asmx. The ASP.NET handler is registered with IIS as handling this extension; therefore, the request in its entirety is handed over to the ASP.NET process. A SOAP Message
POST /Test/Service1.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/HelloWorld"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<HelloWorld xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
At this point, the ASP.NET worker process examines the request and discovers that it has a handler registered in machine.config, the system-level .NET configuration file, for a request for resources with the extension .asmx. It therefore kicks up this handler and passes the request onto it. Next, the handler creates an instance of the class defined in the handler, and uses reflection to examine which operations the class supports and how those operations should be routed. By default, operations are mapped to methods using the SOAPAction HTTP header, but this can be changed to map to the first child element of the SOAP body. Once the message is routed, the handler uses the XML Serializer to deserialize the request XML into the parameters that the method is expecting. Once the method returns, this return value and any out parameters are mapped back into XML with the XML Serializer. Then, a response SOAP message is created that wraps this XML, and the response is sent back, via ASP.NET's HttpResponse context. Figure shows how this routing of messages appears. 3. Message Routing
If an exception is thrown, then it is wrapped inside of a SoapException, and a SOAP fault is returned instead, with the HTTP status code set to 500. (For nonerror responses, the status code is 200, or OK.) The SOAP Extension architecture permits developers to write custom code to be run at various points within this time line. SOAP extensions are discussed later in this chapter. Building Document-Literal ServicesBy default, XML Web services built with ASP.NET are document based; that is, they are not remote method calls. They are request–response and are mapped to methods as part of the programming model. But there is nothing inherent within the XML sent and received from these services that is RPC based. Section 7 of the SOAP specification outlines the design of XML that is for remote method invocations, and by default that section is not followed. Also by default, services are schema based, or what is called literal XML. Section 5 of the SOAP specification outlines a mapping from data types in common programming languages to XML constructs. By default .NET does not use this encoding. Instead, a mapping is made using the XML Serializer that is completely XSD schema based. The programming model for these document/literal services with .NET is quite extensible. You can create operations that receive and respond with a wide variety of XML. For example, imagine that we wanted to create an operation that takes this (greatly simplified from the real world) purchase order document as a request inside of the SOAP <Body>:
<PurchaseOrder ID="1234">
<Date>1/2/2001>Date>
<Item>Widgets</Items>
<Amount>400</Amount>
</PurchaseOrder>
And responds with a receipt document that looks like this:
<PurchaseOrderReceipt>
<ReceiptID>5678</ReceiptID>
</PurchaseOrderReceipt>
We can map this XML into our method in several ways. Listing 3.3 shows a method that maps this purchase order document as several parameters. A Method with Wrapped Parameter Style
[WebMethod]
[return: XmlElement("PurchaseOrderReceipt")]
public void PurchaseOrder(
[XmlAttribute] String ID,
DateTime Date,
int Amount,
out String ReceiptID )
{
ReceiptID = "5678";
return;
}
The XmlElementAttribute and XmlAttributeAttribute attribute classes are used for XML Serialization. In order for this code to compile, the System.Xml.Serialization namespace must be imported: using System.Xml.Serialization; By default, the method name is mapped to the root element, which in this example is PurchaseOrder. Also by default, each parameter of the method is mapped to a child element of this XML document. However, the ID parameter should be mapped to an XML attribute, not a child element. Thus, this facet of the serialization is overridden with the XmlAttribute attribute. XML Serialization uses metadata attributes like this one to control the mapping of XML into data types. Because ReceiptID is an out parameter, it is serialized into XML for the response. It is made a child element of the response wrapper element. By default, this is the name of the method with the word Response appended to it. In this case, it would be "PurchaseOrderResponse" but should be "PurchaseOrderReceipt". Thus, an attribute is put on the return value of this method; using the XmlElementAttribute class, this value is set to the correct one. This method of serialization, in which the method name is used for the root element of the document is called wrapped. There is another parameter style called bare, in which the method name is immaterial. You can choose either of these by using the SoapDocumentMethodAttribute attribute found in the System.Web.Services.Protocols namespace. By default, the parameter style is wrapped. First, you would import the namespace: using System.Web.Services.Protocols; Next, you would change the method to be bare, and remove the [XmlAttribute] attribute from the ID parameter, as shown in Listing 3.4. A Method with a Bare Parameter Style
[WebMethod]
[return: XmlElement("PurchaseOrderReceipt")]
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
public String PurchaseOrder( String ID, DateTime Date, int Amount)
{
return "5946";
}
As a result, the SOAP request for this method would not be serialized with the method name as a wrapping element, as can be seen in Listing 3.5. The SOAP Request for the Bare Style
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ID xmlns="http://tempuri.org/">5946</ID>
<Date xmlns="http://tempuri.org/">1/1/2002 4:00:00PM</Date>
<Amount xmlns="http://tempuri.org/">600</Amount>
</soap:Body>
</soap:Envelope>
Furthermore, the response XML would be different, as shown in Listing 3.6. The SOAP Response for a Bare Style Method
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PurchaseOrderReceipt
xmlns="http://tempuri.org/">5946</PurchaseOrderReceipt>
</soap:Body>
</soap:Envelope>
Of course, these requests and responses are fairly nondescriptive. The original request and response were easier to understand. However, with the bare parameter style, you can still create messages that look as if they were wrapped, because you can create classes that mirror the XML documents that you want to create. These classes will be serialized in a similar manner to the methods. First, you would create a class called PO:
public class PO
{
[XmlAttribute]
public String ID;
public DateTime Date;
public int Amount;
}
Next, you would create a class for the response:
public class POReceipt
{
public String ReceiptID;
}
You can apply the SoapDocumentServiceAttribute attribute to an entire class to change the parameter style, or you can do it per method with the SoapDocumentMethodAttribute attribute. For example, you can change the method to take and return the following classes:
[WebMethod]
[return: XmlElement("PurchaseOrderReceipt")]
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
public POReceipt PurchaseOrder( PO PurchaseOrder )
{
POReceipt receipt = new POReceipt();
return receipt;
}
Listing 3.7 shows the resulting request message. A Descriptive Bare Style Message
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PurchaseOrder ID="string" xmlns="http://tempuri.org/">
<Date>1/1/2002 4:00:00PM </Date>
<Amount>600</Amount>
</PurchaseOrder>
</soap:Body>
</soap:Envelope>
Listing 3.8 shows the resulting response message. A Descriptive Bare Style Response Message
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PurchaseOrderReceipt xmlns="http://tempuri.org/">
<ReceiptID>5946</ReceiptID>
</PurchaseOrderReceipt>
</soap:Body>
</soap:Envelope>
Notice that, with metadata shaping, the message format is very easy. As you will learn later in this chapter, this code can be automatically generated for you with wsdl.exe—a tool used primarily to create clients of XML Web services (refer to Chapter 4, Creating Web Service Clients, for more information), but which also can be used to create abstract base classes for servers. Building Document-Encoded ServicesThe examples so far have been document based; that is, documents have been passed inside of SOAP messages. These documents are mapped to methods within your Web service, but this is purely a programming model conceit. They are also request–response, but again, they are not remote method calls in the classic sense. In addition, these documents have been based on a schema. You can see this schema in the types sections of the automatically created WSDL. When a schema contains all of the information needed to serialize and deserialize the XML in a message, the WSDL calls it a literal message. However, Section 5 of the SOAP specification offers an alternative to literal documents—encoded XML, which is a specialized mapping of data types to XML documents. For example, Section 5 describes how to create arrays, structs, and other common data type constructs. It also details a method for object referencing. Listing 3.9 is an example of an encoded message. An Encoded SOAP Message
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/"
xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<types:PO xsi:type="types:PO">
<ID xsi:type="xsd:string">1234-56</ID>
<Date xsi:type="xsd:dateTime">1/1/2002 4:00:00PM</Date>
<Amount xsi:type="xsd:int">40</Amount>
</types:PO>
</soap:Body>
</soap:Envelope>
To create this message structure, change one thing on the method declaration: set the Use property of the SoapDocumentMethodAttribute attribute to be encoded. First, include this namespace: using System.Web.Services.Description; And use this code:
[WebMethod]
[return: SoapElement("PurchaseOrderReceipt")]
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare, Use=SoapBindingUse.Encoded)]
public POReceipt PurchaseOrder( PO PurchaseOrder )
{
POReceipt receipt;
return receipt;
}
Notice that the ID property is no longer an attribute. Encoded SOAP contains no attributes; everything is an element. But even more important, the set of attributes inside of System.Xml.Serialization that start with "[Xml …]" are only for literal XML. Encoded XML uses the "[Soap …]" attributes. This is a bit of a misnomer, because the literal attributes can be used for SOAP as well. To change the PO class to use the encoded XML attributes, you would set the namespace of the XML to be http://encoded/.
[SoapType(Namespace="http://encoded")]
[XmlRoot(Namespace="http://literal")]
public class PO
{
[XmlAttribute]
public String ID;
public DateTime Date;
public int Amount;
}
The request message this service would be expecting would now look something like the one in Listing 3.9. Chapter 4 discusses the XML Serialization architecture in much greater detail. Refer to that chapter to learn more about using metadata attributes to control the serialization of encoded and literal XML. Building RPC-Encoded ServicesAs mentioned earlier, by default .NET creates services that merely map documents inside of SOAP messages to methods. This mapping is a programming model, and not strictly speaking a remote method call. But Section 7 of the SOAP specification does describe how to do method invocations. The discussion here relies heavily on the encoding described in Section 5 of the SOAP specification: an RPC call with encoded XML, or RPC encoded. Basically, it states that you can model a method call as a struct and that you can do so without using Section 5 encoding. In effect, you can create an RPC call with literal XML, or RPC literal. The .NET Framework only supports RPC-encoded operations. There are two ways to change your service from the document/literal default to handle RPC-encoded operations: the SoapRpcServiceAttribute attribute and the SoapRpcMethodAttribute attribute. If you put the SoapRpcServiceAttribute attribute on the class, then all methods will be RPC encoded. If instead you want to control each method, you can use the SoapRpcMethodAttribute attribute on each method.
[WebMethod]
[SoapRpcMethod]
public POReceipt PurchaseOrder( PO PurchaseOrder )
{
POReceipt receipt;
return receipt;
}
The resulting method request would look something like Listing 3.10. A SOAP Message with References
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/"
xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:PurchaseOrder>
<PurchaseOrder href="#id1" />
</tns:PurchaseOrder>
<types:PO id="id1" xsi:type="types:PO">
<ID xsi:type="xsd:string">123-45</ID>
<Date xsi:type="xsd:dateTime">1/1/2002 4:00:00PM </Date>
<Amount xsi:type="xsd:int">40</Amount>
</types:PO>
</soap:Body>
</soap:Envelope>
And the method response would look something like Listing 3.11. A Response Message with References
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/"
xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:PurchaseOrderResponse>
<PurchaseOrderResult href="#id1" />
</tns:PurchaseOrderResponse>
<types:POReceipt id="id1" xsi:type="types:POReceipt">
<ReceiptID xsi:type="xsd:string">123-45</ReceiptID>
</types:POReceipt>
</soap:Body>
</soap:Envelope>
Notice how the encoded XML in Listing 3.11 is creating object references with ID/HREF combinations. If you were to change your service to include two classes that contain the same object, then you could get object references that keep that object identity. Chapter 9 (The Messaging Protocol: SOAP) discusses SOAP and object references in greater detail. Building One-Way ServicesOne of the more interesting aspects of SOAP is how simple it is. The required parts of the SOAP specification describe an enveloping mechanism for sending an XML-based message. That's it—just how to send a message from point A to point B. As we've seen so far, SOAP is often used for slightly more complex scenarios, such as when you want to send a message and receive a response that is correlated to that message. For example, when doing RPC-encoded operations, you are making a method call. In that case, you want to send a message—the method invocation with the parameters—and you want to receive a response as part of that HTTP request—the return value and the value of any referenced parameters. Document passing operations are similar in that often they want a message in response right away, even if just an acknowledgment. At the very least, it's nice to know there wasn't an error, and that the message was understood. However, there are some interesting messaging scenarios wherein you want to send a message but don't need an immediate response. Some transports other than HTTP, such as SMTP, will require that you don't receive a response, because those transports by their very nature are one-way. .NET allows you to build XML Web services that can receive these one-way messages. Because these messages are over HTTP, an HTTP status code is returned (202 Accepted), but no other message is returned. Only methods that return void can be one-way operations. Other than that, you merely need to set the OneWay property of the SoapDocumentMethodAttribute or SoapRpcMethodAttribute attribute to true. Listing 3.12 shows a one-way operation with the SoapRpcMethodAttribute attribute. A One-Way Method
[WebMethod]
[SoapRpcMethod(OneWay=true)]
public void SubmitPurchaseOrder( PO PurchaseOrder )
{
//do some processing with PurchaseOrder
return;
}
Now, the request message for this operation will look like Listing 3.13. The Request Message for a One-Way Operation
POST /Chapter1/OneWay.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: XXXX
SOAPAction: "http://tempuri.org/SubmitPurchaseOrder"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/"
xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:SubmitPurchaseOrder>
<PurchaseOrder href="#id1" />
</tns:SubmitPurchaseOrder>
<types:PO id="id1" xsi:type="types:PO">
<ID xsi:type="xsd:string">12345</ID>
<Date xsi:type="xsd:dateTime">1/1/2001</Date>
<Amount xsi:type="xsd:int">40</Amount>
</types:PO>
</soap:Body>
</soap:Envelope>
But the response message will look much smaller: HTTP/1.1 202 Accepted One-way messages are best used when you merely need to send the server a message, but don't need a response immediately. It's important to be aware that the 202 Accepted HTTP response occurs almost immediately after the message has been deserialized, but before any of your code runs. So, if you need to communicate an error response to the client, you will be unable to do so. That said, one-way messages offer an easy way to send messages in a quick, fire-and-forget pattern. They also can offer a higher level of performance on the server, because you are spending less time holding a network connection open while your code runs. Controlling RoutingWhen a service request comes in, it looks something like Listing 3.14. A Message in Need of Dispatching
POST /Chapter1/OneWay.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: XXXX
SOAPAction: "http://tempuri.org/SubmitPurchaseOrder"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/"
xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:SubmitPurchaseOrder>
<PurchaseOrder href="#id1" />
</tns:SubmitPurchaseOrder>
<types:PO id="id1" xsi:type="types:PO">
<ID xsi:type="xsd:string">4563</ID>
<Date xsi:type="xsd:dateTime">1/1/2001</Date>
<Amount xsi:type="xsd:int">37</Amount>
</types:PO>
</soap:Body>
</soap:Envelope>
If you have more than one marked [WebMethod], then how does ASP.NET decide which method should receive and process this request? There are two choices of mechanisms for routing, or dispatching, with ASP.NET:
You can use the SoapDocumentService and SoapRpcService attributes to control the method of routing. By default, routing is accomplished with the SOAPAction value. This requires that each operation have a SOAP Action that is unique across the service. If this isn't possible, then you can route instead on the first child element of the <Body> element. The following code snippet shows how to set this:
using System.Web.Services.Protocols;
[SoapDocumentService(RoutingStyle=SoapServiceRoutingStyle.RequestElement)]
public class routing : System.Web.Services.WebService
{
|
- Comment
