Created
January 12, 2012 03:10
-
-
Save bwiggs/1598333 to your computer and use it in GitHub Desktop.
MVC Split AB Test Controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Web; | |
using System.Web.Mvc; | |
public abstract class SplitABController : Controller | |
{ | |
private static readonly Results results = new Results(); | |
private const string B_TEST_SUFFIX = "_B"; | |
private const string SPLIT_TEST_VIEW_COOKIE_NAME = "SPLIT_TEST_VIEW_COOKIE"; | |
private static ViewFilesCache cache; | |
public static Results GetSplitTestResults() { return results; } | |
protected over ride void OnActionExecuting(ActionExecutingContext filterContext) | |
{ | |
AddSplitTestResultsToResultsMap(filterContext); | |
base.OnActionExecuting(filterContext); | |
} | |
private void AddSplitTestResultsToResultsMap(ActionExecutingContext filterContext) | |
{ | |
// If last request was not for a split test view then just return | |
if (Request.Cookies[SPLIT_TEST_VIEW_COOKIE_NAME] == null) return; | |
// Add this controller/action to the results of the split test | |
ResultRow rr = ResultRow.FromString(Request.Cookies[SPLIT_TEST_VIEW_COOKIE_NAME].Value); | |
results.RemoveOneFromResults(rr); | |
rr.ToControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; | |
rr.ToAction = filterContext.ActionDescriptor.ActionName; | |
results.AddOneToResults(rr); | |
} | |
/// <summary> | |
/// If the view has a _B counterpart then we mark this action as a 'Split Test' we | |
/// then record the results of this split view in the next request (OnActionExecuting). | |
/// | |
/// This method also determines if we should supply the A or B view depending on the | |
/// 'ShouldRequestUseBView' algorithm. | |
/// </summary> | |
protected override ViewResult View(string viewName, string masterName, object model) | |
{ | |
if (String.IsNullOrEmpty(viewName)) { viewName = (string) RouteData.Values["action"]; } | |
bool isSplitTestingView = IsSplitTestingView(masterName, viewName); | |
// If this view does not have a _B counterpart then we mark this request as non split test (by | |
// removing the 'SPLIT_TEST_VIEW_COOKIE_NAME' cookie) and just send control to base.View | |
if (!isSplitTestingView) | |
{ | |
Response.Cookies.Remove(SPLIT_TEST_VIEW_COOKIE_NAME); | |
return base.View(viewName, masterName, model); | |
} | |
// Wether to use the A or B view depending on the 'ShouldRequestUseBView' algorithm | |
bool useb = ShouldRequestUseBView(); | |
// Lets create a results row and store it in the cookie (SPLIT_TEST_VIEW_COOKIE_NAME). This will let the | |
// next request (OnActionExecuting) know that we just hit a split test view. | |
ResultRow rr = new ResultRow {FromController = this, FromAction = viewName, UsedBView = useb}; | |
Response.Cookies.Add(new HttpCookie(SPLIT_TEST_VIEW_COOKIE_NAME, rr.ToString())); | |
results.AddOneToResults(rr); | |
// Display the appropriate view (A or B) | |
return useb | |
? base.View(viewName + B_TEST_SUFFIX, masterName, model) | |
: base.View(viewName, masterName, model); | |
} | |
/// <summary> | |
/// Returns wether the specified view has a _B counterpart. | |
/// </summary> | |
private bool IsSplitTestingView(string masterName, string viewName) | |
{ | |
if (cache == null) { lock (GetType()) { if (cache == null) { cache = new ViewFilesCache(B_TEST_SUFFIX); } } } | |
return cache.HasSplitTestingAlternative(this, masterName, viewName); | |
} | |
/// <summary> | |
/// If odd IP then use 'B' view. This will give a ~50% A / B split. | |
/// </summary> | |
private bool ShouldRequestUseBView() | |
{ | |
return Int32.Parse(Request.UserHostAddress.Substring(Request.UserHostAddress.LastIndexOf('.') + 1)) % 2 == 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment