Saturday, November 15, 2008

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.

Array Serialization

For the private array variable I tried writing the indexed property:

  public float this [int index]

   {

     get

        {

          return m_Price[index];

         }

     set

        {

          m_Price[index] = value;

        }

   }

Still this is not sufficient. The get/set proerty must be written as:

        public float[] Price

        {

            get

            {

                return m_Price;

            }

            set

            {

                m_Price = value;

            }

        }

Interestingly, the MSDN document for Serializable Attribute states that: “All the public and private fields in a type that are marked by the SerializableAttribute are serialized by default, unless the type implements the ISerializable interface to override the serialization process.”

More about SerializableAttribute on: http://msdn2.microsoft.com/en-us/library/system.serializableattribute(vs.80).aspx

If the attribute serializes all public, private fields then why get/set is required? L

While sending custom object from web service, the serialization of object could also be achieved by using “System.Xml.Serialization.XmlInclude” instead of “Serializable” attribute.

The code like

[System.Xml.Serialization.XmlInclude(typeof(MarketInformation. PriceInformation))];

should be added instead of [Serializable] attribute.

 

If you don’t have access to the class code base then it could be added on top of the web method using it. Like following:

 

[WebMethod]

[System.Xml.Serialization.XmlInclude(typeof(MarketInformation. PriceInformation))];

public PriceInformation GetPriceInfo()

    {

        PriceInformation price = new PriceInformation ();

        return price;

    }

The member variables will require get and set proerties same as in case of Serializable attribute.