How to Track Marketing Events

The redir.aspx Web page handles redirects that result from clicking campaign items (advertisements and discounts) that are displayed on the site. You should use or modify redir.aspx to track click events on your Commerce Server Core Systems Web site.

Requests to redir.aspx are handled by first calling the CSFRecordEvent pipeline to record the fact that the campaign item was clicked, and then redirecting the request to the URL specified in the campaign item.

Be aware that if the page is accessed outside the CSF context, or if the necessary caches and pipelines required for the page to execute are not set up, this page will internally generate an errors collection describing the reasons for failure to run the RecordEvent pipeline.

See the redir.aspx example later in this topic for a working code sample.

Redir.aspx.cs code workflow

  1. Copy the URL query string variables campaignItemId, pageGroupId, cacheName, eventType (default is 'click' event), and redirectURL into local variables.

  2. Create Order and Context dictionaries to pass through the pipeline. Create the dictionaries using the Primary Interop Assembly (PIA) for the dictionary (Microsoft.CommerceServer.Runtime.DictionaryClass).

  3. Set the _winners, _event, _eventcount, _content, and _performance keys in the Order dictionary (the latter two should come from the content cache for ads or discounts).

  4. Optionally set the SiteName and PageGroupId keys in the context dictionary. Retrieve the pipeline to execute directly from the pipelines collection (CommerceContent.Current.Pipelines).

  5. Call the CSFRecordEvent pipeline to record the fact that the Campaign Item was clicked.

  6. Use Marshal.ReleaseComObject to Release the COM dictionary objects.

  7. If supplied, use Response.Redirect to redirect to the URL supplied in the redirectURL query string parameter.

Example

/*=========================================================================
  FileName : Redir.aspx.cs
=========================================================================*/
using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Microsoft.CommerceServer.Interop.Caching;
using Microsoft.CommerceServer.Runtime;
using Microsoft.CommerceServer.Runtime.Caching;
using Microsoft.CommerceServer.Runtime.Pipelines;

/// <summary>
/// This page handles redirects that result from Clicking on Displayable Campaign Items (Advertisements and Discounts)
/// that are displayed on the site. 
/// Requests to this page are handled by first calling the 'RecordEvent' Pipeline to record the fact that the Campaign Item
/// was clicked, and then redirecting the Request to the Url specified in the Campaign Item.
/// Be aware that if the page is accessed outside the CSF context, or if the necessary caches and pipelines required for 
/// the page to execute are not set up, this page will internally generate an errors collection describing the reasons for
/// failure to run the RecordEvents pipeline. Currently, these errors are logged using Debug.WriteLine and Trace.Warn. 
/// Web site Administrators may want to track these errors differently, or in more detail if considered appropriate. 
/// A high volume of errors on this page could indicate badly authored campaign items, or potential attacks on the site.
/// </summary>
public partial class CSFRedirectPage : System.Web.UI.Page
{
#region internal message strings

/// <summary>
/// These messages are hard-wired into this code file because they are currently only being used for tracing purposes.
/// If these messages will be thrown as exceptions or displayed to the end-user, consider moving them to 
/// a resource file in the Web application and use a Resource Manager to retrieve them.
/// </summary>
private abstract class Messages
{
public const string CommerceContextNull = "CommerceContext.Current is null.";
public const string PipelineNotFound = "A pipeline named '{0}' was not found in CommerceContext.Current.Pipelines.";
public const string CacheNotFound = "A cache named '{0}' was not found in CommerceContext.Current.Caches.";
public const string QueryStringValidationError = "The key '{0}' was either not found, or was invalid in the redirect page query string. The query string is below:\n{1}";
}

#endregion internal message strings

/// <summary>
/// This class handles the CSF redirect logic by parsing the query string. The RecordEvent method on this class
/// records the information from the query string into the Marketing System. If the RecordEvent fails, it 
/// returns false. The ErrorsCollection can be used to retrieve more details about the errors that occured.
/// </summary>
private class CSFRedirectHandler
{
#region constants

private const string QueryStringKeyCacheName = "cachename";
private const string QueryStringKeyCampaignItemId = "ciid";
private const string QueryStringKeyEventType = "evt";
private const string QueryStringKeyPageGroupId = "PageGroupId";
private const string QueryStringKeyRedirectUrl = "url";
private const string DefaultEventType = "CLICK";

#endregion constants

#region fields

int campaignItemId;
int pageGroupId;
string eventType;
string cacheName;
Uri redirectUrl;
private List<string> errorsCollection;

#endregion fields

#region properties

private int CampaignItemId
{
get 
{ 
return this.campaignItemId; 
}
}

private int PageGroupId
{
get
{ 
return this.pageGroupId; 
}
}

private string EventType
{
get 
{ 
return this.eventType; 
}
}

private string CacheName
{
get 
{ 
return this.cacheName; 
}
}

private bool IsValidRedirectQuery
{
get
{
return (this.errorsCollection.Count == 0);
}
}

#endregion properties

#region constructor

/// <summary>
/// Constructs an instance of the RedirectHandler using the query string.
/// </summary>
/// <param name="queryString"></param>
public CSFRedirectHandler(System.Collections.Specialized.NameValueCollection queryString)
{
this.errorsCollection = new List<string>();

this.campaignItemId = GetMarketingIdFromQueryString(queryString, QueryStringKeyCampaignItemId);

this.pageGroupId = GetMarketingIdFromQueryString(queryString, QueryStringKeyPageGroupId);

this.cacheName = queryString[QueryStringKeyCacheName];
if(string.IsNullOrEmpty(this.cacheName))
{
LogInvalidKeyInQueryString(QueryStringKeyCacheName, queryString);
}

//optional event type - by default we set it to click
this.eventType = queryString[QueryStringKeyEventType];
if(string.IsNullOrEmpty(this.eventType))
{
Debug.WriteLine("No Event Type specified in Redirect Query. Assuming 'CLICK' event");
this.eventType = DefaultEventType;
}

//optional redirect uri, by default we will just not do a redirect
string redirectUrl = queryString[QueryStringKeyRedirectUrl];
Uri.TryCreate(redirectUrl, UriKind.RelativeOrAbsolute, out this.redirectUrl);
if (this.redirectUrl == null)
{
LogInvalidKeyInQueryString(QueryStringKeyRedirectUrl, queryString);
}
}

#endregion constructor

#region public methods

/// <summary>
/// Returns the Redirect Url that was specified in the query string.
/// </summary>
public Uri RedirectUrl
{
get
{
return this.redirectUrl;
}
}

/// <summary>
/// A collection of errors that occured during processing of the query string and the RecordEvent pipeline 
/// (if any), or an empty collection.
/// </summary>
public IList<string> ErrorsCollection
{
get
{
return errorsCollection.AsReadOnly();
}
}

/// <summary>
/// Executes the record event pipeline. 
/// </summary>
/// <returns>Returns true if successful. False if there were errors.</returns>
public bool RecordEvent()
{
const string RecordEventPipelineName = "recordevent";
const string DictKeyWinners = "_winners";
const string DictKeyEvent = "_event";
const string DictKeyPerformance = "_performance";
const string DictKeyFactory = "factory";
const string DictKeySiteName = "SiteName";
const string DictKeyPageGroupId = "PageGroupId";
const string DictKeyContentList = "_content";
const string DictKeyCache = "_cache";

if(!this.IsValidRedirectQuery)
{
return false;
}

if (CommerceContext.Current == null)
{
this.errorsCollection.Add(Messages.CommerceContextNull);
return false;
}

PipelineBase recordEventPipeline = CommerceContext.Current.Pipelines[RecordEventPipelineName];
if (recordEventPipeline == null)
{
this.errorsCollection.Add(string.Format(System.Globalization.CultureInfo.CurrentCulture,
Messages.PipelineNotFound, RecordEventPipelineName));
return false;
}

CommerceCache csfCache = CommerceContext.Current.Caches[this.CacheName];
if (csfCache == null)
{
this.errorsCollection.Add(string.Format(System.Globalization.CultureInfo.CurrentCulture,
Messages.CacheNotFound, this.CacheName));
return false;
}

IDictionary contextDictionary = null;
IDictionary orderDictionary = null;
IContentListFactory contentListFactory = null;
IContentList contentList = null;
try
{
//create context dictionary and set up keys 
contextDictionary = new DictionaryClass();
contextDictionary[DictKeySiteName] = CommerceContext.Current.SiteName;
contextDictionary[DictKeyPageGroupId] = this.PageGroupId;

//get the cache dictionary
//we should not release this dictionary because it is referenced
//by CommerceContext.Current. It will be released when 
//CommerceContext.Current is disposed
IDictionary csfCacheDictionary = (IDictionary)csfCache.GetCache();
contextDictionary[DictKeyCache] = csfCacheDictionary;

//create the order dictionary and set up keys
orderDictionary = new DictionaryClass();
orderDictionary[DictKeyWinners] = this.CampaignItemId;
orderDictionary[DictKeyEvent] = this.EventType;
orderDictionary[DictKeyPerformance] = csfCacheDictionary[DictKeyPerformance];
contentListFactory = (IContentListFactory)csfCacheDictionary[DictKeyFactory];
contentList = contentListFactory.CreateNewContentList();
orderDictionary[DictKeyContentList] = contentList;
recordEventPipeline.Execute(orderDictionary, contextDictionary);
return true;
}
finally
{
if (contextDictionary != null)
{
Marshal.ReleaseComObject(contextDictionary);
}
if (orderDictionary != null)
{
Marshal.ReleaseComObject(orderDictionary);
}
if (contentListFactory != null)
{
Marshal.ReleaseComObject(contentListFactory);
}
if (contentList != null)
{
Marshal.ReleaseComObject(contentList);
}
}
}

#endregion public methods

#region helper methods

private void LogInvalidKeyInQueryString(string keyName,
System.Collections.Specialized.NameValueCollection queryString)
{
string queryStringDisplay;
if (queryString == null || queryString.Count == 0)
{
queryStringDisplay = "<null>";
}
else
{
queryStringDisplay = queryString.ToString();
}
this.errorsCollection.Add(string.Format(System.Globalization.CultureInfo.CurrentCulture,
Messages.QueryStringValidationError, keyName, queryStringDisplay));
}

private int GetMarketingIdFromQueryString(System.Collections.Specialized.NameValueCollection queryString, 
string keyName)
{
int tempResult = -1;
string value = queryString[keyName];
if (!string.IsNullOrEmpty(value))
{
Int32.TryParse(value, out tempResult);
}
if (tempResult < 0)
{
LogInvalidKeyInQueryString(keyName, queryString);
}
return tempResult;
}

#endregion helper methods 
}

    protected void Page_Load(object sender, EventArgs e)
    {
CSFRedirectHandler redirectHandler = new CSFRedirectHandler(Request.QueryString);

if (!redirectHandler.RecordEvent())
{
//Sites should do more detailed error handling and logging
foreach (string error in redirectHandler.ErrorsCollection)
{
Debug.WriteLine(error);
Trace.Warn(error);
}
}
//finally, lets try to redirect the user if a valid redirect url was specified
if (redirectHandler.RedirectUrl != null)
{
Response.Redirect(redirectHandler.RedirectUrl.OriginalString);
}
    }
}

See Also

Other Resources

Marketing Run-time Scenarios

CSFRecordEvent