J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman
Microsoft Corporation
May 2004
Related Links
Home Page for Improving .NET Application Performance and Scalability
Chapter 5, Improving Managed Code Performance
Checklist: Web Services Performance
Send feedback to [email protected]
patterns & practices Library
Summary: This chapter focuses on design guidelines and techniques, such as state management, asynchronous invocation, serialization, threading, to help you develop efficient Web services. This chapter also presents a formula for reducing thread contention and HTTP connections to increase the throughput for your Web services.
Objectives
Overview
How to Use This Chapter
Architecture
Prescriptive Guidance for Web Services, Enterprise Services, and .NET Remoting
Performance and Scalability Issues
Design Considerations
Implementation Considerations
Connections
Threading
One-Way (Fire-and-Forget) Communication
Asynchronous Web Methods
Asynchronous Invocation
Timeouts
WebMethods
Serialization
Caching
State Management
Bulk Data Transfer
Attachments
COM Interop
Measuring and Analyzing Web Services Performance
Web Service Enhancements
Summary
Additional Resources
Services are the ideal communication medium for distributed applications. You should build all of your services using Web services and then, if necessary, use Enterprise Services or Microsoft® .NET remoting within the boundaries of your service implementation. For example, you might need to use Enterprise Services for distributed transaction support or object pooling.
Web services are ideal for cross-platform communication in heterogeneous environments because of their use of open standards such as XML and Simple Object Access Protocol (SOAP). However, even in a closed environment where both client and server systems use the .NET Framework, ease of deployment and maintenance make Web services a very attractive approach.
This chapter begins by examining the architecture of ASP.NET Web services, and then explains the anatomy of a Web services request from both the client and server-side perspectives. You need a solid understanding of both client and server to help you identify and address typical Web services performance issues. An understanding of Web services architecture will also help you when you configure the HTTP runtime to optimize Web services performance. The chapter then presents a set of important Web services design considerations, followed by a series of sections that address the top Web services performance issues.
To get the most out of this chapter:
The server-side infrastructure is based on ASP.NET and uses XML serialization. When the Web server processes an HTTP request for a Web service, Internet Information Services (IIS) maps the requested extension (.asmx) to the ASP.NET Internet server application programming interface (ISAPI) extension (Aspnet_isapi.dll). The ASP.NET ISAPI extension then forwards the request to the ASP.NET worker process, where it enters the request processing pipeline, which is controlled by the HttpRuntime object. See Figure 10.1 for an illustration of the Web services architecture and request flow.
Figure 10.1: ASP.NET Web services architecture and request flow
The request is initially passed to the HttpApplication object, followed by the series of registered HttpModule objects. HttpModule objects are registered in the system-wide Machine.config file or in the <httpModules> section of an application-specific Web.config file. HttpModule objects handle authentication, authorization, caching, and other services.
After passing through the HTTP modules in the pipeline, HttpRuntime verifies that the .asmx extension is registered with the WebServiceHandlerFactory handler. This creates an HTTP handler, an instance of a type that derives from WebServiceHandler, which is responsible for processing the Web services request. The HTTP handler uses reflection to translate SOAP messages into method invocations. WebServiceHandler is located in the System.Web.Services.Protocols namespace.
On the client side, proxy classes provide access to Web services. Proxy classes use XML serialization to serialize the request into a SOAP message, which is then transported using functionality provided by the System.Net namespace.
You can use the Wsdl.exe tool to automatically generate the proxy class from the Web Services Description Language (WSDL) contract file. Depending on the bindings specified in the WSDL, the request issued by the proxy may use the HTTP GET, HTTP POST, or HTTP SOAP protocols.
The proxy class is derived from one of the following base classes:
These all derive from System.Web.Services.Protocols.HttpWebClientProtocol, which in turn derives from the System.Web.Services.Protocols.WebClientProtocol base class in the inheritance chain. WebClientProtocol is the base class for all automatically generated client proxies for ASP.NET Web services, and, as a result, your proxy class inherits many of its methods and properties.
Services are the preferred communication technique to use across application boundaries, including platform, deployment, and trust boundaries. You can implement services today by using Web services or Web Services Enhancements (WSE). Although WSE provides a rich set of features, you should evaluate whether or not you can accept the WSE support policy. Enterprise Services provides component services such as object pooling, queued components, a role-based security model and distributed transactions, and should be used as an implementation detail within your service when you need those features. .NET remoting is preferred for cross-application communication within the same process.
When you design distributed applications, use the services approach whenever possible. Although object orientation provides a pure view of what a system should look like and is effective for producing logical models, an object-based approach can fail to consider real-world factors, such as physical distribution, trust boundaries, and network communication, as well as nonfunctional requirements, such as performance and security.
Table 10.1 summarizes some key differences between object orientation and service orientation.
Table 10.1: Object Orientation vs. Service Orientation
Object Orientation | Service Orientation |
---|---|
Assumes a homogeneous platform and execution environment. | Assumes a heterogeneous platform and execution environment. |
Shares types, not schemas. | Shares schemas, not types. |
Assumes cheap, transparent communication. | Assumes variable cost, explicit communication. |
Objects are linked: object identity and lifetime are maintained by the infrastructure. | Services are autonomous: security and failure isolation are a must. |
Typically requires synchronized deployment of both client and server. | Allows continuous, separate deployment of client and server. |
Is easy to conceptualize and thus provides a natural model to follow. | Builds on ideas from component software and distributed objects. Dominant theme is to manage/reduce sharing between services. |
Provides no explicit guidelines for state management and ownership. | Owns and maintains state or uses the reference state. |
Assumes a predictable sequence, timeframe, and outcome of invocations. | Assumes message-oriented, potentially asynchronous, and long-running communications. |
Goal is to transparently use functions and types remotely. | Goal is to provide inter-service isolation and wire interoperability based on standards. |
Common application boundaries include platform, deployment, trust, and evolution. (Evolution refers to whether or not you develop and upgrade applications together.) When you evaluate architecture and design decisions that affect your application boundaries, consider the following:
When you are working with ASP.NET Web services, Enterprise Services, and .NET remoting, Microsoft recommends that you:
When you work with ASP.NET Web services, Enterprise Services, or .NET remoting, consider the following caveats:
The main issues that can adversely affect the performance and scalability of your Web services are summarized in the following list. Subsequent sections in this chapter provide strategies and technical information to prevent or resolve each of these issues.
Other issues that affect the amount of data passed across the wire include improper data transfer strategies for large amounts of data. Selecting an appropriate data transfer strategy — such as using a SOAP extension that performs compression and decompression or offloading data transfer to other services — is critical to the performance of your Web services solution.
On the client side, consumers of Web services have the option of calling Web services asynchronously or synchronously. Your code should call a Web service asynchronously only when you want to avoid blocking the client while a Web service call is in progress. If you are not careful, you can use a greater number of worker and I/O threads, which negatively affects performance. It is also slower to call a service asynchronously; therefore, you should avoid doing so unless your client application needs to do something else while the service is invoked.
To help ensure that you create efficient Web services, there are a number of issues that you must consider and a number of decisions that you must make at design time. The following are major considerations:
Design chunky interfaces by exposing Web methods that allow your clients to perform single logical operations by calling a single Web method. Avoid exposing properties. Instead, provide methods that accept multiple parameters to reduce roundtrips.
Do not create a Web service for each of your business objects. A Web service should wrap a set of business objects. Use Web services to abstract these objects and increase the chunkiness of your calls.
You can design Web services by using either of two programming models: messaging style and RPC style. The RPC style is based on the use of objects and methods. Web methods take object parameters to do the processing, and then return the results. This style generally relies on making multiple Web method calls to complete a single logical operation, as shown in the following code snippet.
//client calling a Web service Serv.SendItemsToBePurchased(Array[] items); Serv.ShippingAddress(string Address); Serv.CheckOut();
The messaging style does not focus on objects as parameters. It is based on a data contract (schema) between the Web service and its clients. The Web service expects the message to be XML that conforms to the published data contract.
//Client string msg = "<Items>…</Items>"; MyMethod(msg); //Server [WebMethod] void MyMethod(string msg){ . . . }
This approach allows you to package and send all parameters in a single message payload and complete the operation with a single call, thus reducing chatty communication. The Web service may or may not return results immediately; therefore, the clients do not need to wait for results.
The encoded formatting of the parameters in messages creates larger messages than literal message encoding (literal message encoding is the default). In general, you should use literal format unless you are forced to switch to SOAP encoding for interoperability with a Web services platform that does not support the literal format.
There are two broad categories of parameter types that you can pass to Web services:
Maintaining per-caller state in memory on the server limits scalability because the state consumes server resources. As an alternative, you can pass state back and forth between the client and Web service. Although this approach enables you to scale your service, it does add performance overhead — including the time taken to serialize, transmit, parse, and de-serialize the state with each call.
If you have a Web method that performs costly and time-consuming processing, consider validating the Web method input before processing it. It can be more efficient to accept the validation overhead to eliminate unnecessary downstream processing. However, unless you are likely to receive invalid input frequently, you should probably avoid schema validation due to the significant overhead that it introduces. You need to assess your specific situation to determine whether or not schema validation is appropriate.
You can validate input data either by using SOAP extensions or by using separate internal helper methods that your Web methods call. The advantage of using SOAP extensions is that they permit you to separate your validation code from your business logic. If there is any schema change in the future, the extension can change independently of the Web method.
Another option is to use the XmlValidatingReader class to perform schema-based validation, as shown in the following code snippet.
[WebMethod] public void ValidateCreditCard(string xmlCardInfo){ try { // Create and load a validating reader XmlValidatingReader reader = new XmlValidatingReader(xmlCardInfo, XmlNodeType.Element, null); // Attach the XSD schema to the reader reader.Schemas.Add( "urn:CardInfo-schema",@"http://localhost/Card/Cardschema.xsd"); // Set the validation type for XSD schema. // XDR schemas and DTDs are also supported reader.ValidationType = ValidationType.Schema; // Create and register an event handler to handle validation errors reader.ValidationEventHandler += new ValidationEventHandler( ValidationErrors ); // Process the input data while (reader.Read()) { . . . } // Validation completed successfully } catch { . . .} } // Validation error event handler private static void ValidationErrors(object sender, ValidationEventArgs args) { // Error details available from args.Message . . . }
You can greatly enhance Web services performance by caching data. With ASP.NET Web services, you can use many of the same caching features that are available to ASP.NET applications. These include ASP.NET output caching, HTTP response caching, and ASP.NET application caching.
In common with any caching solution, your caching design for a Web service must consider issues such as how frequently the cached data needs to be updated, whether or not the data is user-specific or application-wide, what mechanism to use to indicate that the cache needs updating, and so on. For more information about caching with Web services, see the "Caching" section later in this chapter.
You can use the following approaches to optimize the performance of bulk data transfer:
To handle attachments, your options include:
More Information
For more information about these approaches, see the "Bulk Data Transfer" and "Attachments" sections later in this chapter.
Web services located on the same computer as a client ASP.NET application share the same thread pool with the ASP.NET application. Therefore, the client application and the Web service share the same threads and other related resources, such as CPU for request processing. Calling a local Web service also means that your request travels through the entire processing pipeline and incurs overhead, including serialization, thread switching, request queuing, and de-serialization.
In addition, the maxconnection attribute of Machine.config has no affect on the connection limit for making calls to local Web services. Therefore, local Web services always tend to give preference to the requests that come from the local computer over requests that come from other machines. This degrades the throughput of the Web service for remote clients.
There are two main approaches to solving this problem:
More Information
When you move from application design to development, consider the implementation details of your Web services. Important Web services performance measures include response times, speed of throughput, and resource management:
By following best practice implementation guidelines, you can increase the performance of Web services. The following sections highlight performance considerations for Web services features and scenarios.
When you call Web services, transmission control protocol (TCP) connections are pooled by default. If a connection is available from the pool, that connection is used. If no connection is available, a new connection is created, up to a configurable limit. There is always a default unnamed connection pool. However, you can use connection groups to isolate specific connection pools used by a given set of HTTP requests. To use a separate pool, specify a ConnectionGroupName when you make requests. If you don't specify a connection group, the default connection pool is used. To use connections efficiently, you need to set an appropriate number of connections, determine whether connections will be reused, and factor in security implications.
The following recommendations improve connection performance:
The maxconnection attribute in Machine.config limits the number of concurrent outbound calls.
Note This setting does not apply to local requests (requests that originate from ASP.NET applications on the same server as the Web service). The setting applies to outbound connections from the current computer, for example, to ASP.NET applications and Web services calling other remote Web services.
The default setting for maxconnection is two per connection group. For desktop applications that call Web services, two connections may be sufficient. For ASP.NET applications that call Web services, two is generally not enough. Change the maxconnection attribute from the default of 2 to (12 times the number of CPUs) as a starting point.
<connectionManagement> <add address="*" maxconnection="12"/> </connectionManagement>
Note that 12 connections per CPU is an arbitrary number, but empirical evidence has shown that it is optimal for a variety of scenarios when you also limit ASP.NET to 12 concurrent requests (see the "Threading" section later in this chapter). However, you should validate the appropriate number of connections for your situation.
Increasing the maxconnection attribute results in increased thread pool and processor utilization. With the increase in the maxconnection value, a higher number of I/O threads will be available to make outbound concurrent calls to the Web service. As a result, you process incoming HTTP requests more quickly.
You should consider increasing the connections only if you have available CPU. You should always check processor utilization before increasing the attribute because increasing the attribute results in more work for the processor, as described above. For this reason, increasing this attribute makes sense only when your processor utilization is below the threshold limits (usually less than 75 percent utilization).
For more information, see the "Threading" section later in this chapter.
Changing the attribute may involve multiple iterations for tuning and involves various trade-offs with respect to thread pool utilization. Therefore, the changes in the maxconnection attribute may require changes to other thread pool – related configuration attributes, such as maxWorkerThreads and maxIoThreads.
When you load test your application after making the configuration changes, you should monitor CPU utilization and watch the ASP.NET Applications/Requests/Sec and ASP.NET Applications/Requests in Application Queue performance counters. Requests in Application Queue should decrease while Requests/Sec and CPU utilization should increase.
Enumerate and prioritize the Web services you call. Allocate more connections to your critical Web services. You specify each Web service by using the address attribute as follows.
<connectionManagement> <add address="WebServiceA" maxconnection="8"> <add address="WebServiceB" maxconnection="4"> </connectionManagement>
For example, if your application typically makes more requests to WebServiceA than WebServiceB, you can dedicate more connections, as shown in the example above.
Use a single trusted identity for making Web services calls where you can. This helps limit the number of separate connection pools. Although you may need to create separate pools of connections for different discrete Web services, avoid creating pools per user. If you need to create pools per user, then specify a ConnectionGroupName when you call the Web service, but be aware that this hurts performance and leads to a large number of pools.
The connection pool your call uses is not determined by the identity of the caller. The ConnectionGroupName determines which connection pool is used. If separate identities use the same ConnectionGroupName, they use the same pool of connections, as shown in the following code snippet.
// Create a secure group name. …. serv = new WebService1(); // Set the PreAuthenticate property to send the authenticate request in first go serv.PreAuthenticate=true; // Set the client side credentials ICredentials conCredentials = new NetworkCredential("UserId","Password","NPSTest" ); serv.Credentials = conCredentials; // Do not allow the server to auto redirect as this may compromise security serv.AllowAutoRedirect=false; // Use the same connectionGroup Name for all the calls serv.ConnectionGroupName = "SameForAllUsers";
You may need to create separate pools of connections for different discrete Web services or if you flow the identity of the original caller.
If ASP.NET calls Web services that allow anonymous callers, connections from the default connection pool are used. This is the default behavior unless you specify a ConnectionGroupName, as shown in above example.
If your ASP.NET application calls a Web service that uses Microsoft Windows® integrated authentication, consider enabling UnsafeAuthenticatedConnectionSharing. By default, when you connect using Windows Integrated authentication, connections are opened and closed per request. This means that connections are not pooled by default. By enabling UnsafeAuthenticatedConnectionSharing, you keep connections open so they can be reused.
Consider the following guidelines:
//set the UnsafeAuthenticatedConnectionSharing to true myWebService.UnsafeAuthenticatedConnectionSharing = true; NetworkCredential myCred = new NetworkCredential("UserA","PasswordA","DomainA"); CredentialCache myCache = new CredentialCache(); myCache.Add(new Uri("http://Someserver/WS/service1.asmx"), "NTLM", myCred); myWebService.Credentials = myCache; myWebService.ConnectionGroupName = "SameName"; string result = myWebService.HelloWorld(); //as the ConnectionGroupName property is same for different client requests //only the first connection from above gets authenticated // the request below reuses the connection from above myCred = new NetworkCredential("UserB","PasswordB","DomainB"); CredentialCache myCache = new CredentialCache(); myCache.Add(new Uri("http://Someserver/WS/service1.asmx"), "NTLM", myCred); myWebService.Credentials = myCache; myWebService.ConnectionGroupName = "SameName"; result = myWebService.HelloWorld();
More Information
If you use Basic authentication, the proxy's PreAuthenticate property can be set to true or false. Set it to true to supply specific authentication credentials to cause a WWWauthenticate HTTP header to be passed with the Web request. This prevents the Web server from denying access to the request and performing authentication on the subsequent retry request.
Note Pre-authentication only applies after the Web service successfully authenticates the first time. Pre-authentication has no impact on the first Web request.
private void ConfigureProxy( WebClientProtocol proxy, string domain, string username, string password ) { // To improve performance, force pre-authentication proxy.PreAuthenticate = true; // Set the credentials CredentialCache cache = new CredentialCache(); cache.Add( new Uri(proxy.Url), "Negotiate", new NetworkCredential(username, password, domain) ); proxy.Credentials = cache; proxy.ConnectionGroupName = username; }
Web Services use ASP.NET thread pooling to process requests. To ensure that your Web Services use the thread pool most effectively, consider the following guidelines:
The Formula for Reducing Contention can give you a good starting point for tuning the ASP.NET thread pool. Consider using the Microsoft product group recommended settings (shown in Table 10.2) if you have available CPU, your application performs I/O bound operations (such as calling a Web method or accessing the file system), and you have queued requests as indicated by the ASP.NET Applications/Requests in Application Queue performance counter.
Table 10.2: Recommended Threading Settings for Reducing Contention
Configuration setting | Default (.NET 1.1) | Recommended value |
---|---|---|
maxconnection | 2 | 12 * #CPUs |
maxIoThreads | 20 | 100 |
maxWorkerThreads | 20 | 100 |
minFreeThreads | 8 | 88 * #CPUs |
minLocalRequestFreeThreads | 4 | 76 * #CPUs |
To address this issue, you need to configure the following items in Machine.config. The changes described in the following list should be applied across the settings and not in isolation. For a detailed description of each of these settings, see "Thread Pool Attributes" in Chapter 17, "Tuning .NET Application Performance."
Note The above recommendations are starting points rather than strict rules. You should perform appropriate testing to determine the correct settings for your environment.
If the formula has worked, you should see improved throughput and less idle CPU time:
If this does not improve your performance, you may have a CPU-bound situation. If this is the case, by adding more threads you increase thread context switching. For more information, see "ASP.NET Tuning" in Chapter 17, "Tuning .NET Application Performance."
More Information
For more information, see Microsoft Knowledge Base article 821268, "PRB: Contention, Poor Performance, and Deadlocks When You Make Web Service Requests from ASP.NET Applications," at http://support.microsoft.com/default.aspx?scid=kb;en-us;821268.
If you have burst load scenarios that are intermittent and short (0 to 10 minutes), then the thread pool may not have enough time to reach the optimal level of threads. The use of minIoThreads and minWorkerThreads allows you to configure a minimum number of worker and I/O threads for load conditions.
At the time of this writing, you need a supported fix to configure the settings. For more information, see the following Microsoft Knowledge Base articles:
For more information about threading and Web services, see:
Consider using the OneWay attribute if you do not require a response. Using the OneWay property of SoapDocumentMethod and SoapRpcMethod in the System.Web.Services.Protocols namespace frees the client immediately instead of forcing it to wait for a response.
For a method to support fire-and-forget invocation, you must decorate it with the OneWay attribute, as shown in the following code snippet.
[SoapDocumentMethod(OneWay=true)] [WebMethod(Description="Returns control immediately")] public void SomeMethod() {…}
This is useful if the client needs to send a message, but does not expect anything as return values or output parameters. Methods marked as OneWay cannot have output parameters or return values.
You can call a Web service asynchronously regardless of whether or not the Web service has been implemented synchronously or asynchronously. Similarly, you can implement a synchronous or asynchronous Web service, but allow either style of caller. Client-side and server-side asynchronous processing is generally performed to free up the current worker thread to perform additional work in parallel.
The asynchronous implementation of a Web method frees up the worker thread to handle other parallel tasks that can be performed by the Web method. This ensures optimal utilization of the thread pool, resulting in throughput gains.
For normal synchronous operations, the Web services asmx handler uses reflection on the assembly to find out which methods have the WebMethod attribute associated with them. The handler simply calls the appropriate method based on the value of the SOAP-Action HTTP header.
However, the Web services asmx handler treats asynchronous Web methods differently. It looks for methods that adhere to the following rules:
The Web services asmx handler then exposes the method, as shown in the following code snippet.
[WebMethod] IAsyncResult BeginMyProc(…) [WebMethod] EndMyProc(…) //the WSDL will show the method as MyProc(…)
The Web services asmx handler processes incoming requests for asynchronous methods as follows:
Consider the following guidelines for asynchronous Web methods:
Consider using asynchronous Web methods if you perform I/O-bound operations such as:
The .NET Framework provides the necessary infrastructure to handle these operations asynchronously, and you can return an IAsyncResult interface from these types of operations. The .NET Framework exposes asynchronous methods for I/O-bound operations using the asynchronous design pattern. The libraries that use this pattern have BeginXXX and EndXXX methods.
The following code snippet shows the implementation of an asynchronous Web method calling another Web service.
// The client W/S public class AsynchWSToWS { WebServ asyncWs = null; public AsynchWSToWS(){ asyncWs = new WebServ(); } [System.Web.Services.WebMethod] public IAsyncResult BeginSlowProcedure(int milliseconds,AsyncCallback cb, object s){ // make call to other web service and return the IAsyncResult return asyncWs.BeginLengthyCall(milliseconds,cb,s); } [System.Web.Services.WebMethod ] public string EndSlowProcedure(IAsyncResult call) { return asyncWs.EndLengthyCall(call); } } // The server W/S public class WebServ { [WebMethod] public string LengthyCall(int milliseconds){ Thread.Sleep(milliseconds); return "Hello World"; } }
Asynchronous implementation helps when you want to free up the worker thread instead of waiting on the results to return from a potentially long-running task. For this reason, you should avoid asynchronous implementation whenever your work is CPU bound because you do not have idle CPU to service more threads. In this case, an asynchronous implementation results in increased utilization and thread switching on an already busy processor. This is likely to hurt performance and overall throughput of the processor.
Note You should not use asynchronous Web methods when accessing a database. ADO.NET does not provide asynchronous implementation for handling database calls. Wrapping the operation in a delegate is not an option either because you still block a worker thread.
You should only consider using an asynchronous Web method if you are wrapping an asynchronous operation that hands back an IAsyncResult reference.
You should not implement Web methods when your asynchronous implementation depends upon callbacks or delegates because they use worker threads internally. Although the delegate frees the worker thread processing the request, it uses another worker thread from the process thread pool to execute the method. This is a thread that can be used for processing other incoming requests to the Web service. The result is that you consume a worker thread for the delegate-based operation and you increase context switching.
Alternatively, you can use synchronous Web methods and decrease the minFreeThreads setting so that the worker threads can take requests and execute them directly.
In this scenario, you could block the original worker thread by implementing the Web method to run synchronously. An example of the delegate-based implementation is shown in the following code snippet.
// delegate public delegate string LengthyProcedureAsyncStub(int milliseconds); //actual method which is exposed as a web service [WebMethod] public string LengthyCall(int milliseconds) { System.Threading.Thread.Sleep(milliseconds); return "Hello World"; } [WebMethod] public IAsyncResult BeginLengthyCall(int milliseconds,AsyncCallback cb, object s) { LengthyProcedureAsyncStub stub = new LengthyProcedureAsyncStub(LengthyCall); //using delegate for asynchronous implementation return stub.BeginInvoke(milliseconds, cb, null); } [System.Web.Services.WebMethod] public string EndLengthyCall(IAsyncResult call) { return ms.asyncStub.EndInvoke(call); }
Web services clients can call a Web service either synchronously or asynchronously, independently of the way the Web service is implemented.
For server applications, using asynchronous calls to a remote Web service is a good approach if the Web service client can either free the worker thread to handle other incoming requests or perform additional parallel work before blocking for the results. Generally, Windows Forms client applications call Web services asynchronously to avoid blocking the user interface.
Note The HTTP protocol allows at most two simultaneous outbound calls from one client to one Web service.
The WSDL-generated proxy contains support for both types of invocation. The proxy supports the asynchronous call by exposing BeginXXX and EndXXX methods.
The following guidelines help you decide whether or not calling a Web service asynchronously is appropriate:
Asynchronous invocation is the most useful when the client has additional work that it can perform while the Web method executes. Asynchronous calls to Web services result in performance and throughput gains because you free the executing worker thread to do parallel work before it is blocked by the Web services call and waits for the results. This lets you concurrently process any work that is not dependent on the results of the Web services call. The following code snippet shows the approach.
private void Page_Load(object sender, System.EventArgs e) { serv = new localhost.WebService1(); IAsyncResult result = serv.BeginLengthyProcedure(5000,null,null); // perform some additional processing here before blocking // wait for the asynchronous operation to complete result.AsyncWaitHandle.WaitOne(); string retStr = serv.EndLengthyProcedure(result); }
Consider asynchronous invocation if you need to call multiple Web services that do not depend on each other's results. Asynchronous invocation lets you call the services concurrently. This tends to reduce response time and improve throughput. The following code snippet shows the approach.
private void Page_Load(object sender, System.EventArgs e){ serv1 = new WebService1(); serv2 = new WebService2(); IAsyncResult result1 = serv1.BeginLengthyProcedure(1000,null,null); IAsyncResult result2 = serv2.BeginSlowProcedure(1000,null,null); //wait for the asynchronous operation to complete WaitHandle[] waitHandles = new WaitHandle[2]; waitHandles[0] = result1.AsyncWaitHandle; waitHandles[1] = result2.AsyncWaitHandle; WaitHandle.WaitAll(waitHandles); //depending upon the scenario you can //choose between WaitAny and WaitAll string retStr1 = serv1.EndLengthyProcedure(result1); string retStr2 = serv2.EndSlowProcedure(result2); }
By calling a Web service asynchronously from a Windows Forms application, you free the main user interface thread. You can also consider displaying a progress bar while the call progresses. This helps improve perceived performance.
However, you need to perform some additional work to resynchronize the results with the user interface thread because the Web service call is handled by a separate thread. You need to call the Invoke method for the control on which you need to display the results.
More Information
For more information, see the MSDN article, "At Your Service: Performance Considerations for Making Web Service Calls from ASPX Pages," at http://msdn.microsoft.com/library/en-us/dnservice/html/service07222003.asp.
It is very common for an ASP.NET application to call a Web service. If your application's Web page times out before the call to the Web service times out, this causes an unmanaged resource leak and a ThreadAbortException. This is because I/O completion threads and sockets are used to service the calls. As a result of the exception, the socket connection to the Web service is not closed and cannot be reused by other outbound requests to the Web service. The I/O thread continues to process the Web service response.
To avoid these issues, set timeouts appropriately as follows:
When you call a Web service synchronously, set the Timeout property of the Web service proxy. The default value is 100 seconds. You can programmatically set the value before making the call, as shown in the following code snippet.
MyWebServ obj = new MyWebServ(); obj.Timeout = 15000; // in milliseconds
For ASP.NET applications, the Timeout property value should always be less than the executionTimeout attribute of the httpRuntime element in Machine.config. The default value of executionTimeout is 90 seconds. This property determines the time ASP.NET continues to process the request before it returns a timed out error. The value of executionTimeout should be the proxy Timeout, plus processing time for the page, plus buffer time for queues.
The Web service timeout needs to be handled differently, depending upon whether you call the Web service synchronously or asynchronously. In either case, you should ensure that the timeouts are set to a value less than the executionTimeout attribute of the httpRuntime element in Machine.config. The following approaches describe the options for setting the timeouts appropriately:
MyWebServ obj = new MyWebServ(); obj.Timeout = 15000; // in milliseconds
You can also set the value in the proxy class generated by the WSDL for the Web service. You can set it in the class constructor, as shown in the following code snippet.
public MyWebServ() { this.Url = "http://someMachine/mywebserv/myserv.asmx"; this.Timeout = 10000; //10 seconds }
Or you can set it at the method level for a long-running call.
public string LengthyProc(int sleepTime) { this.Timeout = 10000; //10 seconds object[] results = this.Invoke("LengthyProc", new object[] {sleepTime}); return ((string)(results[0])); }
MyWebServ obj = new MyWebServ(); IAsyncResult ar = obj.BeginFunCall(5,5,null,null); // wait for not more than 2 seconds ar.AsyncWaitHandle.WaitOne(2000,false); if (!ar.IsCompleted) //if the request is not completed { WebClientAsyncResult wcar = (WebClientAsyncResult)ar; wcar.Abort();//abort the call to web service } else { //continue processing the results from web service }
After you make the configuration changes described in the previous section, if your Web pages time out while Web services calls are in progress, you need to ensure that you abort the Web services calls. This ensures that the underlying connections for the Web services calls are destroyed.
To abort a Web services call, you need a reference to the WebRequest object used to make the Web services call. You can obtain this by overriding the GetWebRequest method in your proxy class and assigning it to a private member of the class before returning the WebRequest. This approach is shown in the following code snippet.
private WebRequest _request; protected override WebRequest GetWebRequest(Uri uri){ _request = base.GetWebRequest(uri); return _request; }
Then, in the method that invokes the Web service, you should implement a finally block that aborts the request if a ThreadAbortException is thrown.
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(…)] public string GoToSleep(int sleepTime) { bool timeout = true; try { object[] results = this.Invoke("GoToSleep", new object[] {sleepTime}); timeout = false; return ((string)(results[0])); } finally { if(timeout && _request!=null) _request.Abort(); } }
Note Modifying generated proxy code is not recommended because the changes are lost as soon as the proxy is regenerated. Instead, derive from the proxy class and implement new functionality in the subclass whenever possible.
When you make Web services calls from an ASP.NET application, if you are increasing the value of both the proxy timeout and the executionTimeout to greater than 180 seconds, consider changing the responseDeadlockInterval attribute for the processModel element in the Machine.config file. The default value of this attribute is 180 seconds. If there is no response for an executing request for 180 seconds, the ASP.NET worker process will recycle.
You must reconsider your design if it warrants changing the attributes to a higher value.
You add the WebMethod attribute to those public methods in your Web services .asmx file that you want to be exposed to remote clients. Consider the following Web method guidelines:
[WebMethod(BufferResponse=false)] public string GetTextFile() { // return large amount of data }
To determine whether or not to enable or disable buffering for your application, measure performance with and without buffering.
[WebMethod(CacheDuration=60)] public string GetSomeDetails() { // return large amount of data }
Note that because caching consumes server memory, it might not be appropriate if your Web method returns large amounts of data or data that frequently changes
[WebMethod(EnableSession=true)] public string GetSomeDetails() { // return large amount of data }
Note that clients must also maintain an HTTP cookie to identify the state between successive calls to the Web method.
For more information, see "WebMethodAttribute.EnableSession Property" on MSDN at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemWebServicesWebMethodAttributeClassEnableSessionTopic.asp.
The amount of serialization that is required for your Web method requests and responses is a significant factor for overall Web services performance. Serialization overhead affects network congestion, memory consumption, and processor utilization. To help keep the serialization overhead to a minimum:
To limit which fields of an object are serialized when you pass the object to or from a Web method and to reduce the amount of data sent over the wire, use the XmlIgnore attribute as shown in the following code snippet. The XmlSerializer class ignores any field annotated with this attribute.
Note Unlike the formatters derived from the IFormatter interface, XmlSerializer serializes only public members.
// This is the class that will be serialized. public class MyClass { // The str1 value will be serialized. public string str1; /* This field will be ignored when serialized-- unless it's overridden. */ [XmlIgnoreAttribute] public string str2; }
Reducing round trips to a Web service reduces the number of times that messages need to cross serialization boundaries. This helps reduce the overall serialization cost incurred. Design options that help to reduce round trips include the following:
Compressing the XML payload sent over the wire helps reduce the network traffic significantly. You can implement XML compression by using one of the following techniques:
For more information about serialization, see:
Caching is a great way to improve Web services performance. By reducing the average request time and easing server load, caching also helps scalability. You can cache frequently used data applicable to all users, or you can cache SOAP response output. You can cache application data by using ASP.NET caching features. You can cache SOAP output by using either the ASP.NET output cache or by employing perimeter caching. When designing a caching strategy for your Web services, consider the following guidelines:
If portions of your output are static or nearly static, use ASP.NET output caching. To use ASP.NET output caching with Web services, configure the CacheDuration property of the WebMethod attribute. The following code snippet shows the cache duration set to 30 seconds.
[WebMethod(CacheDuration=30)] public string SomeMethod() { ... . }
For more information, see Microsoft Knowledge Base article 318299, "HOW TO: Perform Output Caching with Web Services in Visual C# .NET," at http://support.microsoft.com/default.aspx?scid=kb;en-us;318299.
Web services clients can implement custom caching solutions to cache the response from Web services. If you intend that clients of your Web services should cache responses, consider providing cache expiration–related information to the clients so that they send new requests to the Web service only after their cached data has expired. You can add an additional field in the Web service response that specifies the cache expiration time.
If the output from your Web services changes infrequently, use hardware or software to cache the response at the perimeter network. For example, consider ISA firewall-based caching. Perimeter caching means that a response is returned before the request even reaches the Web server, which reduces the number of requests that need to be serviced.
For more information about ISA caching, see the white paper, "Scale-Out Caching with ISA," at http://www.microsoft.com/isaserver/techinfo/deployment/ScaleOutCachingwithISA.asp.
Web services state can be specific to a user or to an application. Web services use ASP.NET session state to manage per-user data and application state to manage application-wide data. You access session state from a Web service in the same way you do from an ASP.NET application — by using the Session object or System.Web.HttpContext.Current. You access application state using the Application object, and the System.Web.HttpApplicationState class provides the functionality.
Maintaining session state has an impact on concurrency. If you keep data in session state, Web services calls made by one client are serialized by the ASP.NET runtime. Two concurrent requests from the same client are queued up on the same thread in the Web service — the second request waits until the first request is processed. If you do not use session data in a Web method, you should disable sessions for that method.
Maintaining state also affects scalability. First, keeping per-client state in-process or in a state service consumes memory and limits the number of clients your Web service can serve. Second, maintaining in-process state limits your options because in-process state is not shared by servers in a Web farm.
If your Web service needs to maintain state between client requests, you need to choose a design strategy that offers optimum performance and at the same time does not adversely affect the ability of your Web service to scale. The following guidelines help you to ensure efficient state management:
To maintain state between requests, you can use session state in your Web services by setting the EnableSession property of the WebMethod attribute to true, as shown in the following code snippet. By default, session state is disabled.
[WebMethod(EnableSession=true)] YourWebMethod() { ... }
Since you can enable session state at the Web method level, apply this attribute only to those Web methods that need it.
Note Enabling session state pins each session to one thread (to protect session data). Concurrent calls from the same session are serialized once they reach the server, so they have to wait for each other, regardless of the number of CPUs.
If you do use session state, in-process session state offers the best performance, but it prevents you from scaling out your solution and operating your Web services in a Web farm. If you need to scale out your Web services, use a remote session state store that can be accessed by all Web servers in the farm.
You have the following basic options for passing large amounts of data including binary data to and from Web methods:
With this approach, you pass a byte array as a method parameter. An additional parameter typically specifies the transfer data size. This is the easiest approach, and it supports cross-platform interoperability. However, it has the following issues:
Note that you can limit the maximum SOAP message size for a Web service by using the maxRequestLength setting in the <httpRuntime> section of the Web.config file. In the following example, the limit is set to 8 KB.
<configuration> <system.web> <httpRuntime maxRequestLength="8096" useFullyQualifiedRedirectUrl="true" executionTimeout="45"/> </system.web> </configuration>
For binary data transfer, you can use Base 64 to encode the data. Base 64 encoding is suitable for cross-platform interoperability if your Web service has a heterogeneous client audience.
This approach is more suitable if your data isn't large and the encoding/decoding overhead and size of the payload are not of significant concern. For large-sized data, you can implement a WSE filter and various compression tools to compress the message before sending it over the wire.
For more information about Base 64 encoding and decoding, see:
Returning a URL from the Web service is the preferred option for large file downloads. With this approach, you return a URL to the client, and the client then uses HTTP to download the file.
You can consider using the Background Intelligent Transfer Service (BITS), a Windows service, for this purpose. For more information about BITS, see the MSDN article, "Write Auto-Updating Apps with .NET and the Background Intelligent Transfer Service API" by Jason Clark, at http://msdn.microsoft.com/msdnmag/issues/03/02/BITS/.
If you need to use BITS for your .NET application for uploading and downloading of files, you can use the Updater Application Block. For more information, see the MSDN article "Updater Application Block for .NET" at http://msdn.microsoft.com/library/en-us/dnbda/html/updater.asp.
Although returning a URL works for downloads, it is of limited use for uploads. For uploads, you must call the Web service from an HTTP server on the Internet, or the Web service will be unable to resolve the supplied URL.
If you need to transfer large amounts of data (several megabytes, for example) from a Web method, consider using streaming. If you use streaming, you do not need to buffer all of the data in memory on the client or server. In addition, streaming allows you to send progress updates from a long-running Web service operation to a client that is blocked waiting for the operation to return.
In most cases, data is buffered on both the server and client. On the server side, serialization begins after the Web method has returned, which means that all of the data is usually buffered in the return value object. On the client side, the deserialization of the entire response occurs before the returned object is handed back to the client application, again buffering data in memory.
You can stream data from a Web service in two ways:
The XmlSerializer has special support for types that implement IList whereby it obtains and serializes one list item at a time. To benefit from this streaming behavior, you can implement IList on your return type, and stream out the data one list item at a time without first buffering it.
While this approach provides streaming and the ability to send progress updates, it forces you to work with types that implement IList and to break data down into list items.
Note The serializer is still responsible for serializing and deserializing each individual list item.
Another disadvantage is that the progress is reported from the returned type. This means that the instance of your type that implements IList used on the client side must be able to communicate progress to the client application while the serializer calls IList.Add. While this is achievable, it is not ideal.
The following server and client code samples show how to implement this approach. The server code is shown below.
// Server code // If the server wants to return progress information, the IList indexer would // have to know the current progress and return a value indicating that progress // when it is called. public class MyList : IList { int progress=0; public object this[int index] { get { // Pretend to do something that takes .5 seconds System.Threading.Thread.Sleep(500); if (progress <= 90) return progress+=10; else return "Some data goes here"; } set { // TODO: Add setter implementation } } ... other members omitted } [WebMethod] public MyList DoLongOperation() { // To prevent ASP.NET from buffering the response, the WebMethod must set // the BufferOutput property to false HttpContext.Current.Response.BufferOutput=false; return new MyList(); }
By using the above code, the Web service response is streamed out and consists of 10 progress data points (10 to 100), followed by a string of data.
The corresponding method on the client proxy class must return a type that implements IList. This type must know how to stream items as they are added to the list, and, if required, how to report progress information as it is retrieved from the stream. The relevant MyList member on the client is the Add method:
Note With .NET Framework 1.1 you must manually edit the generated proxy code because Wsdl.exe and the Add Web Reference option in Visual Studio.NET generate a proxy class with a method that returns an object array. You need to modify this to return a type that implements IList.
The client code is shown below.
// Client code public class MyList : IList { public int Add(object value) { if (progress < 100) { progress = Convert.ToInt32(value); Console.WriteLine("Progress is {0}",progress); } else { Console.WriteLine("Received data: {0}",value); } return 0; } } The client's proxy class then contains a method that returns MyList as shown below: public ProgressTestClient.MyList DoLongOperation() { ... code omitted }
Another possible approach is to create a type that implements IXmlSerializable, and return an instance of this type from your Web method and the client proxy's method. This gives you full control of the streaming process. On the server side, the IXmlSerializable type uses the WriteXml method to stream data out:
This solution is slightly cleaner than the previous approach because it removes the arbitrary restriction that the returned type must implement IList. However, the programming model is still a bit awkward because progress must be reported from the returned type. Once again, in.NET Framework 1.1, you must also modify the generated proxy code to set the correct method return type.
The following code sample shows how to implement this approach.
public class ProgressTest : IXmlSerializable { public void WriteXml(System.Xml.XmlWriter writer) { int progress=0; while(progress <= 100) { writer.WriteElementString("Progress", "http://progresstest.com", progress.ToString()); writer.Flush(); progress += 10; // Pretend to do something that takes 0.5 second System.Threading.Thread.Sleep(500); } writer.WriteElementString("TheData", "http://progresstest.com","Some data goes here"); } }
The Web method must disable response buffering and return an instance of the ProgressTest type, as shown below.
[WebMethod] public ProgressTest DoLongOperation2() { HttpContext.Current.Response.BufferOutput=false; return new ProgressTest(); }
For more information about bulk data transfer, see:
You have various options when handling attachments with Web services. When choosing your option, consider the following:
SwA (also known as WS-I Attachments Profile 1.0) is not supported. This is because you cannot model a MIME message as an XML Infoset, which introduces a non-SOAP processing model and makes it difficult to compose SwA with the rest of the WS-* protocols, including WS-Security. The W3C MTOM work was specifically chartered to fix this problem with SwA, and Microsoft is planning to support MTOM in WSE 3.0.
Calling single-threaded apartment (STA) objects from Web services is neither tested nor supported. The ASPCOMPAT attribute that you would normally use in ASP.NET pages when calling Apartment threaded objects is not supported in Web services.
For more information, see Microsoft Knowledge Base article 303375, "INFO: XML Web Services and Apartment Objects," at http://support.microsoft.com/default.aspx?scid=kb;en-us;303375.
The quickest way to measure the performance of a Web services call is to use the Microsoft Win32® QueryPerformanceCounter API, which can be used with QueryPerformanceFrequency to determine the precise number of seconds that the call consumed.
Note You can also use the ASP.NET/Request Execution Time performance counter on the server hosting the Web service.
Web Service Enhancements (WSE) is an implementation provided to support emerging Web services standards. This section briefly explains WSE, its role in Web services, and sources of additional information.
WSE 2.0 provides a set of classes implemented in the Microsoft.Web.Services.dll to support the following Web services standards:
Figure 10.2 shows how WSE extends the .NET Framework to provide this functionality.
Figure 10.2: WSE runtime
The WSE runtime consists of a pipeline of filters that intercepts inbound SOAP requests and outgoing SOAP response messages. WSE provides a programming model to manage the SOAP headers and messages using the SoapContext class. This gives you the ability to implement various specifications that it supports.
For more information about WSE, see the MSDN article, "Web Services Enhancements (WSE)," at http://msdn.microsoft.com/webservices/building/wse/default.aspx.
Web services are the recommended communication mechanism for distributed .NET applications. It is likely that large portions of your application are depending on them or will depend on them. For this reason, it is essential that you spend time optimizing Web services performance and that you design and implement your Web services with knowledge of the important factors that affect their performance and scalability.
This chapter has presented the primary Web services performance and scalability issues that you must address. It has also provided a series of implementation techniques that enable you to tackle these issues and build highly efficient Web services solutions.
For more information, see the following resources: