I wrote this article about a year ago for an online magazine. It is based on concepts that were true in .NET 1.1 and should be updated. If you are using .NET 2.0 then you need to further research thread pooling and attachments, I included some links to help. Most of these tips apply to .NET 1.1 and 2.0.
When I first started writing web services a few years ago I noticed that they were often fairly slow. If you are writing web services using the same techniques that you wrote other types of distributed applications then your web services will be slow too. So here are some thoughts on making them fast and powerful. In short, making them fire breathing web services.
The first step in building fire breathing web services is to make sure that you really should be using web services. ASP.NET web services uses XML serialization, do not forget how it works and what its characteristics are. Serialization and network communications are some of the most important bottlenecks in a high performance system. Use both sparingly and give thought to minimizing both wherever possible.
The base functionality for your client request is provided in the WebClientProtocol base class. This is extended in the System.Web.Services.Protocols.HttpwebClientProtocol namespace. A client uses the System.Net classes to generate proxy classes to provide the web services access. Depending on your need the client will be one of three flavors. These are the HttpGetClientProtocol, HttpPostClientProtocol or SoapHttpClientProtocol proxy classes. These classes take the XML request serialize it into a SOAP message.
ASP.NET clients use the same thread pool for encoding and transmission as do ASP.NET web services. So if you have both on the same computer, in addition to wasting time, you have a double hit on the local resources. The maxconnection setting in your machine.config file excludes local client calls so local calls can steal more resources from remote client calls.
Make sure that you change the maxconnection setting in the machine.config file from 2 to something higher. I suggest experimenting starting with 10 connections for each processor in the server and heading up form there. You may find the number between 13 and 16. As you increase them the CPU utilization will increase. Shoot for 70 percent utilization as a maximum. As your users and application grow run periodic tests to ensure you are allocating the optimal level of connections.
This is one of the key things to remember, whatever goes over your communication channel has to be serialized and formed into SOAP (or some other type of) messages and then transported and unserialized. Many client/server developers do not give consideration to what is going over the wire and with web services, your bad habits show through. Network round trips and unnecessarily large messages will hurt performance. Try to keep messages as small as possible and consider if compression is warranted.
When IIS receives an HTTP request for an ASMX page it utilizes the aspnet_isapi.dll ISAPI extension. There is an ASP.NET worker process created and entered in a processing pipeline which is controlled by the HttpRuntime object which passes it to the HttpApplication object and the HttpModule objects specified in your section of the Web.config file or the Machine.config file.
After execution of the modules in the pipeline, the asmx extension is verified with the WebServiceHandlerFactory handler to create a handler instance. This instance of the WebServiceHandler is what is responsible for processing web service requests. The HTTP handler uses reflection to generate method invocations from the SOAP message.
Web services are usually either messaging oriented or RPC (objects and methods) oriented.
Use primitive types at all times if you can. Methods should be stateless and should be combined. Create methods that take multiple parameters and perform multiple functions to reduce round trips.
Try to avoid state. I personally think state is overused. Make sure that you consider the scalability costs of state management. Be wary of excessive state management. Methods should not be stateful. If you need state and are willing to trade off performance for scalability then you can pass state back in forth in your calls instead of managing it server side.
Validate up front. As a rule, validate data at the earliest possible point. If you cannot validate at the user input then validate it before your message is created. Do not waste round trips on invalid data formats. A good option is to validate against a schema stored on the server. You can use XmlValidatingReader to download an XSD and validate client side before sending off the message.
Your parameters should be formatted using the default literal format except where you have interoperability with non.NET web services. In that case you will need SOAP formatting.
Bulk data can be transferred by returning in the SOAP message the URL of a file to download. This can save a ton of time serializing, compressing, etc large data transfers. You can also use chunking of fixed size byte arrays. SOAP attachments can be WS-attachments, Base 64 encoded or SOAP Message Transmission Optimization Mechanism (MTOM).
If you are subject to sudden bursts in traffic then you would experience a performance lag during a traffic burst as the application suddenly needs to create more threads to handle the increase. If you are using .NET framework 1.1 or newer then you can set SetMinThreads method of the ThreadPool class.
I hope these thoughts are helpful and I hope to see fire coming from your next web service.