The .NET Framework makes the creation of a Web service almost embarrassingly simple, particularly when you’re developing in Visual Studio .NET. Just about all you need to conjure the Web service spirits in an otherwise normal looking .NET code file is that magical 11-character incantation: [WebMethod]. With the [WebMethod] attribute decorating a class method, and with the class referenced in an .asmx file, ASP.NET will generate a complete service description in WSDL and begin serving up clients right away.

However, because of the unique demands of their applications, most developers typically won’t be able to stop at the code that is autogenerated by the presence of [WebMethod]. Probably the most obvious additional attribute developers add is [WebService], where you can provide a nice description of the service and, most importantly, change its namespace from the default that ASP.NET automatically assigns and then later complains about when you access the Web service from a browser. In this article, I’ll go beyond these simple tweaks and consider a few more complicated changes that you might make to tailor the WSDL and SOAP that ASP.NET generates for you on the fly.

No SOAP header
Our first example overcomes the fact that ASP.NET, by default, generates no SOAP header element. If you need your clients to pass you a user id and password, for instance, such information really should go into the header to keep it separate from the functional requests that appear in the SOAP body. You need to tell .NET what such a header would look like, which messages might include it, and whether it is absolutely required for any of those messages. First create a small class in the same namespace as your main Web service class.

This new class will represent the header elements, and it must:

  • Inherit System.Web.Services.Protocols.SoapHeader; and
  • Be scoped with the public access-modifier.

You can put whatever public fields you’d like into the class. Using the authentication example, you might include UserID and Password fields, as shown in Listing A. You also need to create a public instance variable of this header class inside your main Web service class. You can name this instance variable any way you’d like; I called it auth in Listing A. Don’t include any instantiation code for the variable; ASP.NET will take care of that.

Having created a class and instance variable that can be used as a SOAP header element, you now need to tell ASP.NET which of the Web service methods use the header. As Listing A shows, just use the [SoapHeader] attribute of the System.Web.Services.Protocols namespace to decorate those methods that you want to have the header in their SOAP messages. Note that the first property in the [SoapHeader] attribute is the name of the instance variable of the special header class, which I called auth in Listing A. The value given to the Direction property indicates that only requests (inbound messages) use this header, and the Required property mandates its use; a request message that doesn’t contain a required header will result in the server returning a SOAP fault. If you now build the service and visit its .asmx file to take a look at the sample SOAP, you’ll see that the request message contains the header element with two child elements, each representing one of the public fields of the special header class.

Since you’ve told ASP.NET to expect the header, you can actually use that instance variable inside the code for your Web method. Listing A accomplishes this by passing the auth variable to another function to validate the user. ASP.NET instantiates the auth variable, so you do not see any instantiation code such as auth = new AuthenticationHeader() in Listing A.

A class with overloaded methods
Next, let’s consider a class with overloaded methods—multiple methods of the same name but with different signatures—all of which you want to turn into Web methods. Perhaps you have two versions of GetEmployeeInfo, one that accepts the employee id as a parameter, and another that accepts the last name and first name:

  • EmployeeInfoStruct GetEmployeeInfo(int nEmployeeID){…}
  • EmployeeInfoStruct GetEmployeeInfo(string sLastName, string sFirstName){…}

If I simply decorate both of them with the [WebMethod] attribute, ASP.NET will complain that two messages can’t share the same name. Having no desire to change the class method names just to make ASP.NET happy, I can instead add the MessageName property to the [WebMethod] attribute, as in Listing B. This specifies SOAP message names of GetEmployeeByID and GetEmployeeByName for the overloaded methods. Although the WSDL will still have two different operations named GetEmployeeInfo within its SOAP binding, the operations will now have different soapAction attributes and different input and output message names.

Making the service efficient
For the next few examples, imagine that your Web service enjoys extremely high traffic and you are therefore very concerned about efficiency. In reviewing the service offerings, you find that some of the Web methods really do not require a response envelope to the client. In other words, these could be “fire and forget” requests. This is not quite as scary as it may sound; the client won’t simply send the message out on the wire and then stop listening (okay, so it’s not really “fire and forget”). In fact, the server holds the HTTP connection open while it loads and parses the request message, but it immediately returns an HTTP 202 response indicating that it has begun working on the request. So the client can at least be comfortable in the knowledge that everything was received and that the server considers the request message to be intelligible and well-formed.

The net effect is that the HTTP connection is freed up faster and the server will not have to expend processing power to create and send a response envelope. All of this magic can be accomplished by decorating a method that returns void with one of the following System.Web.Services.Protocols namespace attributes:

  • [SoapDocumentMethod(OneWay=true)]
  • [SoapRpcMethod(OneWay=true)]

Choose between the two based on whether you use Remote Procedure Call (RPC) formatting for the message. The default for all messages, in the absence of one of these two attributes, is the “document” style of formatting. So if you haven’t been using RPC for all of your other Web methods in the class, you will probably want to remain consistent and choose [SoapDocumentMethod].

With efficiency in mind, you might also decide to work on shortening the request and response messages; every byte counts! As in the earlier example with the overloaded methods, let’s assume you like following your normal programming practices and don’t wish to develop a different style simply for Web services. So you name methods and parameters with the same verbosity as you would for any other class. This keeps the code looking the way you want for purposes of documentation and reflection.

But this doesn’t mean you can’t change how things are called in SOAP messages. For instance, the earlier GetEmployeeInfo example methods contained nice and verbose parameter names such as nEmployeeID, sFirstName, and sLastName. To keep those parameter names while instructing ASP.NET to serialize them differently into WSDL and SOAP, decorate them with the [XmlElement] attribute of the System.Xml.Serialization namespace.

Listing C does this with sLastName and sFirstName by shortening them to ln and fn for SOAP. The same decoration attribute shortens the SOAP names for the public fields of the AuthenticationHeader class that was created earlier. Basically, each of those [XmlElement] attributes tells .NET, “If you ever serialize the following parameter as an element in XML, call it such-and-such.” Since ASP.NET uses XML serialization to create WSDL and SOAP messages, this is precisely what happens.

More description properties
While cutting corners with slightly cryptic parameter names, you might also want to be nice to your clients and put more verbose description properties inside the [WebMethod] attribute, as Listing C shows for the GetEmployeeInfo method. This makes the WSDL files larger, but, more importantly, the SOAP envelopes smaller; this is a nice trade-off.

Keep a few hints in mind as you explore the .NET Framework documentation for other ways to manipulate your SOAP and WSDL output. First, notice that there are attributes to play with in three different namespaces:

  • System.Web.Services
  • System.Web.Services.Protocols
  • System.Xml.Serialization

This means you can manipulate events at different stages of a Web service lifetime, which can be important when looking for a particular solution. For example, you may find that there is no SOAP-related attribute class to accomplish something, but you can turn to the XML serialization attributes to see if you can reach your goal by manipulating at that level. As you peruse these namespaces, look out especially for classes whose names end in “Attribute”. Each bracketed attribute corresponds to one of these classes; this is the case not only for attributes related to Web services, but also for all decoration attributes. For example, the [WebMethod] decoration attribute is really the System.Web.Services.WebMethodAttribute class. The public properties that you see in the class documentation can also be put inside the parentheses of the matching decoration attribute.

A touch of elegance
As I have shown, you can mold the WSDL and SOAP to your liking without having to resort to the brute force approach, which would be to perform the serialization directly in your code, perhaps using the XmlWriter class. The examples have made frequent use of bracketed decoration attributes, which provide an especially elegant way to direct ASP.NET in its creation of these messages. With attributes at your disposal, you can keep your standard program code the way you like it, while at the same time presenting the public interface to the outside world in a manner that is tailored specifically to Web services.