.NET and WSDL


Now that you’ve had a brief introduction to WSDL via the SimpleRecordFinder service, it’s time to talk about the options in .NET for modifying the WSDL that’s automatically generated for a Web service. All of the options that we’re going to look at here are implemented in the complete Web service, RecordFinder, for this chapter at http://localhost/wscr/03/recordfinder.asmx.

We’ll start by looking at how to control what protocols a Web service supports and then look at the different SOAP message formats, one-way methods, and method overloading, among other topics.

Supported Protocols

The SimpleRecordFinder service supports the three protocols you’d expect— SOAP, HTTP-GET, and HTTP-POST. This is not always ideal. On production servers, we tend to turn off HTTP-GET and HTTP-POST and allow only SOAP to access the Web service. Indeed, the tools provided with .NET and Microsoft Visual Studio .NET use only the SOAP protocol when constructing their proxies, so disabling the HTTP-GET and HTTP-POST protocols makes sense.

You can do this easily by adding a few entries to the web.config file for the application hosting the Web service, as we have done for the RecordFinder service:

<configuration>   <system.web>     <webServices>       <protocols>         <remove name="HttpGet" />         <remove name="HttpPost" />       </protocols>     </webServices>   </system.web> </configuration>

If you look at the WSDL for the RecordFinder service, you’ll see that it does indeed support only the SOAP protocol and that all the details for HTTP- GET and HTTP-POST have been removed.

You can also turn off the use of the SOAP protocol. However, although we’ve shown the use of the HTTP-GET and HTTP-POST protocols, the remainder of the book will assume the use of only the SOAP protocol when accessing Web services because higher-level features such as WS-Security require the use of the SOAP protocol.

You can also turn off the automatic documentation of Web services by specifying <remove name="Documentation" />. In practice this might seem like a good idea, but that’s not necessarily so—if you turn off the documentation, you also turn off the Web service’s ability to generate its own WSDL. You can get around this limitation by viewing the WSDL for the Web service before you turn off the documentation feature and then saving it as a static WSDL file. This file will contain the correct WSDL for the Web service, and the <soap:address> element will point at the correct .asmx file on the server. Once you turn off the documentation feature, you can still access the Web service by using the static WSDL file.

Note

One notable feature of .NET is that if you’re accessing a Web service that’s hosted on the same machine as the client, you can still test the Web service using the HTTP-POST protocol even if you’ve turned off support for that protocol in the configuration file. The WSDL for the protocol isn’t created, but you can test the Web service to see that it’s working correctly. If you access the same service using a different machine, the ability to test the Web service disappears.

SOAP Message Formats

As we pointed out earlier, you have five options for the message format you use with the SOAP protocol. So far we’ve only looked at the default options in .NET for the message format—document-oriented, no encoding, with wrapped parameters—and this is the WSDL we’ve looked at so far. However, four other message formats are available:

  • Document-oriented, no encoding, with bare parameters

  • Document-oriented, encoded, with wrapped parameters

  • Document-oriented, encoded, with bare parameters

  • RPC-oriented (which is encoded, with bare parameters)

To show the changes that are made to the WSDL that’s generated for these different formats, we’ve created five new Web services on the server. These all implement exactly the same code as SimpleRecordFinder and differ only in the message format that they require. The first of these, RecordFinder1, is a simple copy of the SimpleRecordFinder service that we’ve already looked at and has the default options for the message format. We’ll introduce the remaining four services, RecordFinder2 through RecordFinder5, as we look at the options that follow.

You can change the format of SOAP messages quite easily in .NET with the use of attributes. For example, you can control whether an entire Web service is RPC-oriented or document-oriented by applying the SoapRpcService or SoapDocumentService attribute, respectively, to the class. Within a service, you can control whether an individual method is RPC-oriented or document-oriented by using the SoapRpcMethod or SoapDocumentMethod attribute, respectively. If you apply an attribute to the method, it overwrites the attribute applied to the class; you can use message formats for a method different from the one that applies to the class as a whole.

As you’ll soon see, the choice of SOAP message format greatly affects the WSDL that’s generated for a Web service. Note that the message format changes only the details in the <message> and <types> sections of the WSDL document. The values of attributes in the other WSDL elements, such as the style attribute of the <soap:binding> element, change to illustrate the options you’ve chosen but the structure of those elements doesn’t change.

Document-Oriented Messages and Types

Once you specify a document-oriented service or method, you have two further options for how to construct the SOAP messages. The SoapDocumentService and SoapDocumentMethod attributes both support the Use and ParameterStyle parameters.

The Use parameter controls whether the Web service expects encoded messages. It can take one of two values, Encoded or Literal. The default in .NET is literal messaging, but you can easily change this:

[SoapDocumentMethod(Use=SoapBindingUse.Encoded)]

The ParameterStyle parameter controls whether the Web service expects bare or wrapped messages. It can take one of two values, which are, not surprisingly, Bare and Wrapped. As you saw in the previous chapter, with wrapped messaging each element you use is described as an XSD complex type that contains various sequence details, whereas bare messaging maps to an XSD simple type. The default in .NET is wrapped messages, but you can easily change this:

[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]

Literal messages and types

With a literal message, the message parts use the element attribute to point at an element defined in the <types> section of the document. They cannot point directly at an XSD type.

The RecordFinder1 service (which is at http://localhost/wscr/03/soap/ recordfinder1.asmx) uses the document-literal-wrapped message format. The RecordFinder2 service (which is at http://localhost/wscr/03/soap/ recordfinder2.asmx) uses bare parameters rather than the wrapped parameters used by RecordFinder1.

As we saw when we looked at SimpleRecordFinder, the <part> elements in RecordFinder1 are all created with the name parameters and point at an <s:element> defined in the <types> section. The naming of the elements in the <types> section for the document-literal-wrapped message format is as follows:

  • The <s:element> element that wraps the complex type is named after the <operation> it’s being used in, with Response appended if it’s the response from the method.

  • Each wrapper <s:element> eventually contains an <s:element> that defines the data type. The name for incoming types is the name of the parameter; for outgoing types, it’s the name of the <operation>, with Result appended.

RecordFinder2 uses bare parameters, so there’s no need to wrap the elements we’re using inside a complex type. The <part> elements now follow the naming scheme introduced earlier in Table 3-2, and they still reference an <s:element> defined in the <types> section. The element we reference can now reference defined XSD types directly—we do away with the wrapping in a complex type. The <part> element and the <s:element> it references share the same name:

  • For incoming messages, the elements have the same name as the parameter name in code.

  • For outgoing messages, the elements have the same name as the method, with Result appended.

Encoded messages and types

With an encoded message, the message parts use the type attribute to point at a complex type defined in the <types> section of the document or at an intrinsic XSD type. As with literal messaging, what the <part> elements contain depends on whether you’re using wrapped or bare messaging.

As you’ve probably gathered, we now have two new versions of the Web service on the server that show the options we’ve discussed. RecordFinder3 (at http://localhost/wscr/03/soap/RecordFinder3.asmx) uses wrapped parameters; RecordFinder4 (at http://localhost/wscr/03/soap/RecordFinder4.asmx), uses bare parameters.

If you look at the WSDL for RecordFinder3, you’ll see that the <part> elements are all created with a name of parameters and use the type attribute to point at an <xsd:complexType> defined in the <types> section. A complex type is created even if the message part is actually an intrinsic type. The rules used to name the types are the same as those for wrapped parameters when you use the literal message format:

  • The <s:complexType> elements are named after the <operation> they’re being used in, with Response appended if they’re the return from the method.

  • Each <s:complexType> eventually contains an <s:element>. The name, for incoming types, is the name of the parameter; for outgoing types, it’s the name of the <operation>, with Result appended.

If you look at WSDL for RecordFinder4, which uses bare parameters, you can see that it behaves similarly for encoded messages and for literal messages. As with literal messaging, you do away with the need for the containing element, but when you use encoding you can go one step further. Whereas with literal messaging you have to point to an element, with encoded messaging you can point straight to an intrinsic type rather than a defined complex type if you want. You’ll see this if you compare the GetAlbumsForArtistSoapIn message across all four versions of the service.

The naming rules are the same for bare parameters regardless of whether you’re using literal or encoded messaging, but for the encoded message format you no longer have XSD elements that must be named:

  • For incoming messages, the <part> element has the same name as the parameter name in code.

  • For outgoing messages, the <part> element has the name of the method, with Result appended.

RPC-Oriented Messages and Types

The last option for the SOAP message format is to use RPC-oriented messaging. As you saw in Chapter 2, this forces the use of encoding and bare parameters.

The quick-thinking among you will realize that using RPC-oriented messaging is nearly the same as using document-oriented messaging when you encode the message and use bare parameters. If you compare the WSDL generated for the RecordFinder4 service with the WSDL generated for RecordFinder5 (at http://localhost/wscr/03/soap/RecordFinder5.asmx), you’ll see that the WSDL is the same except for the style attributes of the <soap:binding> and <soap:operation> elements.

Other Configuration Options

Now that we’ve covered the options specific to RPC-oriented and document- oriented messages, we’ll look at seven other options that are common to the SoapRpcMethod and SoapDocumentMethod attributes.

Rather than creating a new Web service for every example, as we’ve done so far, we’ll simply add the code to the complete version of the RecordFinder service (which is at http://localhost/wscr/03/RecordFinder.asmx). To avoid any confusion, this service uses the default options in .NET for the message format.

One-Way Methods

As we mentioned, WSDL allows two operation types to be used for exposed methods: request-response and one-way. In .NET, the default operation type is request-response, but you can override this behavior on a method and change it to one-way by using the OneWay parameter and setting its value to true.

Contrary to what you might assume, specifying a method as void is not the same as implementing a one-way method. If you expose a void method as a method of a Web service, you don’t actually create a one-way method—you simply create a method that has a type containing a return parameter that is ultimately defined as not containing any data.

To correctly declare a method as one-way, you must specify the method as one-way in the attribute, and you must also declare the method as void. If you declare it as anything other than void, the code will build correctly but when you attempt to access the Web service, you’ll generate an exception, as shown in Figure 3-2.

click to expand
Figure 3-2: Error returned when a one-way method returns a value

If you look at the RecordFinder service, you’ll see that it has a one-way method called UpdateAlbum. Although it functionally does nothing in this example, in the real world it would be used to update details in the database for a particular album. The signature for the method is as follows:

[WebMethod,SoapDocumentMethod(OneWay=true)] public void UpdateAlbum(string strTitle, string strArtist)

If you now look at the WSDL for the service, you can see that there is no <output> element for either the <portType> or the <binding> elements for the UpdateAlbum method. You can see this by looking at the <portType> details for the service:

<portType name="RecordFinderSoap">   <operation name="UpdateAlbum">     <input message="s0:UpdateAlbumSoapIn" />    </operation> </portType>

Similarly, if you look at the WSDL for the service, you can see that the <binding> element also has only an <input> element.

Changing the SOAP Action

As you saw in the previous chapter, you must provide the SOAP action value when you use the SOAP protocol across HTTP. You’ve already seen that the default value is the operation name appended to the namespace of the Web service. This is certainly adequate for most Web services, but if you need to change it, you can do so by using the action parameter of the SoapDocumentMethod or SoapRpcMethod attribute.

You can add this value to the UpdateAlbum method for RecordFinder service as follows:

[WebMethod,SoapDocumentMethod(OneWay=true, Action="UpdateAlbum")] public void UpdateAlbum(string strTitle, string strArtist)

If you now look at the <operation> element within the <binding> element, you can see that the soapAction attribute has changed:

<operation name="UpdateAlbum">   <soap:operation soapAction="UpdateAlbum" style="document" />     </operation>

Note, however, that as we’ve shown here, .NET allows any string to be used as the value of the SOAP action. This is not strictly correct; the SOAP specification states that this value must be a URI.

Changing the Request and Response Namespaces

Although we declared our Web service to be in a given namespace using the Namespace parameter of the WebService attribute, sometimes you might want the request and responses to individual methods to be in different namespaces. .NET allows you to do this by using the RequestNamespace and ResponseNamespace parameters of the SoapRpcMethod and SoapDocumentMethod attributes.

You can add request and response namespaces to your methods quite easily; the format is the same whether you use the RPC or document version of the attribute, as you’ll see with the new GetArtistForAlbum method that we’ve added to our RecordFinder service:

[WebMethod,     SoapDocumentMethod(         RequestNamespace="http://www.notashop.com/wscr/request",         ResponseNamespace="http://www.notashop.com/wscr/response")] public string GetArtistForAlbum()

This affects the WSDL that’s generated for the service in several ways. As soon as we use different namespaces with the Web service, we immediately have new prefixes added to the <definitions> element. These are all in the same format as the prefix we declared for the namespace for the overall service. Because we added two new namespaces, we now have an s1 and s2 prefix as well as the “standard” s0 prefix. Additional namespaces would be named along the same lines: s3, s4, and so on.

The actual type definitions within the <types> section are also moved into their own schema sections:

<s:schema elementFormDefault="qualified"   targetNamespace="http://www.notashop.com/wscr">    </s:schema> <s:schema elementFormDefault="qualified"   targetNamespace="http://www.notashop.com/wscr/request">    </s:schema> <s:schema elementFormDefault="qualified"   targetNamespace="http://www.notashop.com/wscr/response">    </s:schema>

The actual definitions of the types used by the Web service do not change at all. We simply refer to the types in the <part> elements using their new prefix rather than the old prefix.

For encoded messages, you also add the namespace to the <soap:body> elements in the namespace attribute of the respective <input> and <output> messages for the <binding>.

Changing the Request and Response Element Names

Having changed the namespace of the requests and responses to a method, we can, if we’re using wrapped parameters, change the name of an element or complex type that’s defined in the <types> section by using the RequestElementName and ResponseElementName parameters.

Even if we change a name in the <types> section, this action has no effect on the proxy that .NET generates because the names of the parameters are actually the names of the <s:element> elements that specify the data types and not the names of the elements or complex types referenced by the <part> element.

If you’re using bare parameters, you can’t change the name of the element or complex type in the <types> section because the names in this case contain the information used to generate the proxy at the client. If you try to change the names in this case, the values are ignored and the types are created with the default names.

We can change easily the element names for our GetArtistForAlbum method:

[WebMethod,     SoapDocumentMethod(       RequestNamespace="http://www.notashop.com/wscr/request",       ResponseNamespace="http://www.notashop.com/wscr/response"        RequestElementName="GAFA_Request",       ResponseElementName="GAFA_Response")] public string GetArtistForAlbum(string strAlbum)

If you now look at the types that are generated, you’ll see that they use the names we specified and that the <part> elements point to the correct element:

<message name="GetArtistForAlbumSoapIn">     <part name="parameters" element="s1:GAFA_Request" />  </message> <message name="GetArtistForAlbumSoapOut">     <part name="parameters" element="s2:GAFA_Response" />  </message> 

Specifying Documentation Elements

One of the first things we looked at in the WSDL specification was the ability to add basic documentation to all elements within a WSDL file. You can add documentation in .NET, but only to methods and classes, which in WSDL equate to the <portType> and <service> elements, respectively.

To add a <documentation> element, you simply specify a string as the value of the Description parameter to the WebService or WebMethod attribute:

[WebService(Namespace="http://www.notashop.com/wscr",     Description="RecordFinder service")] public class RecordFinder : System.Web.Services.WebService [WebMethod(Description="Search for albums by a given artist")] public string[] GetAlbumsForArtist(string strArtist)

We’ve similarly added the Description parameter to the three methods that we’ve looked at so far. If you now look at the WSDL that is generated, you’ll see the added <documentation> elements:

<portType name="RecordFinderSoap">   <operation name="UpdateAlbum">     <documentation>Update the artist for a given album</documentation>        </operation>   <operation name="GetArtistForAlbum">     <documentation>Get the artist for a given album</documentation>        </operation>   <operation name="GetAlbumsForArtist">     <documentation>Search for albums by a given artist</documentation>        </operation> </portType>  <service name="RecordFinder">   <documentation>RecordFinder service</documentation>    </service>

If you look at the service in Microsoft Internet Explorer, you’ll see that the <documentation> elements are also picked up by .NET. (We’ll shortly look at the LogMessage and LogMessageWithName methods.) This process is shown in Figure 3-3.

click to expand
Figure 3-3: Documentation elements are pulled from the WSDL file.

SOAP Headers

As you saw in Chapter 2, you can quite easily add SOAP headers to a Web service you’ve created. We briefly mentioned how the WSDL is altered when you use SOAP headers. We’ll now explain this further.

We’ve added a SOAP header to the UpdateAlbum method of the RecordFinder service, and we’ll pass security information in this header. This is very insecure because we’re sending username and password details in the open. We’ll see in Chapter 15 that there are infinitely better ways to validate users. But for our purposes here, it’s fine.

As you know, to add a SOAP header you need a class to pass as the header and a public instance of that class. You must also add an attribute to tell the method that it needs a header. Here’s the code we’ve added to the RecordFinder service:

public class UserHeader : SoapHeader {     public string username;     public string password; } public UserHeader authHeader; [WebMethod(Description="Update the artist for a given album"),     SoapDocumentMethod(OneWay=true,Action="UpdateAlbum"),     SoapHeader("authHeader",Direction=SoapHeaderDirection.In)]

Chapter 2 discussed how to pass this to the Web service, the serialization that takes place, and everything else that occurs when you use SOAP headers, so we won’t repeat all that here. What we’re interested in here is the WSDL that’s generated.

As you’ve seen, .NET serializes classes correctly and creates an <s:element> or an <s:complexType> for the class, depending on whether you’re using wrapped or bare parameters. RecordFinder uses wrapped parameters, so the UserHeader class becomes an element and a complex type in the <types> section:

<s:element name="UserHeader" type="s0:UserHeader"> <s:complexType name="UserHeader">   <s:sequence>     <s:element minOccurs="0" maxOccurs="1" name="username"        type="s:string" />     <s:element minOccurs="0" maxOccurs="1" name="password"        type="s:string" />   </s:sequence> </s:complexType>

We now have the necessary type defined for the header, so it’s time to look at the WSDL that’s actually generated for the <soap:header> element, as well as the required <message> element that maps to the element we created for the SOAP header class.

Because this header is specified with a direction of SoapHeaderDirection.In, the <soap:header> element is added to the <input> element of the UpdateAlbum operation. The <soap:header> element maps to the correct <message> and <part> elements by using the message and part attributes, as you can see for the RecordFinder service:

<soap:header   message="s0:UpdateAlbumUserHeader"   part="UserHeader"   use="literal" /> 

From the <soap:header> element you can see that we’re referring to a specific <message> element and a specific <part> element within it. There’s nothing special about the <message> element we use with a SOAP header. It’s identical to the <message> elements we’ve been looking at:

<message name="UpdateAlbumUserHeader">  <part name="UserHeader" element="s0:UserHeader" />  </message>

That’s it. We’ve specified that we require a SOAP header for the UpdateAlbum method, and .NET generates all of the required WSDL. This is not the end of the story, however. We have two more issues to explore.

Although the UpdateAlbum method has a header, it is defined as being an “in” header only, so the <soap:header> element is added to the <input> element. As you’ll remember, you can also specify out and inout as directions for the header, and this changes where the <soap:header> element is added. For a direction of out, the header is added to the <output> element; for a direction of inout, the header is added to both the <input> and <output> elements and both <soap:header> elements point at the same <message> element and <part> element.

The second issue is that when you use SOAP headers, they follow the same message format rules as everything else concerned with the SOAP protocol. If you use encoding, the <soap:header> element shows the encoding details. If you use bare parameters instead of wrapped parameters, you use <s:complexType> elements instead of <s:element> elements.

Method Overloading

One topic that’s inadequately defined in the current version of the WSDL specification is method overloading. This is being looked at in the next draft of the specification. In fact, the current specification neither allows overloading nor outlaws it. All of the languages supported by .NET allow method overloading, so it follows that Web services developed in .NET allow overloaded methods to be exposed.

To overload methods in .NET, you simply define two methods with the same name and a different parameter list. We’ll add two overloaded methods to our RecordFinder service:

[WebMethod(), SoapDocumentMethod(OneWay=true)] public void LogMessage(string strMessage) {     // logging code would go here }      [WebMethod(), SoapDocumentMethod(OneWay=true)] public void LogMessage(string strMessage, string strUserName) {     // logging code would go here }

This isn’t all. This code builds correctly if you try to browse to the Web service, but an InvalidOperationException is generated, as you can see in Figure 3-4.

click to expand
Figure 3-4: Overloading methods isn’t as simple as it could be.

The error message we received provides a clue about what went wrong. WSDL <message> elements must have unique names and must follow the naming rules we’ve talked about. .NET is trying to create two <message> elements with the same name.

For overloading to work correctly, you need to specify a different message name for each of the overloaded methods exposed to the Web service. The error we received offers a clue to the solution. We must use the MessageName parameter of the WebMethod attribute to change the message name used for one of the overloaded methods:

[WebMethod(MessageName="LogMessageWithName"),     SoapDocumentMethod(OneWay=true) ] public void LogMessage(string strMessage, string strUserName)

If you now browse to the Web service, you’ll see that the value we specified for the MessageName is used in place of the method name. We now have LogMessage and LogMessageWithName methods, as you can see in Figure 3-5.

click to expand
Figure 3-5: Overloaded methods take the wrong name.

When you first look at this, you might think we have a problem. Until now, the name displayed on this page has been the name of the method we have in code. Our use of the MessageName parameter has changed this. The methods are overloaded correctly, as you’ll see shortly, but the MessageName is used rather than the method name so a distinction can be made between the overloaded methods when you look at the Web service in this manner.

To prove that the methods are overloaded correctly, you can look at the WSDL that’s generated. The <operation> elements of both the <binding> and <portType> elements have the name LogMessage, and the <input> elements take the value of the MessageName parameter, as you can see from the <operation> elements for the <portType> element:

<operation name="LogMessage">   <input message="s0:LogMessageSoapIn" />  </operation> <operation name="LogMessage">   <input name="LogMessageWithName"     message="s0:LogMessageWithNameSoapIn" />  </operation>

If you still don’t believe that the overloaded message names have been used, you can add the Web service to a Visual Studio .NET project and look in the Object Browser; you’ll see that the correct method names have indeed been retained, as shown in Figure 3-6.


Figure 3-6: Overloaded methods in the Object Browser




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