Creating an HTTP Module for Presentation Tier Polling

The presentation tier requires a unique HTTP module to poll the Commerce Foundation operation service for pending cache refresh requests and clear stale caches.

For the Solution Storefront and for custom SharePoint 2010 sites based on the Solution Storefront, the HTTP module configuration is already added to the <httpModules> or <modules> section of Web.config:

<add name="CacheRefreshModule" type="Microsoft.Commerce.Portal.Common.CacheRefreshModule, Microsoft.Commerce.Portal.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

If your site is an ASP.NET site or a SharePoint 2010 site that was not built using the SharePoint Commerce Services Extensibility Kit, you must build a custom HTTP module to handle the presentation tier polling and cache refresh.

This topic covers the following:

  • Considerations for Using Presentation Tier Polling for Cache Refresh

  • Building a Custom HTTP Module for Presentation Tier Polling

  • Adding the HTTP Module configuration to Web.config

Considerations for Using Presentation Tier Polling For Cache Refresh

Before implementing presentation tier polling, always consider any form of caching that may be used in the Commerce Core Systems. (For information about the core systems caches, see Understanding Core Systems Caching.) The effect of configuring caching in various areas of a deployment must be considered from an end-to-end perspective to avoid serving staled information to presentation clients.

Hh567803.alert_caution(en-us,CS.95).gifImportant Note:

Presentation tier polling for cache refresh does not support per item cache timeout. For more information about configuring timeouts for Catalog caching, see Caching in the Catalog System.

Building a Custom HTTP Module for Presentation Tier Polling

If you are implementing cache refresh on an ASP.NET site or a SharePoint 2010 site that was not built with the SharePoint Commerce Services Extensibility Kit, you must create a custom HTTP module to handle the presentation tier polling and cache refresh.

The sample code provided in the following example uses an HTTP module as a mechanism to poll the Commerce Foundation operation service for pending cache refresh requests and clear affected caches in the presentation tier for the specified channel.

When implementing this C# sample code, you must do the following:

  • Specify the channel name associated with the commerce site caches to be refreshed if it is other than the default channel.

  • Write code to log error messages if the query for pending cache refresh requests fails.

  • Write code to clear the presentation tier caches

  • Add the HTTP module configuration to the Web.config of the ASP.NET or SharePoint 2010 site.

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.

namespace Sample
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Threading;
    using System.Web;
    using Microsoft.Commerce;
    using Microsoft.Commerce.Server.Configuration;

    /// <summary>
    /// This HttpModule creates a thread that will poll the foundation at regular intervals and delete affected caches when needed.
    /// </summary>
    public class CacheRefreshPollingModule : IHttpModule
    {       
        #region Members

        private const string DefaultChannelName = "Default";
        private static object lockObj = new object();
         
        #endregion
        
        #region Properties
       
        /// <summary>
        /// Gets the name of the current channel.
        /// </summary>
        /// <value>The name of the current channel.</value>
        protected virtual string CurrentChannelName
        {
            get
            {
                return DefaultChannelName;
            }
        }
              
        #endregion

        #region Methods

        /// <summary>
        /// Initializes the specified HTTP application.
        /// </summary>
        /// <param name="httpApplication">The HTTP application.</param>
        public void Init(HttpApplication httpApplication)
        {                 
            if (CommerceCacheRefreshSection.Current.Enabled)   
            {                
                CacheRefreshWorker cacheRefreshWorker = new CacheRefreshWorker(
                                this.CurrentChannelName, 
                                CommerceCacheRefreshSection.Current.RefreshPollingInterval);
                Thread workerThread = new Thread(cacheRefreshWorker.Begin);
                workerThread.Start();
            }
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
        }
                          
        #endregion

        #region Worker Class

        /// <summary>
        /// Cache Refresh Worker class
        /// </summary>
        private class CacheRefreshWorker
        {
            #region Members
            private static object lockObj = new object();
            private OperationServiceAgent agent = null;
            private int currentInterval = 30000;
            private string currentChannelName = "Default";
            private long lastCacheStamp = 0;
            #endregion
            
            #region Constructor

            /// <summary>
            /// Initializes a new instance of the <see cref="CacheRefreshWorker"/> class.
            /// </summary>
            /// <param name="channelName">Name of the channel.</param>
            /// <param name="interval">The interval in seconds.</param>
            public CacheRefreshWorker(string channelName, int interval)
            {
                this.currentChannelName = channelName;
                this.currentInterval = interval * 1000;
            }

            #endregion
           
            #region Methods

            /// <summary>
            /// Begins this instance.
            /// </summary>
            public void Begin()
            {
                try
                {
                    this.lastCacheStamp = this.GetLastCacheStamp(this.currentChannelName);
                }
                catch (Exception)
                {
                    // Log Error
                }
                
                this.CheckCommerceCacheStatus();
            }

            /// <summary>
            /// Checks the commerce cache status.
            /// </summary>
            private void CheckCommerceCacheStatus()
            {
                while (true)
                {
                    System.Threading.Thread.Sleep(this.currentInterval);
                
                    try
                    {
                        long newCacheStamp = 0;
                        List<string> cacheNames = this.RetrieveCacheNamesToBeRefreshed(this.lastCacheStamp, this.currentChannelName, out newCacheStamp);

                        if (cacheNames != null && cacheNames.Count > 0)
                        {
                            this.RefreshCaches(cacheNames, this.currentChannelName);
                            this.lastCacheStamp = newCacheStamp;
                        }
                    }
                    catch (Exception)
                    {
                        // Log Error
                    }
                }               
            }

            /// <summary>
            /// Gets the last cache stamp.
            /// </summary>
            /// <param name="channelName">Name of the channel.</param>
            /// <returns>The lastest cache stamp.</returns>
            private long GetLastCacheStamp(string channelName)
            {
                long cacheStamp = 0;
                this.RetrieveCacheNamesToBeRefreshed(0, channelName, out cacheStamp);
                return cacheStamp;
            }

            /// <summary>
            /// Retrieves the cache names to be refreshed.
            /// </summary>
            /// <param name="lastCacheStamp">The last stamp filter.</param>
            /// <param name="channelName">Name of the channel.</param>
            /// <param name="newCacheStamp">The cache stamp.</param>
            /// <returns>List of Cache.</returns>
            private List<string> RetrieveCacheNamesToBeRefreshed(long lastCacheStamp, string channelName, out long newCacheStamp)
            {
                List<string> cacheNames = new List<string>();

                CommerceQuery<CommerceEntity> queryCacheNames =
                    new CommerceQuery<CommerceEntity>("CommerceCache");
                queryCacheNames.SearchCriteria.Model.Properties["CacheStamp"] = lastCacheStamp;

                CommerceResponse response = this.ProcessRequest(queryCacheNames.ToRequest(), channelName);
                CommerceQueryOperationResponse queryResponse = response.OperationResponses[0] as CommerceQueryOperationResponse;

                if (queryResponse.CommerceEntities != null && queryResponse.CommerceEntities.Count > 0)
                {
                    newCacheStamp = lastCacheStamp;
                    foreach (CommerceEntity entity in queryResponse.CommerceEntities)
                    {
                        cacheNames.Add((string)entity.Id);
                        newCacheStamp = Convert.ToInt64(entity.Properties["CacheStamp"], CultureInfo.InvariantCulture);
                    }
                }
                else
                {
                    // Set it as the lastCacheStamp used for this query.
                    newCacheStamp = lastCacheStamp;
                }

                return cacheNames;
            }

            /// <summary>
            /// Refreshes the specifed caches.
            /// </summary>
            /// <param name="cacheNames">The cache names.</param>
            /// <param name="channelName">Name of the channel.</param>
            private void RefreshCaches(List<string> cacheNames, string channelName)
            {
                if (cacheNames == null)
                {
                    return;
                }

                //// Insert any Presentation Layer Cache Refresh code here               
            }

            /// <summary>
            /// Processes the operation request.
            /// </summary>
            /// <param name="request">The operation request.</param>
            /// <param name="channelName">Name of the channel.</param>
            /// <returns>The operation response.</returns>
            private CommerceResponse ProcessRequest(CommerceRequest request, string channelName)
            {
                if (this.agent == null)
                {
                    lock (lockObj)
                    {
                        if (this.agent == null)
                        {
                            this.agent = new OperationServiceAgent();
                        }
                    }
                }

                CommerceRequestContext context = new CommerceRequestContext();
                context.RequestId = Guid.NewGuid().ToString("B");
                context.Channel = channelName == DefaultChannelName ? null : channelName;
                context.UserLocale = CultureInfo.CurrentCulture.ToString();
                context.UserUILocale = CultureInfo.CurrentUICulture.ToString();

                return this.agent.ProcessRequest(context, request);
            }

            #endregion
        }

        #endregion
    }   
}

Adding the HTTP Module configuration to Web.config

Once you have compiled the code for your custom HTTP module, you must reference the HTTP module configuration in Web.config under configuration\system.webServer.

<add name="CacheRefreshModule" type="Samples.CacheRefreshPollingModule, CacheRefreshPollingModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c1003ff8655cfee8" />

If necessary, adjust the namespace, assembly name, version and token according to your implementation.

Depending on your deployment scenario, the HTTP module must appear in the appropriate configuration file(s):

Deployment scenario

Where to specify HTTP module

Three-tier and three-tier Web farm

Web.config file for the web application on the presentation tier (each node for Web farm)

Two-tier and two-tier Web farm

Web.config file for the web application (each node for Web farm)

See Also

Other Resources

Working with Cache Refresh (Pull or Polling Model)

About the CommerceCache Entity

Overview of CommerceCache Operation Sequences

Using APIs to Manage Commerce Server Caches

Configuring Authorization Security for the CommerceCache Entity

Modifying the Cache Refresh Configuration