Saturday, November 15, 2008

WS Response: XML Compression

Compressing the XMLs sent over the wire helps reduce the network traffic significantly. Compressing responses on the server and decompressing them on the client consumes additional CPU cycles on both server and client, but the savings in bandwidth is likely to justify the processing cost.

XML compression can be implemented by using one of the following techniques:

1. Set EnableDecompression property

EnableDecompression property gets or sets a Boolean that indicates whether decompression is enabled for this HttpWebClientProtocol. Default value for the property is “False”. The property is supported in .Net framework 2.0 and above.

E.g.

WebServiceRef.Service service = new WebServiceRef.Service();

service.EnableDecompression = true;

When the server returns the results, they are in gzip format, deflated format, or uncompressed. The property is only a suggestion, not a demand.

2.     By extending proxy class in client for the compression and decompression of requests and responses

To enable compression in .Net 1.1 GetWebRequest, GetWebResponse methods needs to be overridden in the proxy class generated. As the modified code will be removed if web reference needs to be added again, instead of modifying proxy code a class is inherited from proxy class and the methods are overridden in that class.

This method is also useful when the message created by proxy needs modification before sending it.

E.g.

protected override WebRequest GetWebRequest(Uri uri)

// Update the request's HTTP headers to specify that

// we can accept compressed (gzipped, deflated) responses

          {

                   WebRequest request = base.GetWebRequest(uri);

                   if (compressResponse)

                   {

                             request.Headers.Add("Accept-Encoding", "gzip, deflate");

                   }

                             return request;

                   }

protected override WebResponse GetWebResponse(WebRequest request)

// If we've requested compressed responses, return a WebResponse

// derivative that's capable of uncompressing it.

          {

                   WebResponse result;

                   // If user checked the Compressed Response checkbox

                   if (compressResponse)

                   {

                             result = new CompressedWebResponse((HttpWebRequest) request);

                   }

                   else // no compression requested, return stock WebResponse object

                   {

                             result = base.GetWebResponse(request);

                   }

                   // Keep track of content length to measure bandwidth savings.

                   responseContentLength = result.ContentLength;

                   return result;

          }

The CompressedWebResponse class uses the #ziplib library (available athttp://www.icsharpcode.net/OpenSource/SharpZipLib/) to decompress responses.

3.     Use HTTP compression features available in IIS 5.0 and later versions for compressing the response from the Web services.

Note that you need a decompression mechanism on the client and client should request the server for zipped response (This could be done by point 1 and 2 above).

To enable IIS 5.0 to compress .aspx pages, follow these steps:

Open a command prompt.

1.      Type net stop iisadmin, and then press ENTER.

2.      Type cd C:\InetPub\adminscripts, and then press ENTER.

3.      Type the following, and then press ENTER:

4.      CSCRIPT.EXE ADSUTIL.VBS SET     W3Svc/Filters/Compression/GZIP/HcScriptFileExtensions "asp" "dll" "exe" "aspx" Type the following, and then press ENTER:

5.      CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/DEFLATE/HcScriptFileExtensions "asp" "dll" "exe" "aspx"

6.      Type net start w3svc, and then press ENTER

Performance of the application can be increased by executing multiple web methods at the same time if they are not dependent on each other.

For example,

Consider two web service methods as follows:

[WebMethod]

    public float CalculateIncomeTax(float basic)

    {

        //Complicated calculations which takes time

        Thread.Sleep(8000);      

 

        return basic/100;

    }

[WebMethod]

    public float CalculateServiceTax(float basic)

    {

        //Complicated calculations which takes time

        Thread.Sleep(5000);

 

        return basic/1000;

    }

The following code calls these methods.

Synchronous call:

private void buttonSynch_Click(object sender, EventArgs e)

        {

            //Calculates Income tax

            StartProcess(true);

            newWebService.CalculateIncomeTax(100000);

            StartProcess(false);

            //Calculates Service tax

            StartProcess(true);

            newWebService.CalculateServiceTax(100000);

            StartProcess(false);

        }

Time required for execution = Time required for ( CalculateIncomeTax) + Time required for (CalculateServiceTax)

=  Approx 8 Sec. + Approx 5 Sec.

=  13.963955 ( Actual time required)

= Approx 14 Sec.

 

Asynchronous call for one function:

private void buttonAsynch_Click(object sender, EventArgs e)

        {

            StartProcess(true);

            newWebService.CalculateIncomeTaxAsync(100000);

            StartProcess(true);

            newWebService.CalculateServiceTax(100000);

            StartProcess(false);

  }

Time required for execution = Max (Time required for ( CalculateIncomeTax),  Time required for (CalculateServiceTax) )

=  Max ( Approx 8 Sec. , Approx 5 Sec. )

=  8.429886 (Actual time required )

= Approx 8 Sec.

Method StartProcess is written to signal start and end of processing.

int countProcess = 0

void StartProcess( bool startProcess )

        {

            if (startProcess)

                m_countProcess++;

            else

                m_countProcess--;

            if( m_countProcess == 0 )

                labelProcessingTax.Text = "";

            else

                labelProcessingTax.Text = "Processing";

            labelProcessingTax.Refresh();

}

Event handler is written to handle Asynchronous call completion event for CalculateIncomeTax method.

newWebService.CalculateIncomeTaxCompleted += new WindowsApplication1.NewService.CalculateIncomeTaxCompletedEventHandler(newWebService_CalculateIncomeTaxCompleted);

void newWebService_CalculateIncomeTaxCompleted(object sender, WindowsApplication1.NewService.CalculateIncomeTaxCompletedEventArgs e)

        {

            if (e.Cancelled == true)

                return;

             //Output is e.Result, use it to display IncomeTax

             StartProcess(false);

        }

Web service proxy class is generated by Visual studio when web reference is added.

Proxy class contains two overloaded methods for each web method to facilitate asynchronous calls.

 

       I.            Single-invocation:

(Web method name)Async ( method parameters…)

 

     II.            Multiple-invocation:

(Web method name)Async ( method parameters…, , object userState)

          Userstate should be unique value (for example, a GUID or hash code).

Usage:

1.      Multiple asynchronous calls:

Multiple-invocation makes possible calling methods asynchronously multiple times without waiting for any pending asynchronous operations to finish.

Trying to call methods asynchronously with single-invocation syntax multiple times, before previous invocation is completed throws InvalidOperationException.

 

2.      Tracking Pending Operations:

userState objects passed as parameters can be stored in collection to keep track of all pending tasks.

 

3.      Canceling Pending Operations:

Any particular instance of asynchronous operation can be cancelled by specifying the userState parameter passed during invocation to CancelAsync function.

E.g. newWebService.CancelAsync(userState);

Asynchronous calls invoked by single-invocation syntax are not cancellable.

Caching in ASP.Net WebService

One of the ways to enhance Web Service Performance is by caching data.

Various caching mechanisms possible are:

·         ASP.NET Output Caching

·         HTTP Response Caching

·         ASP.NET Application Caching

Before using any caching mechanism caching design for a Web service no. issues will be required to be considered and addressed, like:

·         How frequently the cached data needs to be updated

·         Whether the data is user-specific or application-wide

·         What mechanism to use to indicate that the cache needs updating, etc 

Which caching mechanism could be used will depend upon the pros and cons of each method and the properties of the data being returned.

 

ASP.NET Output Caching:

This type of caching could be considered when the data is static or almost static. 

This could be simply achieved by adding a ‘CacheDuration’ property to the WebMethod.

 

e.g.

  [WebMethod ( CacheDuration = 60)]

  public string GetCacheEntryTime(string Name)

    {

        StringBuilder sb = new StringBuilder("Hi ");

        sb.Append(Name);

        sb.Append(", the Cache entry was made at ");

        sb.Append(System.DateTime.Now.ToString());

       

        return (sb.ToString());

    }  

 

The method could be tested by calling it repeatedly with same and different parameters.

Calling the method in following sequence with a gap of 1 min:

textwriter.WriteLine(webService.GetCacheEntryTime("Angelina"));

textwriter.WriteLine(webService.GetCacheEntryTime("Brad"));

textwriter.WriteLine(webService.GetCacheEntryTime("DiffAngelina"));

textwriter.WriteLine(webService.GetCacheEntryTime("DiffBrad"));

textwriter.WriteLine(webService.GetCacheEntryTime("Angelina"));

textwriter.WriteLine(webService.GetCacheEntryTime("Brad"));

 

Results in Output as follows:

Hi Angelina, the Cache entry was made at 12/11/2007 1:16:37 PM

Hi Brad, the Cache entry was made at 12/11/2007 1:16:38 PM

Hi DiffAngelina, the Cache entry was made at 12/11/2007 1:16:41 PM

Hi DiffBrad, the Cache entry was made at 12/11/2007 1:16:42 PM

Hi Angelina, the Cache entry was made at 12/11/2007 1:16:37 PM

Hi Brad, the Cache entry was made at 12/11/2007 1:16:38 PM

 

Notice the same time stamp for same parameter value passed.

The .asmx page of Web Service by Default uses POST with “No-Cache” specified. Thus, this could not be tested with the test page of Web Service.

If the client wants to override server side output caching then it is possible by specifing “No-Cache” while making the request.

More on WS State Management

I tried to consume the Web service using a simple C# client by adding a web reference.

The code to call “PerSessionServiceUsage” was similar to follows:

private void buttonStateMgmtWithSession_Click(object sender, EventArgs e)

        {

            WebServiceRef.Service webService = new WebServiceRef.Service();

           

            int result;

 

            for (int i = 0; i <>

            {

                            

                result = webService.PerSessionServiceUsage();

 

                MessageBox.Show(result.ToString());

            }

        }

I was expecting the output to be equal to value of ( “variable i” + 1). But I was always getting result as 1!

Maintaining the session using cookies:

The Web service code does not see a valid session ID with the request, so it creates a brand new HttpSessionState object for each call, and returns the initial value of 1. The reason for this is that the client proxy class, which inherits from the System.Web.Services.Protocols.SoapHttpClientProtocol class does not have an instance of the System.Net.CookieContainer class associated with it. Basically, there is no place to store cookies that are returned.

Modifying the code as follows does the trick:

private void buttonStateMgmtWithSession_Click(object sender, EventArgs e)

        {

            WebServiceRef.Service webService = new WebServiceRef.Service();

            System.Net.CookieContainer Cookies = null;

 

            int result;

 

            for (int i = 0; i <>

            {

                if (Cookies == null)

                {

                    Cookies = new System.Net.CookieContainer();

                }

                webService.CookieContainer = Cookies;

                result = webService.PerSessionServiceUsage();

                MessageBox.Show(result.ToString());

            }

        }

Now, the result will be displayed as value of ( “variable i” + 1).

Default cookie container is not automatically associated with an instance of the SoapHttpClientProtocol class.

The same “CookieContainer” instance could be used by multiple instances of “SoapHttpClientProtocol” class.

State Management in XML Web Service

A webservice may need to store data per session so that the data could be used in consequent calls by the same client.

XML Web services have access to state management options similar to ASP.NET applications when the XML Web service class derives from the “WebService” class. The common objects like Application and Session are included in “WebService” class.

To access and store state specific to the Web application hosting the XML Web service:

1.      Derive the XML web service class from “WebService”. A class deriving from “WebService” automatically has access to the “Application” object.

2.      In a web method, the state name and value could be stored in Application as follows:

Application["appMyServiceUsage"] = 1;

 

3.      The value of state stored in Application could be modified as follows:

Application ["appMyServiceUsage "] = ((int) Application ["appMyServiceUsage "]) + 1;

 

Code will look similar to follows:

 

using System.Web.Services;

public class ServerUsage : WebService

{

 

[WebMethod(Description = "Number of times this service has been accessed.")]

    public int ServiceUsage()

    {

        // If the Web service method hasn't been accessed,

        // initialize it to 1.

        if (Application["appMyServiceUsage"] == null)

        {

            Application["appMyServiceUsage"] = 1;

        }

        else

        {

            // Increment the usage count.

 Application["appMyServiceUsage"] = ((int)Application["appMyServiceUsage"]) + 1;

        }

        return (int)Application["appMyServiceUsage"];

    }

}

 

To access and store state specific to a particular client session:

 

1.      Derive the XML web service class from “WebService”

 

2.      Set “EnableSession” property of the “WebMethod” attribute to true

e.g. [ WebMethod(EnableSession=true) ]

 

3.      Store state name and value in the session

e.g. Session["MyServiceUsage"] = 1;

 

4.      The value could be accessed and modified as follows:

Session["MyServiceUsage"] = ((int) Session["MyServiceUsage"]) + 1;

 

Code will look similar to follows:

 

using System.Web.Services;

public class ServerUsage : WebService

{

 

[WebMethod(Description = "Number of times a particular client session has accessed this Web service method.", EnableSession = true)]

    public int PerSessionServiceUsage()

    {

        // If the Web service method hasn't been accessed, initialize

        // it to 1.

        if (Session["MyServiceUsage"] == null)

        {

            Session["MyServiceUsage"] = 1;

        }

        else

        {

            // Increment the usage count.

            Session["MyServiceUsage"] = ((int)Session["MyServiceUsage"]) + 1;

        }

        return (int)Session["MyServiceUsage"];

    }

}

The web methods could be tested by navigating to .asmx page.