How to Apply the Basic Profile

 

Patterns and Practices home

Microsoft Corporation
August 2003

Applies to:
Microsoft® Visual Studio® .NET

Summary: This chapter details some best practice recommendations. These recommendations explain how to use Microsoft® Visual Studio® .NET to create Web services and Web service clients that conform to the Basic Profile. Some recommendations are general guidelines while others apply to particular directives in the Basic Profile. If a recommendation does apply to particular directives, their numbers are listed in square brackets after the recommendation (for example, [R1120]). There are two other types of recommendations:

  • Recommendations that apply when using Visual Studio.NET to create a Web service
  • Recommendations that apply when using Visual Studio.NET to create a Web service client that consumes Web services

(16 printed pages)

Contents

Recommendations for Creating Web Services
Recommendations for Creating Web Service Clients
More Information

Recommendations for Creating Web Services

The most common way of creating a Web service with Visual Studio.NET, and also the easiest starting point for creating a Web service that complies with the Basic Profile, is by implementing it as an ASMX page. This is what automatically is created when you use the Add New Project wizard to generate a new ASP.NET Web service project. The following recommendations assume this method of creating a Web service.

You must consider the following when creating a Web service that complies with the Basic Profile:

  • Parameter types
  • Extensibility mechanisms
  • Conformance claims
  • Cookies
  • Encoding
  • Redirection
  • The WebServiceBinding attribute
  • The SoapDocumentMethod attribute and the SoapDocumentService attribute
  • SOAP headers
  • Exception handling

Parameter Types

If your Web service includes parameters, headers, or return types that allow complex types, then you must ensure that the XML schema generation or serialization based on your usage of the complex type does not violate any of the Basic Profile rules. An example would be a return type of XmlNode, which would allow any possible XML to be returned, so does not restrict you from including soap:encodingStyle attributes.

Extensibility Mechanisms

The .NET framework offers multiple extensibility mechanisms that can modify Web service behavior such as SoapExtension, IHttpModule, IHttpHandler, and SoapExtensionReflector. These types of extensibility mechanisms give you low-level control over the SOAP or HTTP response, or the WSDL generation. When using these types of extensibility mechanisms, you'll need to review the Basic Profile rules directly to check if, for example, a modification to the SOAP response is in conflict with a Basic Profile rule. The WS-I Basic Profile test tools can also be used to help ensure compliance when using an extensibility mechanism.

Conformance Claims

The Basic Profile describes how to include conformance claims in your WSDL description although it is optional to include them. A .NET Web service does not support including a conformance claim when it generates a WSDL description. To include a conformance claim in the WSDL for your Web service, the easiest method is to save the auto-generated WSDL and then manually modify it to add any conformance claims. [R0002][R0003]

Cookies

The Basic Profile does not prohibit using cookies but says that, if you do use them, they should only be used for optimization purposes and should not be required for the Web service to function correctly. [R1120][R1121]

Microsoft recommends that you do not use cookies in your Web service. Any data sent between the client and your Web service should be in the SOAP request or response message itself. Also, Web service implementations would not be able to forward the SOAP message to another processor using a protocol other then HTTP because cookies are only supported by the HTTP protocol.

Encoding

Do not use either the SoapRpcService or SoapRpcMethod attributes in your Web service because the Basic Profile does not allow RPC/Encoded. Similarly, when you use the SoapDocumentMethod or the SoapDocumentService attributes do not set the Use property to SoapBindingUse.Encoded because the Basic Profile does not allow Document/Encoded. [R1005][R1006][R2706]

RPC/Literal

A .NET Web service does not support generating a WSDL description with an RPC/Literal binding. Also, .NET does not provide explicit support for creating a Web service that accepts messages described by an RPC/Literal binding although you can still create a .NET Web service that will accept such a message. [R1007][R2203][R2211] [R2705][R2706][R2717][R2726][R2729][R2735][R2737]

Microsoft recommends using Document/Literal in describing and implementing your Web service. Microsoft tools provide much better support for Document/Literal then RPC/Literal. Generally, the intended difference between RPC/Literal and Document/Literal binding is the programming model that each implies (RPC vs. messaging). However, there is no difference in terms of the real data that can be sent or the actual programming model that can be used. Document/Literal does offer the benefit of allowing the entire SOAP body to be validated using a single XSD schema.

Note For more information on RPC/Literal, see the More Information section at the end of this chapter.

Here is a sample WSDL description with an RPC/Literal binding, and a .NET Web service that can accept messages described by the WSDL:

WSDL:
<?xml version="1.0" encoding="utf-8"?>
<definitions
    xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:s="http://www.w3.org/2001/XMLSchema"
    xmlns:s0="https://msdn.microsoft.com/practices"
    targetNamespace="https://msdn.microsoft.com/practices"
    xmlns="https://schemas.xmlsoap.org/wsdl/">

  <types>
    <s:schema elementFormDefault="qualified"
targetNamespace="https://msdn.microsoft.com/practices">
      <s:complexType name="Param1Type">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1"
name="FirstValue" type="s:string" />
          <s:element minOccurs="0" maxOccurs="1"

name="SecondValue" type="s:string" />
        </s:sequence>
      </s:complexType>
      <s:complexType name="Param2Type">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1"
name="FirstValue" type="s:string" />
          <s:element minOccurs="0" maxOccurs="1"
name="SecondValue" type="s:string" />
        </s:sequence>
      </s:complexType>
    </s:schema>
  </types>

  <message name="HelloWorldSoapIn">
    <part name="param1" type="s0:Param1Type" />
    <part name="param2" type="s0:Param2Type" />
  </message>
  <message name="HelloWorldSoapOut">
    <part name="HelloWorldResult" type="s:string" />
  </message>

  <portType name="MyWebServiceSoap">
    <operation name="HelloWorld">
      <input message="s0:HelloWorldSoapIn" />
      <output message="s0:HelloWorldSoapOut" />
    </operation>
  </portType>

  <binding name="MyWebServiceSoap" type="s0:MyWebServiceSoap">
    <soap:binding transport="https://schemas.xmlsoap.org/soap/http"
style="rpc" />
    <operation name="HelloWorld">
      <soap:operation style="rpc" />
      <input>
        <soap:body use="literal"
namespace="https://msdn.microsoft.com/practices" />
      </input>
      <output>
        <soap:body use="literal"
namespace="https://msdn.microsoft.com/practices" />
      </output>
    </operation>
  </binding>

  <service name="MyWebService">
    <port name="MyWebServiceSoap" binding="s0:MyWebServiceSoap">
      <soap:address
location="https://localhost/WebServices/MyWebService.asmx" />
    </port>
  </service>
</definitions>

.NET Web service:
[WebService(Namespace="https://msdn.microsoft.com/practices")]
public class Service1 : System.Web.Services.WebService
{

    [WebMethod]
    [SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
    [return: XmlElement(Namespace="")]
    public string HelloWorld(HelloWorldRequestType HelloWorld)
    {
        return "Hello World";
    }
}

[XmlType(Namespace="")]
public class HelloWorldRequestType
{
    public Param1Type param1;
    public Param2Type param2;
}

[XmlType(Namespace="https://msdn.microsoft.com/practices")]
public class Param1Type
{
    public string FirstValue;
    public string SecondValue;
}

[XmlType(Namespace="https://msdn.microsoft.com/practices")]
public class Param2Type
{
    public string FirstValue;
    public string SecondValue;
}

Redirection

If you need to provide a redirect response in your Web service, do not use the Context.Response.Redirect method because the HTTP response will differ from what the Basic Profile mandates. [R1130]

The following example shows how to give a redirect response that complies with the Basic Profile:

[WebMethod]
public string HelloWorld()
{
  Context.Response.StatusCode = 307;
  Context.Response.AddHeader("Location","<redirect URL>");
  return null;
}

WebServiceBindingAttribute

When using a WebServiceBindingAttribute for importing a WSDL description, there are two restrictions you must follow to ensure that the WSDL generated by your .NET Web service does not violate any Basic Profile rules.

First, you must specify the Location property with a non-empty value. Otherwise, the WSDL generated will include a WSDL import statement which imports an XSD schema. [R2007]

Second, the Namespace property value must match the value of the targetNamespace attribute on the wsdl:definitions element in the WSDL description that is specified by the Location property of the WebServiceBindingAttribute. [R2005]

SoapDocumentMethodAttribute & SoapDocumentServiceAttribute

When using a SoapDocumentMethodAttribute or SoapDocumentServiceAttribute there are certain restrictions if you set the ParameterStyle property to SoapParameterStyle.Bare. These restrictions are you must not have more then one parameter to your WebMethod, and no two WebMethods can have identical parameters. [R2210][R2710]

SOAP Headers

When using a SoapHeaderAttribute, do not set the Direction property to SoapHeaderDirection.Fault or the WSDL description generated by your .NET Web service will include a soapbind:header element for the wsdl:output, which will not be included in the response messages from your Web service. [R2738]

The Basic Profile requires that any headers received by a Web service that are marked as mandatory must be validated before any processing can occur. Even if there are no headers declared for the Web service itself, the caller of the Web service might send headers that are marked as mandatory. These headers must also be checked. You can check for undeclared headers that have been sent by a caller by adding a SoapHeaderAttribute to a variable typed as an array of SoapUnknownHeader. [R1025][R1027][R2739][R2753][R2725]

This example shows how to check both a defined header called "myHeader" and any undefined headers the caller may have sent by using an "unknownHeaders" header:

public MyHeaderType myHeader;
public SoapUnknownHeader[] unknownHeaders;

[WebMethod]
[SoapHeader("myHeader", Required=false)]
[SoapHeader("unknownHeaders", Required=false)]
public string HelloWorld()

{
    bool isHeadersValid = true;

    // Check if declared header is marked as mandatory
    if (myHeader.MustUnderstand == true)
    {
        // Validate the information in the declared header
        if (<validation test of myHeader data> == false)
        {
            myHeader.DidUnderstand = false;
            isHeadersValid = false;
        }
    }

    // Check for unknown headers marked as mandatory
    foreach (SoapUnknownHeader header in unknownHeaders)
    {
        if (header.MustUnderstand)
        {
            header.DidUnderstand = false;
            isHeadersValid = false;
        }
    }
    if (isHeadersValid == false)
    {
        // exit method before processing
        return null;
    }
    // Execute normal web service processing
}

Exception Handling

Any exception thrown in a .NET Web service that is not of type SoapException is automatically wrapped in a SoapException by the .NET framework. In turn, the SoapException will then be converted to a SOAP fault message that is returned to the caller of the Web service. Because the Basic Profile has restrictions on SOAP fault messages, there are restrictions on how you throw a SoapException so that, when the .NET framework generates the SOAP Fault message, it does not violate any rules of the Basic Profile. These restrictions concern both the Code property and the Detail property.

The Code Property

The Basic Profile does not require, but does recommend that you use a namespace qualified fault code or one of the four fields of the SoapException class to set the code property. Specifically, these fields are the SoapException.VersionMismatchFaultCode, the SoapException.MustUnderstandFaultCode, the SoapException.ClientFaultCode, and the SoapException.ServerFaultCode. [R1004]

The Detail Property

When the detail property is specified, the root node element must be created using the SoapException.DetailElementName values. [R1000][R1001]

Here is an example:

XmlDocument doc = new Document();
// detail node is required to be the parent element
XmlNode detail = doc.CreateNode(XmlNodeType.Element, 
SoapException.DetailElementName.Name, 
SoapException.DetailElementName.Namespace);
// Add additional child nodes here
throw new SoapException("MyFault", SoapException.ServerFaultCode, 
"MyActor", detail);

You are not allowed to add attributes to the detail node with the namespace "https://schemas.xmlsoap.org/soap/envelope/". [R1003]

Here is an example of what not to do:

detail.Attributes.Append(doc.CreateAttribute("soap", 
"MyAttribute", "https://schemas.xmlsoap.org/soap/envelope/"));

Recommendations for Creating Web Service Clients

The most common way of creating a Web service client with Visual Studio.NET is to use the Add Web Reference wizard, which reads the WSDL description of the Web service and auto-generates a proxy class that inherits from the SoapHttpClientProcotol class. The following information assumes this is how you create your Web service client, and also assumes the Web service and its WSDL are Basic Profile compliant. Issues you must consider when creating a Web service client that complies with the Basic Profile fall into two categories. One category is what happens when you invoke a Web service using an auto-generated proxy class. The other is using the Add Web Reference wizard. We will discuss each in turn.

Invoking the Web Service Using the Proxy Class

When you invoke a Web service, you must consider the following issues:

  • Conformance claims
  • Message format
  • Parameter types
  • Cookies
  • One-way operations
  • SOAP headers

Conformance Claims

The Basic Profile describes how to include conformance claims in your message although it is optional to include them. A proxy class generated by the Add Web Reference wizard will not include WS-I conformance claims in messages sent to the Web service. [R0004][R0005][R0006][R0007]

To include conformance claims you will need to manually modify the proxy class to make it support including a conformance claim header. You must also make sure to set the header property (ClaimHeaderValue in this example) to a valid instance of the header and not set the MustUnderstand property before invoking the Web service. The following shows the code necessary to add to the proxy class to make it support including a conformance claim:

// Add the following to the variable declarations
// of the proxy
public ClaimHeaderType ClaimHeaderValue;

// Add the following immediately before the WebMethod
[SoapHeaderAttribute("ClaimHeaderValue")]

// Add the following class at the end of the file
 [XmlRoot ("Claim",
Namespace="http://ws-i.org/schemas/conformanceClaim/",
IsNullable=false)]
public class ClaimHeaderType : SoapHeader
{
  [XmlAttribute ()]
  public string conformsTo = "http://ws-i.org/profiles/basic/1.0";
}

Message Format

When calling a Web service, the default message serialization format is UTF-8. The SoapHttpClientProtocol class allows you to override this by setting the RequestEncoding property. Since the Basic Profile permits either UTF-8 or UTF-16 encoding, you can set this property to either UTF8Encoding or UnicodeEncoding (which is the same as UTF-16). [R1012]

Parameter Types

If a parameter or SOAP header value of the Web service allows complex types, then you must ensure that your usage of the complex type does not result in a serialized form when passing, which would violate any of the Basic Profile rules. An example would be a parameter type of XmlNode, which would allow any possible XML to be passed, so would not restrict you from including soap:encodingStyle attributes.

Cookies

If, when calling a Web service, any cookies are returned in the CookieContainer property, you must not use the values of the cookies for any purpose other then passing them back to the Web service in additional calls. [R1123]

One-Way Operations

The Basic Profile requires the consumer to ignore the SOAP response for a one-way operation; however in an error condition the proxy class will read the response to include in the details of the SoapException. Your client code must ignore the details in any SoapException when invoking a one-way operation. [R2750]

If, when calling a Web service, the method is a one-way operation, you must also not write your application so that it interprets a successful response to mean that the message was valid or that the receiver will process it. [R2727]

SOAP Headers

When invoking a method of the proxy class, all SoapHeader properties of the proxy class that are used by that method must first be initialized with an instance of the header. [R2738]

Using the Add Web Reference wizard

The following cover issues related to the Add Web Reference wizard's reading of a WSDL description.

Conformance Claims

The Add Web Reference wizard fails if a WSDL description contains WS-I conformance claims. You'll need to manually edit the WSDL to remove the conformance claims before using the Add Web Reference wizard. [R0002][R0003]

Required WSDL Extension Elements

The Basic Profile requires that all WSDL extension elements marked as required must not be ignored when processing the WSDL. However, the Add Web Reference wizard ignores any WSDL extension elements it does not recognize, even if they are marked as required. You must review the WSDL first to see if any such elements exist. You can also use the wsdl.exe command line tool which displays warnings if it finds WSDL elements marked as required. [R2027]

WSDL Description Encoding

The Basic Profile allows WSDL descriptions to be encoded in either UTF-8 or UTF-16. However, the Add Web Reference wizard in Visual Studio.NET only supports UTF-8 encoded WSDL descriptions. If the WSDL description is encoded with UTF-16, you must manually convert it to UTF-8 before using the Add Web Reference wizard. Notepad.exe can be used for converting because it supports opening a UTF-16 document, and using the Save As dialog, saving in ANSI format. [R4003]

RPC-Literal Binding in WSDL Description

The Basic Profile allows WSDL descriptions to have RPC-Literal bindings. However, the Add Web Reference wizard in Visual Studio.NET does not support WSDL descriptions with RPC-Literal bindings. You can manually create a class which inherits from SoapHttpClientProtocol and uses the SoapDocumentMethodAttribute with a ParameterStyle of SoapParameterStyle.Bare on your WebMethods. Alternatively, you can edit the WSDL description to convert it to a Document-Literal binding (see "RPC/Literal and Freedom of Choice" under More Information). [R1007][R2203][R2208][R2211][R2705][R2717][R2726][R2729][R2735][R2737]

The following example shows how to create such a class. Using the same WSDL description example that is in the RPC/Literal section of this chapter, this example is the C# code (including the class which inherits from the SoapHttpClientProtocol class), which allows you to use the WSDL description with an RPC-Literal binding.

SoapHttpClientProtocol inherited class - 
[WebServiceBinding(Name="MyWebServiceSoap", 
Namespace="https://msdn.microsoft.com/practices")]
public class MyWebService : SoapHttpClientProtocol {

    public MyWebService() {
        this.Url = "https://localhost/WebServices/MyWebService.asmx";
    }

    [SoapDocumentMethod("https://msdn.microsoft.com/practices/HelloWorld", 
ParameterStyle=SoapParameterStyle.Bare)]
    [return: XmlElement(Namespace="")]
    public string HelloWorld([XmlElement("HelloWorld", 
Namespace="https://msdn.microsoft.com/practices")] 
HelloWorldRequestType HelloWorld1) {
        object[] results = this.Invoke("HelloWorld", new object[] {
                    HelloWorld1});
        return ((string)(results[0]));
    }
}

[XmlType(Namespace="https://msdn.microsoft.com/practices")]
public class Param2Type {
    public string FirstValue;
    public string SecondValue;
}

[XmlType(Namespace="https://msdn.microsoft.com/practices")]
public class Param1Type {
    public string FirstValue;
    public string SecondValue;
}

[XmlType(Namespace="")]
public class HelloWorldRequestType {
    public Param1Type param1;
    public Param2Type param2;
}

More Information

The WS-I Basic Profile 1.0

RPC/Literal and Freedom of Choice