Inside WSDL with .NET Attribution

 

Keith Pijanowski
Microsoft .NET Developer Evangelist

January 2004

Applies to:
   Microsoft® ASP.NET
   Microsoft Visual Studio® .NET
   Web Services Definition Language (WSDL)
   XML Web services

Summary: Understanding how a WSDL file describes your Web service is the key to understanding how XML Web services work in general. This article is for .NET developers who want to demystify the WSDL files that Microsoft ASP.NET generates by examining the seven major elements that compose a WSDL file. Techniques for altering the generated WSDL by using the attributes available within the System.Web.Services, System.Web.Services.Protocols, and the System.Xml.Serialization namespaces are also shown. (31 printed pages)

Download the associated code sample for this article.

Contents

Introduction
Definitions
Types
Controlling Types with .NET Attributes
XML Serialization
Messages
Controlling Messages with .NET Attributes
Port Types
Bindings
Ports
Controlling Port Types with .NET Attributes
Services
Controlling Services with .NET Attributes
Conclusion
Background Information
Related Articles

Introduction

WSDL is an XML format for describing network services in a platform-independent fashion. The WSDL 1.1 specification calls for the following seven XML elements to describe network services:

  1. Definitions
  2. Types
  3. Messages
  4. portTypes
  5. Bindings
  6. Ports
  7. Services

The strong support for XML in the .NET Framework allows developers to create Web services without getting caught up in the minutiae of the XML needed to describe Web service interfaces. However, should you need them, the attributes listed below in Table 1 can be used to modify the generated WSDL.

Table 1. Attributes used to modify generated WSDL

Namespace Attribute
System.Web.Services WebMethod
System.Web.Services WebService
System.Web.Services WebServiceBinding
System.Web.Services.Protocols SoapDocumentMethod
System.Web.Services.Protocols SoapDocumentService
System.Web.Services.Protocols SoapRpcMethod
System.Web.Services.Protocols SoapRpcService
System.Xml.Serialization XmlElement
System.Xml.Serialization XmlType
System.Xml.Serialization XmlElementAttribute
System.Xml.Serialization XmlAttributeAttribute
System.Xml.Serialization XmlIgnore

The Web service used throughout this article supports the SOAP protocol only. By default, version 1.1 of the .NET Framework has the SOAP protocol turned on and HttpPost and HttpGet turned off via the machine.config configuration file. Version 1.0 of the .NET Framework has all three protocols turned on by default. So, if you have not upgraded to Visual Studio .NET 2003 and version 1.1 of the .NET Framework and you wish to follow the examples in a "hands-on" fashion, then go to the protocols section of your Web service's web.config file and remove the HTTPPost and HTTPGet protocols as illustrated below.

      <protocols>
         <remove name = "HttpPost"/>
         <remove name = "HttpGet"/>
      </protocols>

Definitions

Since a WSDL document is nothing more than a set of definitions, it is only appropriate that a definitions element be the root element of all WSDL documents. This element always contains the Types, Messages, portTypes, Bindings, Ports, and Services elements described in this article. The root element may also contain optional import statements and an optional documentation element. The general format of a WSDL document follows:

<definitions>
   <import namespace = "uri" location = "uri">
   <documentation></documentation>
   <types></types>
   <message name = "nmtoken"></message>
   <portType name = "nmtoken"></portType>
   <binding name = "nmtoken"></binding>
   <service name = "nmtoken">
      <port name = "nmtoken"></port>
   </service>
</definitions>

Types

The first element you will see at the top of every ASP.NET-generated WSDL file is the Types element. The WSDL 1.1 specification states the following about the Types element:

"The types element encloses data type definitions that are relevant for the exchanged messages."

In other words, the Types element contains type definitions that describe custom data types used as WebMethod parameter values, and custom data types used as WebMethod return values. The Types element may also contain element declarations that describe the contents of SOAP body elements used to send requests and receive response messages for a WebMethod. Before investigating these element declarations that are used for messages, let's concentrate on fully understanding how the Types element is used to describe the custom data types needed by our Web service.

Consider the code in Code block 1 which implements an order class, a confirmation class, and a MyStore Web service class containing a WebMethod that receives an order object as a parameter and returns a confirm object.

Code block 1. The Order class, the Confirmation class, and the MyStore Web service class

Imports System.Web.Services

<WebService([Namespace]:="http://KeithPij.org/WSLibrary/MyStore", 
   Description:="Demonstration of a Web Service")> _
Public Class MyStore
    Inherits System.Web.Services.WebService

    <WebMethod()> _
    Public Function PlaceOrder(ByVal objOrder As Order) As Confirm
        Dim objConfirm As New Confirm()
        objConfirm.OrderID = 1234
        objConfirm.Status = "Pending"
        Return objConfirm
    End Function

End Class

Public Class Order
    Public OrderID As Long
    Public ProductID As Long
    Public ProductName As String
    Public Quantity As Integer
    Public UnitPrice As Decimal
    Public OrderDate As Date
End Class

Public Class Confirm
    Public OrderID As Long
    Public Status As String
End Class

The WSDL file for this Web service must contain enough information for a consumer of this Web service to create an order object and understand the returned confirmation object. This is the purpose of the Types element of a WSDL file. The bolded portions of Code block 2 shows the type definitions within the Types element that ASP.NET will automatically generate based on the implementation of the Order and Confirm classes shown in Code block 1.

Code block 2. Types element containing the type definitions for both the Order and Confirmation classes

  <types>
    <!—Namespace declaration -->
    <s:schema elementFormDefault="qualified" targetNamespace="http://KeithPij.org/WSLibrary/MyStore">

      <!—Element declaration for PlaceOrder request messages -->
      <s:element name="PlaceOrder">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="objOrder" type="s0:Order" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <!—Type definition for the Order class -->      <s:complexType name="Order">        <s:sequence>          <s:element minOccurs="1" maxOccurs="1" name="OrderID" type="s:long" />          <s:element minOccurs="1" maxOccurs="1" name="ProductID" type="s:long" />          <s:element minOccurs="0" maxOccurs="1" name="ProductName" type="s:string" />          <s:element minOccurs="1" maxOccurs="1" name="Quantity" type="s:int" />          <s:element minOccurs="1" maxOccurs="1" name="UnitPrice" type="s:decimal" />          <s:element minOccurs="1" maxOccurs="1" name="OrderDate" type="s:dateTime" />        </s:sequence>      </s:complexType>

      <!—Element declaration for PlaceOrder response messages -->
      <s:element name="PlaceOrderResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="PlaceOrderResult" type="s0:Confirm" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <!—Type definition for the Confirm class -->      <s:complexType name="Confirm">        <s:sequence>          <s:element minOccurs="1" maxOccurs="1" name="OrderID" type="s:long" />          <s:element minOccurs="0" maxOccurs="1" name="Status" type="s:string" />        </s:sequence>      </s:complexType>

      <!—Element declaration for CheckOrder request messages -->
      <s:element name="CheckOrder">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="nOrderID" type="s:int" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <!—Element declaration for CheckOrder response messages-->
      <s:element name="CheckOrderResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="CheckOrderResult" type="s0:Confirm" />
          </s:sequence>
        </s:complexType>
      </s:element>

    </s:schema>
  </types>

These two type definitions contain everything we need to know about our two classes. In fact, it is this information that Visual Studio .NET uses to build the client-side implementation of these classes when generating the client proxy for our Web service. Code block 3 shows the Visual Studio .NET-generated implementation of our two classes.

Code block 3. Class definitions generated as a part of the Web service proxy

    '<remarks/>
    <System.Xml.Serialization.XmlTypeAttribute([Namespace]:="http://KeithPij.org/WSLibrary/MyStore")>  _
    Public Class Order
        '<remarks/>
        Public OrderID As Long
        '<remarks/>
        Public ProductID As Long
        '<remarks/>
        Public ProductName As String
        '<remarks/>
        Public Quantity As Integer
        '<remarks/>
        Public UnitPrice As Decimal
        '<remarks/>
        Public OrderDate As Date
    End Class
    
    '<remarks/>
    <System.Xml.Serialization.XmlTypeAttribute([Namespace]:="http://KeithPij.org/WSLibrary/MyStore")>  _
    Public Class Confirm   
        '<remarks/>
        Public OrderID As Long
        '<remarks/>
        Public Status As String
    End Class

Finally, notice that the types portion of our WSDL file contains more than type definitions. There are four element declarations named PlaceOrder, PlaceOrderResponse, CheckOrder, and CheckOrderResponse that are needed to generate request messages and interpret response messages from our WebMethods. These element declarations will be discussed further when we investigate the message elements of our WSDL file.

Controlling Types with .NET Attributes

There are no mandatory attributes that need to be utilized when we implement classes need by our Web services. However, if we look closely at the Types element that ASP.NET generates for our Order and Confirm classes, we will see that these classes have been put into the "http://KeithPij.org/WSLibrary/MyStore" namespace. This is the same namespace in which our Web service resides. Ordinarily this is not a problem; however, this is a problem if we build separate Web services that need to pass instances of our classes (objects) to each other. Specifically, if we want to pass an instance of our order class from our current Web service to a second Web service that implements this order class in the same exact way (that is, the same exact class definition, but it is assigned a different XML namespace), then the second Web service would not recognize our Order object as the correct type, and a Web service fault would result. To allow objects to be passed from one Web service to another, we need to assign a namespace to our classes by using the XMLType attribute. Code block 4 illustrates the use of this attribute when implementing our Order and Confirm classes.

Code block 4. Use of the XMLType attribute to specify a namespace

<XmlType(NameSpace:="http://KeithPij.org/WSLibrary/DataTypes")> _
Public Class Order
    Public OrderID As Long
    Public ProductID As Long
    Public ProductName As String
    Public Quantity As Integer
    Public UnitPrice As Decimal
    Public OrderDate As Date
End Class

<XmlType(NameSpace:="http://KeithPij.org/WSLibrary/DataTypes")> _
Public Class Confirm
    Public OrderID As Long
    Public Status As String
End Class

By using this attribute, any other Web service that defines an Order class or Confirm class in exactly the same way (including the specification of the namespace) will be able to accept instances of these classes, even if they were instantiated using the definition from a different WSDL file. It is often a best practice to put all class definitions used by more than one Web service into a single code file so that the namespace declarations can be easily maintained. This technique is illustrated in the DataTypes.vb file from the code download. I have left it as an exercise for the reader to investigate the modifications to the generated WSDL once these attributes are applied to the Order and Confirm classes.

XML Serialization

This is a good time to discuss XML serialization. XML serialization is the process by which our .NET objects are converted into XML, and deserialization is the process of turning XML into .NET objects. Table 2 lists all the classes in the System.Xml.Serialization namespace along with a short description of each class. This table is reproduced here exactly as it appears in the Visual Studio .NET online Help.

Table 2. The classes in the System.Xml.Serialization namespace

Class Description
SoapAttributeAttribute Specifies that the XmlSerializer should serialize the class member as an encoded SOAP attribute.
SoapAttributeOverrides Allows you to override property, field, and class attributes when you use an XmlSerializer to serialize or deserialize an object as encoded SOAP.
SoapAttributes Represents a collection of attribute objects that control how the XmlSerializer serializes and deserializes SOAP methods.
SoapElementAttribute Specifies that the public member value be serialized by the XmlSerializer as an encoded SOAP XML element.
SoapEnumAttribute Controls how the XmlSerializer serializes an enumeration member.
SoapIgnoreAttribute Instructs the XmlSerializer not to serialize the public field or public read/write property value.
SoapIncludeAttribute Allows the XmlSerializer to recognize a type when it serializes or deserializes an object as encoded SOAP XML.
SoapTypeAttribute Controls the schema generated by the XmlSerializer when a class instance is serialized as SOAP-encoded XML.
UnreferencedObjectEventArgs Provides data for the known, but unreferenced, object found in an encoded SOAP XML stream during deserialization.
XmlAnyAttributeAttribute Specifies that the member (a field that returns an array of XmlAttribute objects) can contain any XML attributes.
XmlAnyElementAttribute Specifies that the member (a field that returns an array of XmlElement or XmlNode objects) can contain objects that represent any XML element that has no corresponding member in the object being serialized or deserialized.
XmlAnyElementAttributes Represents a collection of XmlAnyElementAttribute objects.
XmlArrayAttribute Specifies that the XmlSerializer should serialize a particular class member as an array of XML elements.
XmlArrayItemAttribute Specifies the derived types that the XmlSerializer can place in a serialized array.
XmlArrayItemAttributes Represents a collection of XmlArrayItemAttribute objects.
XmlAttributeAttribute Specifies that the XmlSerializer should serialize the class member as an XML attribute.
XmlAttributeEventArgs Provides data for the UnknownAttribute event.
XmlAttributeOverrides Allows you to override property, field, and class attributes when you use the XmlSerializer to serialize or deserialize an object.
XmlAttributes Represents a collection of attribute objects that control how the XmlSerializer serializes and deserializes an object.
XmlChoiceIdentifierAttribute Specifies that the member can be further disambiguated by using an enumeration.
XmlElementAttribute Indicates that a public field or property represents an XML element when the XmlSerializer serializes or deserializes the containing object.
XmlElementAttributes Represents a collection of XmlElementAttribute, which the XmlSerializer uses to override the default way it serializes a class.
XmlElementEventArgs Provides data for the UnknownElement event.
XmlEnumAttribute Controls how the XmlSerializer serializes an enumeration member.
XmlIgnoreAttribute Instructs the Serialize method of the XmlSerializer not to serialize the public field or public read/write property value.
XmlIncludeAttribute Allows the XmlSerializer to recognize a type when it serializes or deserializes an object.
XmlNamespaceDeclarationsAttribute Specifies that the target property, parameter, return value, or class member contain prefixes associated with namespaces that are used within an XML document.
XmlNodeEventArgs Provides data for the UnknownNode.
XmlRootAttribute Controls XML serialization of the attribute target as an XML root element.
XmlSerializer Serializes and deserializes objects into and from XML documents. The XmlSerializer enables you to control how objects are encoded into XML.
XmlSerializerNamespaces Contains the XML namespaces and prefixes that the XmlSerializer uses to generate qualified names in an XML-document instance.
XmlTextAttribute Indicates to the XmlSerializer that the member should be treated as XML text when the containing class is serialized or deserialized.
XmlTypeAttribute Controls the XML schema generated when the attribute target is serialized by the XmlSerializer.
XmlTypeMapping Contains a mapping of one type to another.

This namespace contains classes that are used to serialize objects into XML and also to deserialize XML into .NET objects. Since there are many ways to serialize an object into XML, these classes provide all the necessary options for controlling the serialization process. Most of these classes are .NET attributes, which means that they are not instantiated and invoked in a traditional manner. Rather, these attributes are used to decorate classes, fields, and properties (not functions or methods—this namespace is only concerned with data). This additional information that developers specify via attribution is placed in the assembly. At run time, whenever the data within our objects needs to be serialized to XML, an XML serializer object will serialize based on what was specified in the serialization attributes. A similar process works when we are going in the reverse direction and XML needs to be deserialized into .NET objects. As an aside, the WebMethod attribute from the System.Web.Services namespace works in a similar fashion; however, the WebMethod attribute causes ASP.NET to intercept requests made against a Web service so that SOAP request messages can be deserialized into a .NET function call with the appropriate parameters. The subsequent response messages are then serialized into SOAP responses and sent back to the original caller.

As a more definitive example of XML serialization, let's attribute our Order class as depicted in Code block 5.

Code block 5. Use of the XMLAttribute attribute to specify that the public fields of all Order objects should be serialized as XML attributes

<XmlType(NameSpace:="http://KeithPij.org/WSLibrary/DataTypes")> _
Public Class Order
    <XmlAttributeAttribute()> _
    Public OrderID As Long
    <XmlAttributeAttribute()> _
    Public ProductID As Long
    <XmlAttributeAttribute()> _
    Public ProductName As String
    <XmlAttributeAttribute()> _
    Public Quantity As Integer
    <XmlAttributeAttribute()> _
    Public UnitPrice As Decimal
    <XmlAttributeAttribute()> _
    Public OrderDate As Date
    <XmlIgnore()> _
    Public InternalNotes As String
End Class

In this example, the public fields of the Order class have been attributed with XmlAttributeAttribute(). This attribute instructs the .NET XML Serializer to serialize these fields as XML attributes as opposed to XML elements. Code block 6 shows the type definition for this class as it will appear in the Types element of the WSDL file, and Code block 7 depicts the SOAP request and SOAP response messages for the PlaceOrder WebMethod.

Code block 6. Type definition for the Order class with the public fields described as XML attributes

...
<s:schema elementFormDefault="qualified" targetNamespace="http://KeithPij.org/WSLibrary/DataTypes">
    <s:complexType name="Order">
        <s:attribute name="OrderID" type="s:long" use="required" />
        <s:attribute name="ProductID" type="s:long" use="required" />
        <s:attribute name="ProductName" type="s:string" />
        <s:attribute name="Quantity" type="s:int" use="required" />
        <s:attribute name="UnitPrice" type="s:decimal" use="required" />
        <s:attribute name="OrderDate" type="s:dateTime" use="required" />
    </s:complexType>
    ...
    ...
</s:schema>

Code block 7. PlaceOrder SOAP request and response messages with the Order object's fields serialized as XML attributes

<!-- Request Before Deserialization 10/17/2003 8:48:59 PM-->
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap=https://schemas.xmlsoap.org/soap/envelope/
                          xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
                          xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <PlaceOrder xmlns="http://KeithPij.org/WSLibrary/MyStore">
            <objOrder OrderID="0" ProductID="1212" ProductName="MSDN Universal" Quantity="10" UnitPrice="1000"
                             OrderDate="2003-10-17T00:00:00.0000000-04:00" />
        </PlaceOrder>
    </soap:Body>

</soap:Envelope>

<!-- Response After Serialization 10/17/2003 8:48:59 PM-->
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap=https://schemas.xmlsoap.org/soap/envelope/
                           xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
                           xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <PlaceOrderResponse xmlns="http://KeithPij.org/WSLibrary/MyStore">
            <PlaceOrderResult OrderID="1234" Status="Pending" />
        </PlaceOrderResponse>
    </soap:Body>

</soap:Envelope>

If this attribute is not specified, then the default behavior of the .NET XML Serializer is to serialize public fields and properties as XML elements. Consequently, the Order type definition of Code Block 2 describes the Order class's fields as XML elements. If you wish to be explicit, there is an XMLElementAttribute() attribute that is used in the same way as the XmlAttributeAttribute attribute. In order to allow for a side-by-side comparison, Code block 8 illustrates a SOAP request and a SOAP response for the PlaceOrder WebMethod with the Order fields serialized as XML elements.

Code block 8. PlaceOrder SOAP request and response messages with the Order object's fields serialized as XML elements

<!-- Request Before Deserialization 10/17/2003 8:22:19 PM-->
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap=https://schemas.xmlsoap.org/soap/envelope/
                           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                           xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <PlaceOrder xmlns="http://KeithPij.org/WSLibrary/MyStore">
            <objOrder>
                <OrderID xmlns="http://KeithPij.org/WSLibrary/DataTypes">0</OrderID>
                <ProductID xmlns="http://KeithPij.org/WSLibrary/DataTypes">1212</ProductID>
                <ProductName xmlns="http://KeithPij.org/WSLibrary/DataTypes">MSDN Universal</ProductName>
                <Quantity xmlns="http://KeithPij.org/WSLibrary/DataTypes">10</Quantity>
                <UnitPrice xmlns="http://KeithPij.org/WSLibrary/DataTypes">1000</UnitPrice>
                <OrderDate xmlns="http://KeithPij.org/WSLibrary/DataTypes">
                    2003-10-17T00:00:00.0000000-04:00
                </OrderDate>
            </objOrder>
        </PlaceOrder>
    </soap:Body>

</soap:Envelope>

<!-- Response After Serialization 10/17/2003 8:22:19 PM-->
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap=https://schemas.xmlsoap.org/soap/envelope/
                           xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
                           xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>
        <PlaceOrderResponse xmlns="http://KeithPij.org/WSLibrary/MyStore">
            <PlaceOrderResult>
                <OrderID xmlns="http://KeithPij.org/WSLibrary/DataTypes">1234</OrderID>
                <Status xmlns="http://KeithPij.org/WSLibrary/DataTypes">Pending</Status>
            </PlaceOrderResult>
        </PlaceOrderResponse>
    </soap:Body>

</soap:Envelope>

There is one additional useful attribute that is illustrated in Code block 5. The XmlIgnoreAttribute() attribute can be used on public fields and properties that are needed only by server-side code. For example, the Order class in Code block 5 has a field named InternalNotes that is decorated with the XmlIgnoreAttribute attribute. When the XmlIgnoreAttribute() attribute is used on a field, then no description of the field will be placed in the WSDL file. When the object is serialized, any information in this field will be ignored.

Messages

The WSDL 1.1 specification states the following about the message elements of WSDL files:

"Messages consist of one or more logical parts. Each part is associated with a type from some type system using a message-typing attribute."

Putting this in terms of software developed for application components, we can say that each message element will describe either the input message or the return message of a WebMethod. For example, the input and output messages of our PlaceOrder WebMethod are described by the two message elements, shown in Code block 9, named PlaceOrderSoapIn and PlaceOrderSoapOut, respectively.

Code block 9. Message elements for the PlaceOrder WebMethod

  <message name="PlaceOrderSoapIn">
    <part name="parameters" element="s0:PlaceOrder" />
  </message>

  <message name="PlaceOrderSoapOut">
    <part name="parameters" element="s0:PlaceOrderResponse" />
  </message>

The first thing to notice is that each message element is a distinct element within the root element of the WSDL file. In other words, the various messages of a Web service are not encapsulated within a higher-level element, as was the case with the type definitions and element declarations within the Types element. This is unfortunate. For the purposes of viewing a WSDL file within an XML editor, it would be nice to have all message elements encapsulated within a single parent element so that message information could be easily collapsed while viewing other portions of the WSDL.

Next, notice that the name of each element (PlaceOrderSoapIn and PlaceOrderSoapOut) is a name that is generated by the .NET Framework based on the name of the WebMethod, the protocol used, and the direction of the message. Consequently, the message name for a SOAP request to the PlaceOrder WebMethod becomes PlaceOrderSoapIn. The message corresponding to the response of the same WebMethod is PlaceOrderSoapOut. This convention insures that all message names within a WSDL file are unique even if multiple protocols are supported, such as HTTPPost and HTTPGet.

The final and most important piece of information that is specified within a message element is a pointer to an element declaration within the Types element. From the perspective of a Web service consumer, this request message element declaration specifies how to format the SOAP request, and the response message element declaration specifies how to interpret the reply from the Web service. For example, look at the message for a SOAP request to the PlaceOrder method (PlaceOrderSoapIn). The part element within this message element points to an element declaration named s0:PlaceOrder, which can be found in the Types element that was described in the previous section. This PlaceOrder element declaration describes all input parameters needed and how to properly serialize an XML request message that passes the desired parameter values to the PlaceOrder WebMethod. Looking at the PlaceOrderSoapOut message, we see that it points to the PlaceOrderResponse element declaration that allows the Web service client to properly deserialize the Soap response.

When we format the parameters and return values of our WebMethods using the technique described above, we say that we are using Literal parameter formatting. This is not the only way to format parameters for SOAP requests and SOAP responses, but it is the best way and it is the way mandated by the WS-I Basic Profile. The other technique for formatting parameters is known as Encoded; this technique was introduced in the early days of Web service standards. Today, most Web services use literal element declarations to serialize and deserialize SOAP requests and SOAP responses. Literal element declarations are a technique introduced by the WSDL 1.1 specification.

If needed, the WSDL specification allows for messages to be broken up into multiple "parts"; however, when using the Document SOAP style and the Literal parameter style, the .NET Framework will generate messages with a single part named parameters. If Soap Headers are used, then an additional message element will be created for each request and response that supports a header. The part name will be based on the class used to represent the header. I will not be covering SOAP Headers in this article. However, if these details interest you, then the code download contains a version of the MyStore Web service (MyStoreSH) that implements SOAP Headers to help you investigate this further. Another version of the MyStore Web service (MyStoreRPC) implements the RPC SOAP style with the Encoded parameter style.

Controlling Messages with .NET Attributes

The .NET Framework allows us to control the message names PlaceOrderSoapIn and PlaceOrderSoapOut with the WebMethod attribute. To control the message name of a WebMethod, we will need to use the MessageName property of the WebMethod attribute as depicted in Code block 10, which illustrates a Web service with an overloaded PlaceOrder function.

Code block 10. Use of the WebMethod attribute to allow for overloaded functions

    <WebMethod(MessageName:="PlaceOrder1")> _
    Public Function PlaceOrder(ByVal objOrder As Order) As Confirm

        Dim objConfirm As New Confirm
        objConfirm.OrderID = 1234
        objConfirm.Status = "Pending"
        Return objConfirm

    End Function

    <WebMethod(MessageName:="PlaceOrder2")> _
    Public Function PlaceOrder(ByVal nProductID As Integer) As Confirm

        Dim objConfirm As New Confirm
        objConfirm.OrderID = 1234
        objConfirm.Status = "Pending"
        Return objConfirm

    End Function

ASP.NET will use our message name as a starting point for constructing the actual message names that appear in the resultant WSDL file. As you can see in Code block 11, monikers indicating protocol and direction will be concatenated to our specified message name so that the developer does not have to specify a message name for the request and response. Also, this ensures uniqueness when multiple protocols are supported by a Web service. Specifying a message name with the WebMethod attribute is mandatory if you have a Web service with an overloaded function.

Code block 11. Message elements for the overloaded PlaceOrder functions of Code Block 10

  <message name="PlaceOrder1SoapIn">
    <part name="parameters" element="s0:PlaceOrder1" />
  </message>

  <message name="PlaceOrder1SoapOut">
    <part name="parameters" element="s0:PlaceOrder1Response" />
  </message>

  <message name="PlaceOrder2SoapIn">
    <part name="parameters" element="s0:PlaceOrder2" />
  </message>

  <message name="PlaceOrder2SoapOut">
    <part name="parameters" element="s0:PlaceOrder2Response" />
  </message>

Next we can specify the name of the element declarations within the types section of our WSDL that will be used for processing requests and responses to the PlaceOrder WebMethod. Reviewing the details in Code block 2, we see that the Types section contains an element declaration named PlaceOrder and PlaceOrderResponse. The PlaceOrderSoapIn and the PlaceOrderSoapOut message elements shown in Code block 9 point to these element declarations via their "part" sub-element. These element declarations describe how the input message and the return message for the PlaceOrder WebMethod will be serialized and deserialized. If you wish to specify the name of these element declarations, you can use the RequestElementName and the ResponseElementName properties of the SoapDocumentMethod attribute. (The SoapRpcMethod has the same properties; however, we will limit ourselves to demonstrating only SoapDocument methods for the sake of brevity.) Code block 12 illustrates this technique. The MyStoreLD version of our Web service in the code download uses these attributes.

Code block 12. The SoapDocumentMethod attribute used to explicitly name the element declarations for input parameters and for the return value

    <WebMethod(MessageName:="CheckOrder"), _
    SoapDocumentMethod( _
                        RequestElementName:="CheckOrderParameter", _
                        ResponseElementName:="CheckOrderResponse")> _
    Public Function CheckOrder(ByVal nOrderID As Integer) As Confirm

        Dim objConfirm As New Confirm
        objConfirm.OrderID = nOrderID
        objConfirm.Status = "Shipped"
        Return objConfirm

    End Function

Port Types

The WSDL 1.1 Specification has the following to say about portTypes:

"A port type is a named set of abstract operations and the abstract messages involved."

The purpose of a portType element is to group related messages together to form operations, and to group together related operations to form abstract ports known as portTypes. Conceptually, portTypes are very similar to interfaces that group together related methods, properties, fields, and events. Code block 13 shows the portType that ASP.NET generates for the MyStore Web service. Here the name of the portType is MyStoreSoap. This name is a generated name that by default is based on the Web service class name and the protocol.

Code block 13. The portType element for the MyStore Web service

  <portType name="MyStoreSoap">

    <operation name="PlaceOrder">
      <input message="s0:PlaceOrderSoapIn" />
      <output message="s0:PlaceOrderSoapOut" />
    </operation>

    <operation name="CheckOrder">
      <input message="s0:CheckOrderSoapIn" />
      <output message="s0:CheckOrderSoapOut" />
    </operation>

  </portType>

In Code block 13 we see that the MyStoreSoap portType has two operations: the PlaceOrder operation (or WebMethod) and the CheckOrder operation. The name of each operation is simply the name of the corresponding WebMethod. Every portType element will have one operation element for every WebMethod that belongs to the portType. The final piece of information that is specified in the portType element is the input and output messages used for each operation. In Code block 13 we see that for the PlaceOrder WebMethod the input and output elements have message attributes that point to the PlaceOrderSoapIn message element and the PlaceOrderSoapOut message element.

We can now see that our Web service is starting to take shape. We have a logical grouping of operations known as a portType; each operation that is enumerated within our portType specifies an input message and an output message; every message is uniquely named and described in the "Message" section of our WSDL; and every message has a parameter part that points to an element declaration within the Types element.

It is also interesting to note that portTypes are intended to be abstract; in other words, they make no reference to concrete implementation details such as the transport, protocol, or address of the Web service. If you were to enable one of the other protocols such as HTTPGet or HTTPPost, you would see that you get a different suite of messages and a portType for every protocol supported. The additional message elements and portTypes are described in a way that does not specify the transport protocol or the address. Since a unique portType and a unique set of messages are needed for each protocol, the true abstract nature of these message and portType elements is questionable. (We must not consider the suffixes concatenated to the ends of our various element names to be a specification of protocol; this technique is used by ASP.NET to insure that element names are unique where necessary.) In theory, portTypes are supposed to be abstract; in reality, this is rarely the case because oftentimes the protocol of a WebMethod will dictate how it is described within the WSDL. As an example, let us consider the HTTPGet and HTTPPost protocols. These protocols have no capabilities for making requests to WebMethods that require complex data types. In other words, HTTPGet and HTTPPost only support WebMethods that accept and return simple data types. As a result of this limitation, a separate portType is required so that WebMethods that require complex types either as input or output can be omitted in the list of operations. Furthermore, the message elements cannot be reused because the SOAP protocol often has message elements that point back to the Types element to describe input and output messages, while HTTPGet and HTTPPost merely define the data types within the message element itself. (As a quick exercise in these concepts, go into the web.config file of the Web service being investigated in this article and turn on the HTTPGet and HTTPPost formats as shown below.

      <protocols>
         <add name = "HttpPost"/>
         <add name = "HttpGet"/>
      </protocols>

Once this is done, view the dynamically generated WSDL and check out the additional message, portType, and binding elements.

Bindings

The WSDL specification states:

"A binding defines message format and protocol details for operations and messages defined by a particular portType. There may be any number of bindings for a given portType."

This is interesting because we are now free to specify what is referred to as concrete information about our Web service. We are now free to specify the transport (examples: HTTP, or SMTP) and protocol (examples: SOAP, HTTPGet, or HTTPPost). If your Web service supports multiple transports and multiple protocols, then you will have to provide a binding element for every combination of transport/protocol that is supported. (This is why we turned off the HTTPPost and HTTPGet protocols. The additional message, portType, and binding elements that ASP.NET generates for these little used protocols make the resultant WSDL appear complicated.)

The MyStore Web service that we are investigating in this article will utilize HTTP as the transport and SOAP as the protocol. Code block 14 shows the binding element for the MyStore Web service that specifies this transport/protocol selection. The first thing you will notice in the binding definition is that the binding element has been assigned the name MyStoreSoap and the type attribute points us back to the portType to be bound, which is also named MyStoreSoap. Since the binding element has been given the same name as the portType being bound, the implication is that there is a one-to-one relationship between portTypes and bindings. This appears to be a violation of the WSDL 1.1 specification that states that a portType may have multiple bindings. However, since ASP.NET only supports the HTTP transport and does not create abstract portTypes that can be reused across multiple bindings for reasons described above, a one-to-one relationship between portTypes and bindings is acceptable. By using the same element names for bindings, ports, and portTypes, the relationships between these three WSDL elements are much easier to see. As a final note on this subject, in the next version of the .NET Framework (code-named Whidbey) you will be able to create SOAP 1.1 and SOAP 1.2 bindings for the same portType.

Within the MyStoreSoap binding element there is a SOAP binding element that contains a transport attribute where the http transport is specified. The style attribute specifies the default SOAP style for all the operations within the portType being bound. In this particular example, the option that is specified is Document style format. The other SOAP style option is Rpc.

Once the transport and default SOAP style are specified, the next thing specified at the operation level is the soapAction for each operation within the portType being bound. The soap action is not to be confused with an address; the soap action is merely a mechanism for locating the correct WebMethod within the Web service being defined. Also specified at the operation level is the SOAP style, even though a default option was previously specified. Finally, the parameter style for the input message and the output message is specified. In the case of the PlaceOrder WebMethod, the parameter style is literal. The other option for the parameter style is Encoded. Typically the Encoded parameter style goes with the RPC SOAP style and the Literal parameter style goes with the Document SOAP style.

For more information on the different SOAP styles and parameter styles, see the Related Articles section.

Code block 14. The Binding element for the MyStore Web service

  <binding name="MyStoreSoap" type="s0:MyStoreSoap">

    <soap:binding transport="https://schemas.xmlsoap.org/soap/http" style="document" />

    <operation name="PlaceOrder">
      <soap:operation soapAction="http://KeithPij.org/WSLibrary/MyStore/PlaceOrder" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>

    <operation name="CheckOrder">
      <soap:operation soapAction="http://KeithPij.org/WSLibrary/MyStore/CheckOrder" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>

  </binding>

Ports

The WSDL 1.1 specification describes ports as follows:

"A port defines an individual endpoint by specifying a single address for a binding."

Code block 15 depicts the service element for the MyStore Web service. The port element is actually a sub-element located under the Service element. (The Service element will be discussed in the next section.) The port is given a unique name within the Service element, which in this case is MyStoreSoap and is linked to a previously defined binding, also named MyStoreSoap, via the binding attribute. Finally and most importantly, the port element contains a SOAP address element that specifies the physical address of this port. If you are using the HTTP transport, then this address is a URL. If the transport specified in the Bindings element is SMTP, then this address would be an e-mail address. All requests transmitted to this address must use the transport and protocol specified in the MyStoreSoap binding element.

Code block 15. The Service element for the MyStore Web service

  <service name="MyStore">

    <documentation>Demonstration of a Web Service</documentation>

    <port name="MyStoreSoap" binding="s0:MyStoreSoap">
      <soap:address location="https://localhost/WSLibrary/MyStore.asmx" />
    </port>

  </service>

Controlling Port Types with .NET Attributes

Let's look at how we can use .NET attribution to control portTypes. I have held off describing these techniques until now because what we do to our portType will also affect bindings and ports. As we have seen above, ASP.NET by default places all of a Web service's WebMethods into a single portType named after the Web service class and the protocol specified (MyStoreSoap). However, the WebServiceBinding attribute allows us to specify a portType name of our choice. (The name of the WebServiceBinding attribute is unfortunate. At first glance one would expect it to control the binding element, but it does not control bindings, it controls portTypes.) This attribute also allows us to specify multiple portTypes within a given Web service class. Code block 16 shows the MyStore Web service class decorated with two instances of the WebServiceBinding attribute that place the PlaceOrder WebMethod into a portType named Sales and the CheckOrder WebMethod into a portType named Support. The MyStoreMB version of our Web service in the code download uses the WebServiceBinding attribute as described above.

Code block 16. The WebServiceBinding attribute

Imports System.Web.Services
Imports System.Web.Services.Protocols

<WebService(Name:="MyStoreMB", [Namespace]:="http://KeithPij.org/WSLibrary/MyStoreMB", 
   Description:="MyStore web service with multiple bindings"), _
WebServiceBinding("Sales", "http://KeithhPij.org/WSLibrary/MyStoreMB/Sales"), _
WebServiceBinding("Support", "http://KeithhPij.org/WSLibrary/MyStoreMB/Support"), _
SoapDocumentService()> _
Public Class MyStoreMB
    Inherits System.Web.Services.WebService

    <WebMethod(), _
    SoapDocumentMethod(Binding:="Sales")> _
    Public Function PlaceOrder(ByVal objOrder As Order) As Confirm
        Dim objConfirm As New Confirm()
        objConfirm.OrderID = 1234
        objConfirm.Status = "Pending"
        Return objConfirm
    End Function

    <WebMethod(), _
    SoapDocumentMethod(Binding:="Support")> _
    Public Function CheckOrder(ByVal nOrderID As Integer) As Confirm
        Dim objConfirm As New Confirm()
        objConfirm.OrderID = nOrderID
        objConfirm.Status = "Shipped"
        Return objConfirm
    End Function

End Class

Notice that the binding property of the SoapDocumentMethod attribute must be used in conjunction with the WebServiceBinding attribute. Specifically, the binding property of the SoapDocumentMethod attribute specifies which portType the WebMethod will belong to. The SoapRPCMethod also has a binding property for specifying bindings for WebMethods that support RPC/Encoded Soap messages.

Let's take a look at what the WebServiceBinding attribute does to our WSDL file. Code block 17 shows the generated WSDL for the Web service shown in Code block 16.

Code block 17. Generated WSDL for the MyStoreMB Web service with multiple bindings

<?xml version="1.0" encoding="utf-8"?>
<definitions 

xmlns:http="https://schemas.xmlsoap.org/wsdl/http/" 
xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/" 
xmlns:i1="http://KeithPij.org/WSLibrary/DataTypes" 
xmlns:s="http://www.w3.org/2001/XMLSchema" 
xmlns:i4="http://KeithhPij.org/WSLibrary/MyStoreMB/Support" 
xmlns:i3="http://KeithhPij.org/WSLibrary/MyStoreMB/Sales" 
   xmlns:soapenc="https://schemas.xmlsoap.org/soap/encoding/" 
xmlns:tns="http://KeithPij.org/WSLibrary/MyStoreMB" 
xmlns:tm="https://microsoft.com/wsdl/mime/textMatching/" 
   xmlns:mime="https://schemas.xmlsoap.org/wsdl/mime/" 
targetNamespace="http://KeithPij.org/WSLibrary/MyStoreMB" 
xmlns="https://schemas.xmlsoap.org/wsdl/">

  <import namespace="http://KeithhPij.org/WSLibrary/MyStoreMB/Sales" 
     location="https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema1" />  <import namespace="http://KeithPij.org/WSLibrary/DataTypes" 
     location="https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema2" />  <import namespace="http://KeithhPij.org/WSLibrary/MyStoreMB/Support" 
     location="https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema3" />  <import namespace="http://KeithhPij.org/WSLibrary/MyStoreMB/Sales" 
     location="https://localhost/WSLibrary/MyStoreMB.asmx?wsdl=wsdl1" />  <import namespace="http://KeithhPij.org/WSLibrary/MyStoreMB/Support" 
     location="https://localhost/WSLibrary/MyStoreMB.asmx?wsdl=wsdl2" />
  <types />

  <service name="MyStoreMB">

    <documentation>MyStore web service with multiple bindings</documentation>

    <port name="Sales" binding="i3:Sales">
      <soap:address location="https://localhost/WSLibrary/MyStoreMB.asmx" />
    </port>

    <port name="Support" binding="i4:Support">
      <soap:address location="https://localhost/WSLibrary/MyStoreMB.asmx" />
    </port>

  </service>

</definitions>

At first glance, this WSDL file looks as if it only has a Service element and is missing all the other elements. The truth is that when the WebServiceBinding attribute is used, ASP.NET will package our WSDL into logical components. This WSDL is still intact, and in the end everything is put together as expected via the import statements located right after the namespace declarations. We can mine out the various portions of our WSDL by navigating to the locations specified in the import statements. Our MyStore Web service is organized as follows in Table 3.

Table 3. MyStore Web service organization

URL Description
https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema1 Element declarations for messages sent to and from the PlaceOrder WebMethod
https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema2 Type definitions for the Order class and the Confirm class
https://localhost/WSLibrary/MyStoreMB.asmx?schema=schema3 Element declarations for messages sent to and from the CheckOrder WebMethod
https://localhost/WSLibrary/MyStoreMB.asmx?WSDL=wsdl1 Sales binding, Sales portType, and all messages associated with the PlaceOrder WebMethod
https://localhost/WSLibrary/MyStoreMB.asmx?WSDL=wsdl2 Support binding, Support portType, and all messages associated with the CheckOrder WebMethod

The WSDL file is now organized into six different XML components: the five listed above and the original file of Code block 17. This is definitely overkill for our simple Web service, but if we were working with a Web service that had a large interface, this breakdown would be welcomed. Finally, if we look closely, we will notice that we now have a separate binding element for each portType created above. If we were supporting multiple protocols via our web.config file (HttpGet and HttpPut), we would see that every portType gets a unique binding for every supported protocol. In other words, WSDL bindings are a cross-product of portTypes and protocols.

Services

From the WSDL 1.1 specification:

"A Service groups a set of related ports together."

Code block 15 depicts the service element for the MyStore Web service. The name of the service element is MyStore; the name of the service element by default is the name of the class that is defined in your ASMX file.

If this Web service contained multiple ports, then all ports would be defined within this single service element. Code block 17 shows the Service element for the MyStoreMB Web service that was discussed in the last section. This Web service has two ports named Sales and Support. The service element may also contain an optional Documentation sub-element that provides a short description about the Web service. This element is for informational purposes only and does not affect the operations of the Web service in any way. The bottom line is that the sole purpose of the service element is to group together related ports. It is the simplest of all the constituent elements of a WSDL file.

Controlling Services with .NET Attributes

.NET developers can override the default name of the service element by using the Name property of the WebService attribute that is located in the .NET Framework System.Web.Services namespace. The use of this code attribute is illustrated in Code block 1. This attribute is a class-level attribute that also has a Description property for specifying the information that is located in the Documentation element described in the previous section.

Most importantly, the WebService attribute should be used to specify the namespace in which the Web service will be located. This is done via the Namespace property of the WebService attribute. Namespaces in Web services serve the same purpose as namespaces within our .NET assemblies. Namespaces group together logically related elements and provide a mechanism for ensuring name uniqueness. When you add a new ASMX file to an ASP.NET Web service project, the default namespace assigned by Visual Studio .NET 2003 is created by concatenating the project name and service name to the string http://tempuri.org (example: http://tempuri.org/WebService1/Service1). It is a best practice to override this default behavior and assign a namespace of your own.

Conclusion

A WSDL file fully describes your Web service, and it is the only piece of information that a consumer of your Web service will have to guide them in calling your Web service correctly. It will not matter how well the underlying code of your Web service is implemented if your WSDL file is sloppy or you are unable to describe this file to a potential customer. Furthermore, understanding how a WSDL file describes your Web service is key to understanding how XML Web services work in general.

Background Information

Simple Object Access Protocol (SOAP) 1.1

Web Services Description Language (WSDL) 1.1

How ASP.NET Web Services Work

Understanding SOAP

Understanding XML Schema

Understanding XML Namespaces

RPC/Literal and Freedom of Choice