ASP.net MVC如何实现dynamic面包屑?

ASP.net MVC如何实现dynamic面包屑 ?

如果你对面包屑有什么好奇:

什么是面包屑? 那么,如果您曾经浏览过网上商店或在论坛上阅读过post,您可能会遇到面包屑。 他们提供了一个简单的方法来查看您在网站上的位置。 像Craigslist这样的站点使用面包屑来描述用户的位置。 在每个页面上的列表上面是这样的:

sf bayarea craigslist>旧金山市>自行车

编辑

我意识到什么是可能的SiteMapProvider。 我也意识到networking上的提供者可以让你将节点映射到控制器和动作。

但是,当你想要一个面包屑的文本匹配一些dynamic的价值,如此:

主页>产品>汽车>丰田

主页>产品>汽车> Chevy

主页>产品>执行器材>电动椅

主页>产品>执行设备>绞车

…产品类别和产品是来自数据库的logging。 一些链接应该静态定义(Home肯定)。

我想弄清楚如何做到这一点,但我相信有人已经用ASP.net MVC做到了这一点。

有一个工具可以在codeplex上做到这一点: http ://mvcsitemap.codeplex.com/ [项目转移到github]

编辑:

有一种方法可以从数据库驱动一个SiteMapProvider: http : //www.asp.net/Learn/data-access/tutorial-62-cs.aspx

你也许可以修改mvcsitemap工具来使用它来得到你想要的。

站点地图绝对是一种方式…或者,你可以自己写一个! (当然,只要遵循标准的MVC规则)…我只写了一个,我想我会在这里分享。

 @Html.ActionLink("Home", "Index", "Home") @if(ViewContext.RouteData.Values["controller"].ToString() != "Home") { @:> @Html.ActionLink(ViewContext.RouteData.Values["controller"].ToString(), "Index", ViewContext.RouteData.Values["controller"].ToString()) } @if(ViewContext.RouteData.Values["action"].ToString() != "Index"){ @:> @Html.ActionLink(ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["controller"].ToString()) } 

希望有人会觉得这有帮助,这正是我寻找的时候,我search的MVC面包屑。

ASP.NET 5(aka ASP.NET Core),MVC核心解决scheme

在ASP.NET Core中,事物被进一步优化,因为我们不需要在扩展方法中对标记进行string化。

~/Extesions/HtmlExtensions.cs

 using System.Text.RegularExpressions; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Rendering; namespace YourProjectNamespace.Extensions { public static class HtmlExtensions { private static readonly HtmlContentBuilder _emptyBuilder = new HtmlContentBuilder(); public static IHtmlContent BuildBreadcrumbNavigation(this IHtmlHelper helper) { if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" || helper.ViewContext.RouteData.Values["controller"].ToString() == "Account") { return _emptyBuilder; } string controllerName = helper.ViewContext.RouteData.Values["controller"].ToString(); string actionName = helper.ViewContext.RouteData.Values["action"].ToString(); var breadcrumb = new HtmlContentBuilder() .AppendHtml("<ol class='breadcrumb'><li>") .AppendHtml(helper.ActionLink("Home", "Index", "Home")) .AppendHtml("</li><li>") .AppendHtml(helper.ActionLink(controllerName.Titleize(), "Index", controllerName)) .AppendHtml("</li>"); if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index") { breadcrumb.AppendHtml("<li>") .AppendHtml(helper.ActionLink(actionName.Titleize(), actionName, controllerName)) .AppendHtml("</li>"); } return breadcrumb.AppendHtml("</ol>"); } } } 

~/Extensions/StringExtensions.cs保持不变(向下滚动查看MVC5版本)。

在剃刀视图中,我们不需要Html.Raw ,因为Razor在处理IHtmlContent时负责IHtmlContent

 .... .... <div class="container body-content"> <!-- #region Breadcrumb --> @Html.BuildBreadcrumbNavigation() <!-- #endregion --> @RenderBody() <hr /> ... ... 

ASP.NET 4,MVC 5解决scheme

=== ORIGINAL / OLD下面的答案===

(扩展上面的肖恩·哈迪的答案)

如果你想扩展驱动(保持视图干净),你可以做这样的事情:

~/Extesions/HtmlExtensions.cs

(兼容MVC5 / bootstrap)

 using System.Text; using System.Web.Mvc; using System.Web.Mvc.Html; namespace YourProjectNamespace.Extensions { public static class HtmlExtensions { public static string BuildBreadcrumbNavigation(this HtmlHelper helper) { // optional condition: I didn't wanted it to show on home and account controller if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" || helper.ViewContext.RouteData.Values["controller"].ToString() == "Account") { return string.Empty; } StringBuilder breadcrumb = new StringBuilder("<ol class='breadcrumb'><li>").Append(helper.ActionLink("Home", "Index", "Home").ToHtmlString()).Append("</li>"); breadcrumb.Append("<li>"); breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["controller"].ToString().Titleize(), "Index", helper.ViewContext.RouteData.Values["controller"].ToString())); breadcrumb.Append("</li>"); if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index") { breadcrumb.Append("<li>"); breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["action"].ToString().Titleize(), helper.ViewContext.RouteData.Values["action"].ToString(), helper.ViewContext.RouteData.Values["controller"].ToString())); breadcrumb.Append("</li>"); } return breadcrumb.Append("</ol>").ToString(); } } } 

~/Extensions/StringExtensions.cs

 using System.Globalization; using System.Text.RegularExpressions; namespace YourProjectNamespace.Extensions { public static class StringExtensions { public static string Titleize(this string text) { return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text).ToSentenceCase(); } public static string ToSentenceCase(this string str) { return Regex.Replace(str, "[az][AZ]", m => m.Value[0] + " " + char.ToLower(m.Value[1])); } } } 

然后使用它(例如在_Layout.cshtml中):

 .... .... <div class="container body-content"> <!-- #region Breadcrumb --> @Html.Raw(Html.BuildBreadcrumbNavigation()) <!-- #endregion --> @RenderBody() <hr /> ... ... 

Maarten Balliauw的MvcSiteMapProvider对我来说工作得很好。

我创build了一个小的mvc应用程序来testing他的提供者: MvcSiteMapProvider Test (404)

我build立了这个nuget包来解决这个问题:

https://www.nuget.org/packages/MvcBreadCrumbs/

如果您有任何想法,您可以在这里贡献:

https://github.com/thelarz/MvcBreadCrumbs

对于任何感兴趣的人,我做了一个HtmlExtension的改进版本,它也考虑了区域,另外还使用了Reflection来检查控制器内的区域或索引操作中是否有Default控制器:

 public static class HtmlExtensions { public static MvcHtmlString BuildBreadcrumbNavigation(this HtmlHelper helper) { string area = (helper.ViewContext.RouteData.DataTokens["area"] ?? "").ToString(); string controller = helper.ViewContext.RouteData.Values["controller"].ToString(); string action = helper.ViewContext.RouteData.Values["action"].ToString(); // add link to homepage by default StringBuilder breadcrumb = new StringBuilder(@" <ol class='breadcrumb'> <li>" + helper.ActionLink("Homepage", "Index", "Home", new { Area = "" }, new { @class="first" }) + @"</li>"); // add link to area if existing if (area != "") { breadcrumb.Append("<li>"); if (ControllerExistsInArea("Default", area)) // by convention, default Area controller should be named Default { breadcrumb.Append(helper.ActionLink(area.AddSpaceOnCaseChange(), "Index", "Default", new { Area = area }, new { @class = "" })); } else { breadcrumb.Append(area.AddSpaceOnCaseChange()); } breadcrumb.Append("</li>"); } // add link to controller Index if different action if ((controller != "Home" && controller != "Default") && action != "Index") { if (ActionExistsInController("Index", controller, area)) { breadcrumb.Append("<li>"); breadcrumb.Append(helper.ActionLink(controller.AddSpaceOnCaseChange(), "Index", controller, new { Area = area }, new { @class = "" })); breadcrumb.Append("</li>"); } } // add link to action if ((controller != "Home" && controller != "Default") || action != "Index") { breadcrumb.Append("<li>"); //breadcrumb.Append(helper.ActionLink((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange(), action, controller, new { Area = area }, new { @class = "" })); breadcrumb.Append((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange()); breadcrumb.Append("</li>"); } return MvcHtmlString.Create(breadcrumb.Append("</ol>").ToString()); } public static Type GetControllerType(string controller, string area) { string currentAssembly = Assembly.GetExecutingAssembly().GetName().Name; IEnumerable<Type> controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(o => typeof(IController).IsAssignableFrom(o)); string typeFullName = String.Format("{0}.Controllers.{1}Controller", currentAssembly, controller); if (area != "") { typeFullName = String.Format("{0}.Areas.{1}.Controllers.{2}Controller", currentAssembly, area, controller); } return controllerTypes.Where(o => o.FullName == typeFullName).FirstOrDefault(); } public static bool ActionExistsInController(string action, string controller, string area) { Type controllerType = GetControllerType(controller, area); return (controllerType != null && new ReflectedControllerDescriptor(controllerType).GetCanonicalActions().Any(x => x.ActionName == action)); } public static bool ControllerExistsInArea(string controller, string area) { Type controllerType = GetControllerType(controller, area); return (controllerType != null); } public static string AddSpaceOnCaseChange(this string text) { if (string.IsNullOrWhiteSpace(text)) return ""; StringBuilder newText = new StringBuilder(text.Length * 2); newText.Append(text[0]); for (int i = 1; i < text.Length; i++) { if (char.IsUpper(text[i]) && text[i - 1] != ' ') newText.Append(' '); newText.Append(text[i]); } return newText.ToString(); } } 

如果绝对可以改进的话(可能不包括所有可能的情况),但是到现在为止我没有失败。