Monday, April 1, 2013

Crumbtrail ActionFilter

Recently, I had to make a crumb-trail in the web application that I am working on (ASP.NET MVC). There are multiple ways of doing this and initially I elected to do this in my controller base class (which is inherited by all my controller classes). I created a method that do the job - but this means that this method has to be called on every single action (with GET method). If a fellow developer miss to call the method, then it would mean that the data in the crumb-trail is not built properly or accurately. If there is just an interceptor that I can hook into that will run automatically every time a controller action is being called ... *sigh

Wait - there is one, ActionFilter!!

So I created an action filter and via configuration register and apply it to all controllers. The logic in my code handles the exceptions to the apply all (such as Account controller, POST method, and non-authenticated users). Here is the code to the ActionFilter code:
  1. public class CrumbTrailKeyValuePair<TKey, TValue>
  2. {
  3. public CrumbTrailKeyValuePair() { }
  4. public CrumbTrailKeyValuePair(TKey key, TValue value)
  5. {
  6. Key = key;
  7. Value = value;
  8. }
  9. public TKey Key { get; set; }
  10. public TValue Value { get; set; }
  11. }
  12.  
  13. public class CrumbTrailAttribute : ActionFilterAttribute
  14. {
  15. public override void OnResultExecuting(ResultExecutingContext filterContext)
  16. {
  17. if (filterContext.HttpContext.User.Identity.IsAuthenticated)
  18. {
  19. // skip recording Account controller actions
  20. if (filterContext.RouteData.Values["controller"] != null &&
  21. filterContext.RouteData.Values["controller"].ToString() != "Account")
  22. {
  23. // put in cookies
  24. Queue<CrumbTrailKeyValuePair<string, string>> crumbTrailQueue =
  25. new Queue<CrumbTrailKeyValuePair<string, string>>();
  26. HttpCookie crumbTrailCookie =
  27. filterContext.HttpContext.Request.Cookies["CrumbTrailLinks"] ?? new HttpCookie("CrumbTrailLinks");
  28. // initialize serializer
  29. var serializer = new JavaScriptSerializer();
  30. // if crumbTrailCookie is not empty, retrieve value from cookie the rehydrate queue
  31. if (crumbTrailCookie != null && !string.IsNullOrEmpty(crumbTrailCookie.Value))
  32. {
  33. // rehydrate crumbTrailQueue with cookie value
  34. crumbTrailQueue =
  35. new Queue<CrumbTrailKeyValuePair<string, string>>
  36. (serializer.Deserialize<IEnumerable<CrumbTrailKeyValuePair<string, string>>>
  37. (HttpUtility.UrlDecode(crumbTrailCookie.Value)));
  38. }
  39. if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "GET")
  40. {
  41. // get page title
  42. var pageTitle = string.IsNullOrEmpty(filterContext.Controller.ViewBag.Title) ?
  43. "PAGE" : filterContext.Controller.ViewBag.Title;
  44. // get url
  45. string url = filterContext.HttpContext.Request.RawUrl;
  46. // if current page is not in both queue, then add it
  47. if (!crumbTrailQueue.Any(x => x.Value == url && x.Key == pageTitle))
  48. {
  49. // remove oldest menu item, keep queue length to 6
  50. if (crumbTrailQueue.Count >= 5)
  51. crumbTrailQueue.Dequeue();
  52. // insert new menu item into queue
  53. crumbTrailQueue.Enqueue(new CrumbTrailKeyValuePair<string, string>(pageTitle, url));
  54. }
  55. crumbTrailCookie.Value = serializer.Serialize(crumbTrailQueue);
  56. crumbTrailCookie.Expires = DateTime.Now.AddDays(365);
  57. crumbTrailCookie.Path = "/";
  58. filterContext.HttpContext.Response.Cookies.Add(crumbTrailCookie);
  59. }
  60. // put in viewbag
  61. if (filterContext.Result.GetType().Name == "ViewResult")
  62. {
  63. (filterContext.Result as ViewResult).ViewBag.QuickAccessQueue = crumbTrailQueue;
  64. }
  65. }
  66. }
  67. }
  68. }
Inside the view, just get the queue from the ViewBag and display accordingly. The queue is stored temporarily in a cookie, so it will be remembered even when the browser is closed and reopen and relogin.

No comments: