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:
    public class CrumbTrailKeyValuePair<TKey, TValue>
    {
        public CrumbTrailKeyValuePair() { }
        public CrumbTrailKeyValuePair(TKey key, TValue value)
        {
            Key = key;
            Value = value;
        }
        public TKey Key { get; set; }
        public TValue Value { get; set; }
    }

    public class CrumbTrailAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // skip recording Account controller actions
                if (filterContext.RouteData.Values["controller"] != null && 
                    filterContext.RouteData.Values["controller"].ToString() != "Account")
                {
                    // put in cookies
                    Queue<CrumbTrailKeyValuePair<string, string>> crumbTrailQueue = 
                       new Queue<CrumbTrailKeyValuePair<string, string>>();
                    HttpCookie crumbTrailCookie = 
                       filterContext.HttpContext.Request.Cookies["CrumbTrailLinks"] ?? new HttpCookie("CrumbTrailLinks");
 
                    // initialize serializer
                    var serializer = new JavaScriptSerializer();
 
                    // if crumbTrailCookie is not empty, retrieve value from cookie the rehydrate queue
                    if (crumbTrailCookie != null && !string.IsNullOrEmpty(crumbTrailCookie.Value))
                    {
                        // rehydrate crumbTrailQueue with cookie value         
                        crumbTrailQueue = 
                           new Queue<CrumbTrailKeyValuePair<string, string>>
                              (serializer.Deserialize<IEnumerable<CrumbTrailKeyValuePair<string, string>>>
                                 (HttpUtility.UrlDecode(crumbTrailCookie.Value)));
                    }
 
                    if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "GET")
                    {
                        // get page title
                        var pageTitle = string.IsNullOrEmpty(filterContext.Controller.ViewBag.Title) ? 
                           "PAGE" : filterContext.Controller.ViewBag.Title;
 
                        // get url
                        string url = filterContext.HttpContext.Request.RawUrl;
 
                        // if current page is not in both queue, then add it
                        if (!crumbTrailQueue.Any(x => x.Value == url && x.Key == pageTitle))
                        {
                            // remove oldest menu item, keep queue length to 6
                            if (crumbTrailQueue.Count >= 5)
                                crumbTrailQueue.Dequeue();
 
                            // insert new menu item into queue
                            crumbTrailQueue.Enqueue(new CrumbTrailKeyValuePair<string, string>(pageTitle, url));
                        }
 
                        crumbTrailCookie.Value = serializer.Serialize(crumbTrailQueue);
                        crumbTrailCookie.Expires = DateTime.Now.AddDays(365);
                        crumbTrailCookie.Path = "/";
                        filterContext.HttpContext.Response.Cookies.Add(crumbTrailCookie);
                    }
 
                    // put in viewbag
                    if (filterContext.Result.GetType().Name == "ViewResult")
                    {
                        (filterContext.Result as ViewResult).ViewBag.QuickAccessQueue = crumbTrailQueue;
                    }
                }
            }
        }
    }
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: