Top Five ASP.NET Web Services Tips

In this article, we explore some tips and tricks for using the Microsoft .NET framework for developing Web services. In the first section, I'll provide a brief comparison between ASP.NET Web services and .NET remoting, and then I'll delve into five tips I've found useful for developing ASP.NET Web services.

Before You Build: ASP.NET Web Services Versus .NET Remoting

ASP.NET Web services and .NET remoting are two separate paradigms for building distributed applications using Internet-friendly protocols and the .NET framework. Each has its advantages and drawbacks, which are important factors in deciding which one to use for your application.
Web services typically use SOAP for the message format and require that you use IIS for the HTTP message transport. This makes Web services good for communication over the Internet, and for communication between non-Windows systems. Web services are a good choice for message-oriented services that must support a wide range of client platforms and a potentially heavy load. Microsoft's MapPoint.NET service is an example of an ASP.NET Web service.
Remoting can be configured to use either SOAP or Microsoft's proprietary binary protocol for communication. The binary protocol yields higher performance, and is great for .NET to .NET communication, but cannot be used to communicate with non-Windows platforms. Remoting does not require an IIS Web server, making it a good choice for peer-to-peer development, but this also means that it cannot leverage the scalability and performance of IIS to support a high number of connections or requests per second. Microsoft's Terrarium is an example of a peer-to-peer application built using .NET remoting.
Related Reading

Tip 1: Disable HTTP/POST and HTTP/GET Protocols

Unless you specify otherwise, .NET will attempt to bind your Web services to three separate protocols: HTTP/POST, HTTP/GET, and SOAP. We say attempt, because depending on the parameter and return types for a service, the HTTP/GET protocol may not be possible. Bindings for these three protocols will be included in the WSDL file automatically generated by .NET, and consumer clients will have the option of choosing any one of them for communication with your service.
You can easily remove these bindings by adding the following section to your Web.config file:

<webservices>
    <protocols>
        <remove name="HttpPost" />
        <remove name="HttpGet" />
    </protocols>
</webservices>
This section will tell the WSDL generator not to include bindings for HTTP/POST and HTTP/GET. The two remove sections specify that HttpPost and HttpGet should not be supported.
Security and interoperability are two good reasons to avoid exposing your Web services using HTTP/POST or HTTP/GET. HTTP/GET is less secure than SOAP, and since HTTP/GET is commonly used for Web linking, a malicious user could potentially trick someone into unknowingly calling a Web service using their security credentials when they thought they were clicking a Web link.
With regard to interoperability, where SOAP is a widely-used standard for Web service communication, HTTP/GET and HTTP/POST are not. As a result, many automatic proxy generation tools weren't designed to "understand" the HTTP/GET and HTTP/POST bindings included by default in a .NET-generated WSDL document. If your service doesn't make use of these bindings, removing them can increase your service's interoperability.

Tip 2: Use tcpTrace to View SOAP Request/Response Messages

Debugging can be an exceptionally difficult task for Web services application developers, because neither the .NET SDK nor Visual Studio.NET includes tools for viewing the SOAP messages sent back and forth between the client and service. Having the capability to view these messages becomes particularly important when you try to identify the cause(s) of interoperability problems between .NET and non-.NET clients and servers, because such problems are often related to the format of the SOAP messages (e.g. "Is the SOAPAction field present?").
One great tool for viewing the message exchanges is tcpTrace. This tool works by setting up a tunnel between your client and server. When you start tcpTrace, you're prompted to enter the destination URL and port number, along with a local port number on which tcpTrace will listen. You can then point your proxy stub to this local port by setting the stub's Url property (e.g. localhost:8080). tcpTrace will log all of the request and response HTTP messages.
One limitation to tcpTrace is that its location in the message flow makes it useless for viewing messages sent over SSL. If you need to view the contents of SOAP messages sent over SSL, your best bet is to write a custom ISAPI filter.

Tip 3: Use Chunky Instead of Chatty Design

This design advice has no doubt been beaten to death in the literature regarding proper approaches to tiered application design, but it's especially important for distributed computing environments like Web services, so I'll take yet another swing at the horse.
When designing for performance and scalability in a distributed system, you want to make sure that you minimize the number of calls between the client and server. By minimizing the number of calls, you improve application speed, reduce communications overhead (why send three SOAP headers when one will do?), and reduce network traffic, all of which are generally considered very good things. So what do chatty and chunky designs look like? Consider the following chatty service and client:
Example 1a: A Chatty Service

using System;
using System.Web.Services;
namespace ChattyService
{
  public class ChattyService : WebService
  {
    private string username;
    private string password;

    public string Username
    {
      [WebMethod]
      set
      {
        username = Username;
      }
    }

    public string Password
    {
      [WebMethod]
      set
      {
        password = Password;
      }
    }

    [WebMethod]
    public bool Logon()
    {
      // Authentication Logic Here
      return true;
    }
  }
}
Example 1b: A Chatty Consumer

namespace ChattyConsumer
{
  class ChattyConsumerClass
  {
    [STAThread]
    static void Main(string[] args)
    {
      bool bReturn = false;
      Proxy.ChattyService objSvc = new Proxy.ChattyService();
      objSvc.set_Username("alex");
      objSvc.set_Password("opensesame");
      bReturn = objSvc.Logon();
    }
  }
}
In Example 1a, username and password are designed as properties that must first be set before calling the logon() method. What is not clear from the code alone is that the properties are exposed as Web methods, which means that each get/set call on a property results in a call to the service -- a chatty service.
Example 1b shows the consumer used to call the service. You can see that the username and password properties are set, followed by a call to the logon() method for a total of three trips to the service. A better design is to use a chunky interface like the following:
Example 2a: A Chunkier Interface

using System;
using System.Web.Services;
namespace ChattyService
{
  public class ChattyService : WebService
  {
    [WebMethod]
    public bool Logon(string Username, string Password)
    {
      // Authentication Logic Here
      return true;
    }
  }
}
The logon() method in this example includes the username and password as part of the method signature. This is a better design, because it reduces the number of calls to the server from three to one; however, for a large number of parameters, the method signature can become a bit of a beast. In that case, it makes sense to start modeling the input parameters as complex types; for example, a credentials object that encapsulates the username and password variables.
The method in Example 2a can be called with the following code:
Example 2b: A Chunky Consumer

namespace ChattyConsumer
{
  class ChattyConsumerClass
  {
    [STAThread]
    static void Main(string[] args)
    {
      bool bReturn = false;
      Proxy.ChunkyService objSvc = new Proxy.ChunkyService();
      bReturn = objSvc.Logon("alex", "opensesame");
    }
  }
}
This approach results in one call to the logon() Web method, reducing the number of network calls to 33% of the first approach.

Tip 4: Store Application-Specific Settings in Web.Config

ASP.NET Web services can take advantage of all of the features available to their .aspx cousins. This includes the ability to use a Web.config file to store application specific data (for example, database connection strings, file paths, and so on). Using Web.config instead of the global.asax file enables you to change configuration settings without having to rebuild the project.

Tip 5: Avoid ASP.NET Session State if You Can

While .NET's implementation of session state solves a number of the problems found in its ASP 3.0 predecessor (for example, request serialization, reliance on cookies, and lack of support for Web farms), it still suffers from several drawbacks. You should realize that it was not designed specifically for managing state in Web service applications, but rather for managing state in ASP.NET applications in general, and as a result, it relies on HTTP cookies (there is also an option to use a cookie-less mode that uses munged URLs, not compatible with a Web service). Cookies are HTTP specific, and while fine for the Web, where all browsers support them, using them in your Web services ties you to the HTTP protocol. SOAP is designed to work independently of the transport protocol, so tying your application to HTTP limits your flexibility and may create a lot of additional work for you down the line if you need to provide services over a transport protocol other than HTTP (for example, SMTP).
A better approach to managing state is to use a ticketing system implemented as metadata in a SOAP header. You can learn more about this technique in Chapter 5, "Managing State," of Programming .NET Web Services.

O'Reilly & Associates recently released (September 2002) Programming .NET Web Services.

1 comments:

Anonymous said...

It is awesome list.I wasn't aware of this feature of this article..thanks!Website Hosting

Post a Comment