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.
Important 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