From 9482304311303fe96823d8bc1c585b70c4006915 Mon Sep 17 00:00:00 2001 From: JPTouron Date: Fri, 30 Jun 2017 09:45:00 -0300 Subject: [PATCH 1/2] - Overridden OnResultExecuted on BreadCrumbAttribute class to remove the crumb when the action result fails - Cleaned up code --- MvcBreadCrumbs.v12.suo | Bin 137216 -> 137216 bytes MvcBreadCrumbs/BreadCrumb.cs | 30 +-- MvcBreadCrumbs/BreadCrumbAttribute.cs | 109 +++++---- MvcBreadCrumbs/HttpSessionProvider.cs | 50 ++-- MvcBreadCrumbs/IProvideBreadCrumbsSession.cs | 16 +- MvcBreadCrumbs/ResourceHelper.cs | 72 +++--- MvcBreadCrumbs/State.cs | 241 ++++++++++--------- MvcBreadCrumbs/StateManager.cs | 72 +++--- 8 files changed, 303 insertions(+), 287 deletions(-) diff --git a/MvcBreadCrumbs.v12.suo b/MvcBreadCrumbs.v12.suo index d460200c5f0b35b9396cebc60e607e106e9fbea0..1b0ee686dbd5e5603a2ce4ee9a6d227c3beb3219 100644 GIT binary patch delta 7006 zcmcf`ZBSg*^_^u|79?Q_gnV>?O$hlQy!Gww19nM5*e^Z;0Rki@x=LPl7uXn<&F))` zXoiiGYIIVY`2U4rd*|a28gOsAf{ri9BmHWFX^#jmr@I42QERraw!teTtrsxB(#- zL5ncezCMXxmwbPISXx~;7m1|^B?x&4*$8R`CZvWu+;S6Y^r`lG*i2SsuFx~FTd?;A z1OSe^G0B;Q_+jbjkt}f`W0``(*nI@y6as0^_QW60dMHC($f$j#aMV*F+|CF%5HE_8c;hJ3wP6K^^~O zkyd?VrAy6}Pt07_$=>iC^6|=6Epx=;sTqm9sx=ezNajy9X?}!Q6yg_)HQLc47ie*< zxCK+a=^cof$Q{LZYT6Oga`B6+3RFb5)CWMKu0hR8)>=j`E^Q<)r@# zHXh77vb-UoQF*7D`B$+D4v~+`JDA<@T@qa}nEn9vsNJ)3TN0zu=oFXw$AhiYcSW&@>^IK93A6*q}8aS~S%`CK_X!exzKy${hX@lS;}RcaQD%3yr~Vy6+$n(QpearOz~hp8k6y)X<|X@D#G0se@v2+^dIync4$KyXIR^ zBLSfT|64f|$7mDT{XL$vjh0be2RsG>xp}yXoZpQYHEjbs+5Y37V59WPe4=|@O+sVK z;M+4*Vc!n}i%dxs%&3A1p#o`MlI5$R;hx>2Z zz-{EVU}PF_0uPb87EnhQEGHvjJ$d2h%o6F9&1CyeUqWnOZ~?8o5%NjP3wk)5GX3d@ z6BQ`hxr~fF#}qhm&k|DpxE^9Nl?X>~C~fB?ZU6&}h137{-uYu$bh{n2By>s-2WKkt z%(H)=`QR%rvEiX*sGqVZo<(%~I?#{}x9H)XnW}q5iFC*X3(2qd=!wvmSd?$kkxr;1 zpFhFKb=}vK@)w`G6eP_vxBkqtza_7pX-kA3l`Jqu$34oXy0m|^HNjYl7MZJe%K2-s_TXjJ#-Gt^!~RYo9cdpWH10r-Q%;N z9jkGUwLmo%#Cc=yo`qY}=tt*ZKMaw#s*7oD2x`et;dc7!Iaux=!pm{qC+wmitrMZ#eKN~Ll))H;0-D- zhn0^ZrCi*J3+5dg-wjWyH&r7O<72QNyh@Q7#U=jgBGdO3MP?5dz;e@oGb=?VHAGv)Rqs;oq?9MvJgWAb>BFiYRkM3)MJi6^XY)&(CJe|B9R?(V0P{!^<m69Vh*)pgu}T&=2VN2wUB%%LeAjU{{Lf>*fE*QwIdJ8ic+}TniyNsrrBZX#_YOd5 z#^mH0E(e|e#vWKe=k103!C9G{+smy%@f>$23cRKn7hYE1)_b9G){KAmURY0GeIM4& zv7nwJx{}?sIO;qA>oQpQFe3K=E{EQy+VSS)7CK=uAYoMe=)Ey%CIoop*VwJ&A3%Gb#m*U$ytx){A&{h5L-&FQyJ{^G76~6!G*)fuJYS zzbh(Z^m_c=m0x1?NxO`!wJ*>UmKaaOV-`%6=o8Uwt`N+&sxHar^ZUa6x8H`FpKXEl z4`MR05fJ#)FQX-=LzZO!MwV0jVf4u+XSHYZ^fcu9A`R6yb{g7Q8hV;SDSz!l(v=~! z>oX`NyeiA+`liwsUy<9M%xDdm(G)v7uFg(_IOCDo(??FT`VXne1y@R{_QJ-tMAmh% ztm{;=Zbu^P;<|TTr9WzCWtPR4pM^@FL5YO&c%wbSn(13?T(l1Gev3;@i zD*a>odc$-7XSfnM=nwYolzpO3l|L?>R4W{orh^Jia2atX3LyVQoD)w`Q zE9EDoY0jm%$MtNz%Jo#tTYjwrlpw~B+L>rt8v?t{upfcgLoNgh<1cvM0b=&%Q{5@3 z8EaRmo&p*hhNWcmnW7B#y&WHV>o644?x$dJIy>2WXZrE#gsp_ujDTj27q3SVyu`3K zunQA?=}{=x`~2NfbeG&E_l4|sTcuI<6B$6o2w6Au+9b zOneMphr=Q5v3aTn#awzYM^c6PM8Z3xf4uqqu|xT)5ZwLzW6#6({{Zo0 B*n$86 delta 1004 zcmYk*NlX(_7zgmac3Or-C@qvo*orJIA}IzG&}c;zV{AaggK~<+Zgm$oTv`_EdsMpdu)biJ)SYfMvyplimn1Jx3tt8aYoCx zo1?9wu{aYEwh0b1;zWpt=mqunp;!#fMla4;>WL9j`5i1lH49=P1_YQx{T;Oz?e#=j z$WmNOhaF;k9Txr{q$u@>@9=F7c%q7^XhnVr-|8Z&m*6s7fvdplT>FBU=Qojef_y%z zJ3`=x)6wjF)=Eypn82TaMqCzlikxvS*>9{8B3T-Wi$V(|>D_D%1@tW}kDB67b|eeE z84_c5a++PbwdfL6@@%?J^yEpbjhty!BF0QFGE} zk$=S@C$y!49vnG_zQC|4+@{b1wRRyk?)l$LB=-PG1( q@HQA(iaa@#qaoY4!P}F?Zj(previousPage); updatedList.Reverse(); - if(updatedList.Count>1) + if (updatedList.Count > 1) return updatedList.Skip(1).First().Url; return null; @@ -97,7 +96,7 @@ public static RedirectResult RedirectToPreviousUrl() if (string.IsNullOrEmpty(updatedList.Skip(1).First().Url)) return new RedirectResult(updatedList.Skip(1).First().Url); } - + return null; } @@ -112,7 +111,6 @@ public static IEnumerable GetOrderedRedirections() public static string Display(string cssClassOverride = "breadcrumb") { - var state = StateManager.GetState(SessionProvider.SessionId); if (state.Crumbs != null && !state.Crumbs.Any()) @@ -135,19 +133,23 @@ public static string Display(string cssClassOverride = "breadcrumb") }); sb.Append(""); return sb.ToString(); - } - public static string DisplayRaw() - { + public static string DisplayRaw(string crumbConcatenator = ">") + { var state = StateManager.GetState(SessionProvider.SessionId); if (state.Crumbs != null && !state.Crumbs.Any()) return ""; - return string.Join(" > ", - state.Crumbs.Select(x => "" + x.Label + "").ToArray()); - + return string.Join(" " + crumbConcatenator + " ", + state.Crumbs.Select(x => + { + if (IsCurrentPage(x.Key)) + return x.Label; + else + return "" + x.Label + ""; + }).ToArray()); } private static bool IsCurrentPage(int compareKey) @@ -158,7 +160,5 @@ private static bool IsCurrentPage(int compareKey) .GetHashCode(); return key == compareKey; } - } - -} +} \ No newline at end of file diff --git a/MvcBreadCrumbs/BreadCrumbAttribute.cs b/MvcBreadCrumbs/BreadCrumbAttribute.cs index a8e2ed6..c8ded1d 100644 --- a/MvcBreadCrumbs/BreadCrumbAttribute.cs +++ b/MvcBreadCrumbs/BreadCrumbAttribute.cs @@ -1,52 +1,59 @@ -using System; -using System.Web.Mvc; - -namespace MvcBreadCrumbs -{ - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] - public class BreadCrumbAttribute : ActionFilterAttribute - { - - public bool Clear { get; set; } - - public string Label { get; set; } - - public Type ResourceType { get; set; } - - private static IProvideBreadCrumbsSession _SessionProvider { get; set; } - - private static IProvideBreadCrumbsSession SessionProvider - { - get - { - if (_SessionProvider != null) - { - return _SessionProvider; - } - return new HttpSessionProvider(); - } - } - - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - - if (filterContext.IsChildAction) - return; - - if (filterContext.HttpContext.Request.HttpMethod != "GET") - return; - - if (Clear) - { - StateManager.RemoveState(SessionProvider.SessionId); - } - - var state = StateManager.GetState(SessionProvider.SessionId); - state.Push(filterContext, Label, ResourceType); - - } - - } - +using System; +using System.Web.Mvc; + +namespace MvcBreadCrumbs +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class BreadCrumbAttribute : ActionFilterAttribute + { + public bool Clear { get; set; } + + public string Label { get; set; } + + public Type ResourceType { get; set; } + + private static IProvideBreadCrumbsSession _SessionProvider { get; set; } + + private static IProvideBreadCrumbsSession SessionProvider + { + get + { + if (_SessionProvider != null) + { + return _SessionProvider; + } + return new HttpSessionProvider(); + } + } + + public override void OnResultExecuted(ResultExecutedContext filterContext) + { + if (filterContext.Exception != null) + { + //if we have an exception on the result (for ANY reason), let's make sure that we don't + //track this failing page on the breadcrumb + var state = StateManager.GetState(SessionProvider.SessionId); + state.OnErrorRemoveCrumb(filterContext); + } + + base.OnResultExecuted(filterContext); + } + + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + if (filterContext.IsChildAction) + return; + + if (filterContext.HttpContext.Request.HttpMethod != "GET") + return; + + if (Clear) + { + StateManager.RemoveState(SessionProvider.SessionId); + } + + var state = StateManager.GetState(SessionProvider.SessionId); + state.Push(filterContext, Label, ResourceType); + } + } } \ No newline at end of file diff --git a/MvcBreadCrumbs/HttpSessionProvider.cs b/MvcBreadCrumbs/HttpSessionProvider.cs index 8681df9..fb5d4b5 100644 --- a/MvcBreadCrumbs/HttpSessionProvider.cs +++ b/MvcBreadCrumbs/HttpSessionProvider.cs @@ -1,27 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; - -namespace MvcBreadCrumbs -{ - public class HttpSessionProvider : IProvideBreadCrumbsSession - { - public string SessionId - { - get - { - var id = HttpContext.Current.Session.SessionID; - var sessionKey = string.Format("{0}-SessionId.MvcBreadCrumbs", id); - - // Apparently you need to actually ad something to session in order to - // stabilize the SessionID between requests, who knew. Just adding SessionID, - // as a dummy. - // Modified Session key to minimize the possibility of hitting existant key - HttpContext.Current.Session[sessionKey] = id; - return id; - } - } - } -} +using System.Web; + +namespace MvcBreadCrumbs +{ + public class HttpSessionProvider : IProvideBreadCrumbsSession + { + public string SessionId + { + get + { + var id = HttpContext.Current.Session.SessionID; + var sessionKey = string.Format("{0}-SessionId.MvcBreadCrumbs", id); + + // Apparently you need to actually ad something to session in order to + // stabilize the SessionID between requests, who knew. Just adding SessionID, + // as a dummy. + // Modified Session key to minimize the possibility of hitting existant key + HttpContext.Current.Session[sessionKey] = id; + return id; + } + } + } +} \ No newline at end of file diff --git a/MvcBreadCrumbs/IProvideBreadCrumbsSession.cs b/MvcBreadCrumbs/IProvideBreadCrumbsSession.cs index bee1717..1b243ca 100644 --- a/MvcBreadCrumbs/IProvideBreadCrumbsSession.cs +++ b/MvcBreadCrumbs/IProvideBreadCrumbsSession.cs @@ -1,9 +1,7 @@ -using System; - -namespace MvcBreadCrumbs -{ - public interface IProvideBreadCrumbsSession - { - string SessionId { get; } - } -} +namespace MvcBreadCrumbs +{ + public interface IProvideBreadCrumbsSession + { + string SessionId { get; } + } +} \ No newline at end of file diff --git a/MvcBreadCrumbs/ResourceHelper.cs b/MvcBreadCrumbs/ResourceHelper.cs index 182e149..a5f1d51 100644 --- a/MvcBreadCrumbs/ResourceHelper.cs +++ b/MvcBreadCrumbs/ResourceHelper.cs @@ -1,37 +1,37 @@ -using System; -using System.Reflection; - -namespace MvcBreadCrumbs -{ - /// - /// Helper class for retrieving the tranlation values - /// - public class ResourceHelper - { - /// - /// Gets the resource lookup. - /// - /// Type of the resource. - /// Name of the resource. - /// - public static string GetResourceLookup(Type resourceType, string resourceName) - { - if ((resourceType != null) && (resourceName != null)) - { - var property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static); - if (property == null) - { - return resourceName; - } - if (property.PropertyType != typeof(string)) - { - return resourceName; - } - - return (string)property.GetValue(null, null); - } - - return resourceName ?? string.Empty; - } - } +using System; +using System.Reflection; + +namespace MvcBreadCrumbs +{ + /// + /// Helper class for retrieving the tranlation values + /// + public class ResourceHelper + { + /// + /// Gets the resource lookup. + /// + /// Type of the resource. + /// Name of the resource. + /// + public static string GetResourceLookup(Type resourceType, string resourceName) + { + if ((resourceType != null) && (resourceName != null)) + { + var property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static); + if (property == null) + { + return resourceName; + } + if (property.PropertyType != typeof(string)) + { + return resourceName; + } + + return (string)property.GetValue(null, null); + } + + return resourceName ?? string.Empty; + } + } } \ No newline at end of file diff --git a/MvcBreadCrumbs/State.cs b/MvcBreadCrumbs/State.cs index 091cff3..e5ce4f8 100644 --- a/MvcBreadCrumbs/State.cs +++ b/MvcBreadCrumbs/State.cs @@ -1,113 +1,130 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; -using System.Web.Mvc; - -namespace MvcBreadCrumbs -{ - public class State - { - public string SessionCookie { get; set; } - public List Crumbs { get; set; } - public StateEntry Current { get; set; } - - public void Push(ActionExecutingContext context, string label, Type resourceType ) - { - var key = - context.HttpContext.Request.Url.LocalPath - .ToLower() - .GetHashCode(); - - if (Crumbs.Any(x => x.Key == key)) - { - var newCrumbs = new List(); - var remove = false; - // We've seen this route before, maybe user clicked on a breadcrumb - foreach (var crumb in Crumbs) - { - if (crumb.Key == key) - { - remove = true; - } - if (!remove) - { - newCrumbs.Add(crumb); - } - } - Crumbs = newCrumbs; - } - - Current = new StateEntry().WithKey(key) - .SetContext(context) - .WithUrl(context.HttpContext.Request.Url.ToString()) - .WithLabel(ResourceHelper.GetResourceLookup(resourceType, label)); - - Crumbs.Add(Current); - } - - public State(string cookie) - { - SessionCookie = cookie; - Crumbs = new List(); - } - } - - public class StateEntry - { - public ActionExecutingContext Context { get; private set; } - public string Label { get; set; } - public int Key { get; set; } - public string Url { get; set; } - - public StateEntry WithKey(int key) - { - Key = key; - return this; - } - - public StateEntry WithUrl(string url) - { - Url = url; - return this; - } - - public StateEntry WithLabel(string label) - { - Label = label ?? Label; - return this; - } - - - public StateEntry SetContext(ActionExecutingContext context) - { - Context = context; - var type = Context.Controller.GetType(); - var actionName = (string)Context.RouteData.Values["Action"]; - var labelQuery = - from m in type.FindMembers(MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance, (memberInfo, _) => memberInfo.Name == actionName, null) - let atts = m.GetCustomAttributes(typeof(DisplayAttribute), false) - where atts.Length > 0 - select ((DisplayAttribute)atts[0]).GetName(); - Label = labelQuery.FirstOrDefault() ?? (string)context.RouteData.Values["Action"]; - return this; - } - - public string Controller - { - get - { - return (string) Context.RouteData.Values["controller"]; - } - } - - public string Action - { - get - { - return (string)Context.RouteData.Values["action"]; - } - } - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; + +namespace MvcBreadCrumbs +{ + public class State + { + public string SessionCookie { get; set; } + public List Crumbs { get; set; } + public StateEntry Current { get; set; } + + public void Push(ActionExecutingContext context, string label, Type resourceType) + { + var key = + context.HttpContext.Request.Url.LocalPath + .ToLower() + .GetHashCode(); + + if (Crumbs.Any(x => x.Key == key)) + { + var newCrumbs = new List(); + var remove = false; + // We've seen this route before, maybe user clicked on a breadcrumb + foreach (var crumb in Crumbs) + { + if (crumb.Key == key) + { + remove = true; + } + if (!remove) + { + newCrumbs.Add(crumb); + } + } + Crumbs = newCrumbs; + } + + Current = new StateEntry().WithKey(key) + .SetContext(context) + .WithUrl(context.HttpContext.Request.Url.ToString()) + .WithLabel(ResourceHelper.GetResourceLookup(resourceType, label)); + + Crumbs.Add(Current); + } + + /// + /// provides a way to remove a crumb from the crumbs list + /// + /// + public void OnErrorRemoveCrumb(ResultExecutedContext context) + { + var key = + context.HttpContext.Request.Url.LocalPath + .ToLower() + .GetHashCode(); + + if (Crumbs.Any(x => x.Key == key)) + { + var crumb = Crumbs.Single(x => x.Key == key); + Crumbs.Remove(crumb); + } + } + + public State(string cookie) + { + SessionCookie = cookie; + Crumbs = new List(); + } + } + + public class StateEntry + { + public ActionExecutingContext Context { get; private set; } + public string Label { get; set; } + public int Key { get; set; } + public string Url { get; set; } + + public StateEntry WithKey(int key) + { + Key = key; + return this; + } + + public StateEntry WithUrl(string url) + { + Url = url; + return this; + } + + public StateEntry WithLabel(string label) + { + Label = label ?? Label; + return this; + } + + public StateEntry SetContext(ActionExecutingContext context) + { + Context = context; + var type = Context.Controller.GetType(); + var actionName = (string)Context.RouteData.Values["Action"]; + var labelQuery = + from m in type.FindMembers(MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance, (memberInfo, _) => memberInfo.Name == actionName, null) + let atts = m.GetCustomAttributes(typeof(DisplayAttribute), false) + where atts.Length > 0 + select ((DisplayAttribute)atts[0]).GetName(); + Label = labelQuery.FirstOrDefault() ?? (string)context.RouteData.Values["Action"]; + return this; + } + + public string Controller + { + get + { + return (string)Context.RouteData.Values["controller"]; + } + } + + public string Action + { + get + { + return (string)Context.RouteData.Values["action"]; + } + } + } } \ No newline at end of file diff --git a/MvcBreadCrumbs/StateManager.cs b/MvcBreadCrumbs/StateManager.cs index 09bd79f..828a541 100644 --- a/MvcBreadCrumbs/StateManager.cs +++ b/MvcBreadCrumbs/StateManager.cs @@ -1,38 +1,36 @@ -using System.Collections.Generic; -using System.Linq; - -namespace MvcBreadCrumbs -{ - public class StateManager - { - public static readonly List States = new List(); - - public static State GetState(string id) - { - if (States.FirstOrDefault(x => x.SessionCookie == id) == null) - { - StateManager.CreateState(id); - } - return States.First(x => x.SessionCookie == id); - } - - public static State CreateState(string cookie) - { - var newstate = new State(cookie); - States.Add(newstate); - - return newstate; - - } - - public static void RemoveState(string id) - { - var state = GetState(id); - if (state != null) - { - States.Remove(state); - } - } - - } +using System.Collections.Generic; +using System.Linq; + +namespace MvcBreadCrumbs +{ + public class StateManager + { + public static readonly List States = new List(); + + public static State GetState(string id) + { + if (States.FirstOrDefault(x => x.SessionCookie == id) == null) + { + StateManager.CreateState(id); + } + return States.First(x => x.SessionCookie == id); + } + + public static State CreateState(string cookie) + { + var newstate = new State(cookie); + States.Add(newstate); + + return newstate; + } + + public static void RemoveState(string id) + { + var state = GetState(id); + if (state != null) + { + States.Remove(state); + } + } + } } \ No newline at end of file From c3e71e453d5806c3561e66dcb101a33afea63aaf Mon Sep 17 00:00:00 2001 From: JP Date: Thu, 11 Jan 2018 09:18:15 -0300 Subject: [PATCH 2/2] re-implemented changes and resolved all conflicts on PR: Fixed issue with considering breadcrumbs on failing pages https://github.com/thelarz/MvcBreadCrumbs/pull/28 --- .vs/MvcBreadCrumbs/v14/.suo | Bin 104448 -> 107008 bytes MvcBreadCrumbs/BreadCrumb.cs | 34 +++++++++++++++++--------- MvcBreadCrumbs/BreadCrumbAttribute.cs | 12 +++++++++ MvcBreadCrumbs/State.cs | 27 +++++++++++++++++--- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.vs/MvcBreadCrumbs/v14/.suo b/.vs/MvcBreadCrumbs/v14/.suo index 7b5b2da2bf1741f8022a67ab133d0e32699479c9..0e614f4561271e831550fed893e336dae9931332 100644 GIT binary patch literal 107008 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 diff --git a/MvcBreadCrumbs/BreadCrumb.cs b/MvcBreadCrumbs/BreadCrumb.cs index 173a89e..5dd678c 100644 --- a/MvcBreadCrumbs/BreadCrumb.cs +++ b/MvcBreadCrumbs/BreadCrumb.cs @@ -39,6 +39,15 @@ internal static IHierarchyProvider HierarchyProvider public static void Add(string url, string label) { + // get a key for the Url. + var key = + url + .ToLower() + .GetHashCode(); + + var current = new StateEntry().WithKey(key) + .WithUrl(url) + .WithLabel(label); var state = StateManager.GetState(SessionProvider.SessionId); state.Push(url, label); } @@ -145,21 +154,24 @@ public static string Display(string cssClassOverride = "breadcrumb") return sb.ToString(); } - public static string DisplayRaw() - { - - var state = StateManager.GetState(SessionProvider.SessionId); + public static string DisplayRaw(string crumbConcatenator = ">") + { + var state = StateManager.GetState(SessionProvider.SessionId); - if (state.Crumbs != null && !state.Crumbs.Any()) - return ""; + if (state.Crumbs != null && !state.Crumbs.Any()) + return ""; // don't allow blank labels to propagate outside state.Crumbs.ToList().ForEach(x => { x.Label = string.IsNullOrWhiteSpace(x.Label) ? x.Action : x.Label; }); - - return string.Join(" > ", - state.Crumbs.Select(x => "" + x.Label + "").ToArray()); - - } + return string.Join(" " + crumbConcatenator + " ", + state.Crumbs.Select(x => + { + if (IsCurrentPage(x.Key)) + return x.Label; + else + return "" + x.Label + ""; + }).ToArray()); + } private static bool IsCurrentPage(int compareKey) { diff --git a/MvcBreadCrumbs/BreadCrumbAttribute.cs b/MvcBreadCrumbs/BreadCrumbAttribute.cs index 8060350..04f2d81 100644 --- a/MvcBreadCrumbs/BreadCrumbAttribute.cs +++ b/MvcBreadCrumbs/BreadCrumbAttribute.cs @@ -35,6 +35,18 @@ private static IProvideBreadCrumbsSession SessionProvider } } + public override void OnResultExecuted(ResultExecutedContext filterContext) + { + if (filterContext.Exception != null) + { + //if we have an exception on the result (for ANY reason), let's make sure that we don't + //track this failing page on the breadcrumb + var state = StateManager.GetState(SessionProvider.SessionId); + state.OnErrorRemoveCrumb(filterContext); + } + + base.OnResultExecuted(filterContext); + } public override void OnActionExecuting(ActionExecutingContext filterContext) { diff --git a/MvcBreadCrumbs/State.cs b/MvcBreadCrumbs/State.cs index e664dcf..94fe5b4 100644 --- a/MvcBreadCrumbs/State.cs +++ b/MvcBreadCrumbs/State.cs @@ -68,9 +68,30 @@ private void Add(string url, string label, Type resourceType = null, ActionExecu .WithLevel(levels) .WithLabel(ResourceHelper.GetResourceLookup(resourceType, label)); - Crumbs.Add(Current); - } - } + Crumbs.Add(Current); + } + + + /// + /// provides a way to remove a crumb from the crumbs list + /// + /// + public void OnErrorRemoveCrumb(ResultExecutedContext context) + { + var key = + context.HttpContext.Request.Url.LocalPath + .ToLower() + .GetHashCode(); + + if (Crumbs.Any(x => x.Key == key)) + { + var crumb = Crumbs.Single(x => x.Key == key); + Crumbs.Remove(crumb); + } + } + + + } public class StateEntry {