asp.net-mvc 如何使用 ASP.net MVC 实现动态面包屑导航?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1066777/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can dynamic breadcrumbs be achieved with ASP.net MVC?
提问by Ronnie Overby
How can dynamic breadcrumbsbe achieved with ASP.net MVC?
如何使用 ASP.net MVC 实现动态面包屑导航?
If you are curious about what breadcrumbs are:
如果您对面包屑是什么感到好奇:
What are breadcrumbs? Well, if you have ever browsed an online store or read posts in a forum, you have likely encountered breadcrumbs. They provide an easy way to see where you are on a site. Sites like Craigslist use breadcrumbs to describe the user's location. Above the listings on each page is something that looks like this:
s.f. bayarea craigslist > city of san francisco > bicycles
什么是面包屑?好吧,如果您曾经浏览过在线商店或在论坛中阅读过帖子,那么您很可能遇到过面包屑。它们提供了一种简单的方法来查看您在网站上的位置。像 Craigslist 这样的网站使用面包屑来描述用户的位置。每个页面上的列表上方是这样的:
SF bayarea craigslist > 旧金山市 > 自行车
EDIT
编辑
I realize what is possible with the SiteMapProvider. I am also aware of the providers out there on the net that will let you map sitenodes to controllers and actions.
我意识到使用 SiteMapProvider 可以实现什么。我也知道网络上的提供者可以让您将站点节点映射到控制器和操作。
But, what about when you want a breadcrumb's text to match some dynamic value, like this:
但是,当您希望面包屑的文本匹配某个动态值时,例如:
Home > Products > Cars > Toyota
Home > Products > Cars > Chevy
Home > Products > Execution Equipment > Electric Chair
Home > Products > Execution Equipment > Gallows
首页 > 产品 > 汽车 > 丰田
首页 > 产品 > 汽车 > 雪佛兰
首页 > 产品中心 > 执行设备 > 电椅
首页 > 产品 > 执行设备 > 绞刑架
... where the product categories and the products are records from a database. Some links should be defined statically (Home for sure).
... 其中产品类别和产品是来自数据库的记录。一些链接应该静态定义(当然是主页)。
I am trying to figure out how to do this, but I'm sure someone has already done this with ASP.net MVC.
我想弄清楚如何做到这一点,但我确信有人已经用 ASP.net MVC 做到了这一点。
采纳答案by ICodeForCoffee
There is a tool to do this on codeplex: http://mvcsitemap.codeplex.com/[project moved to github]
在 codeplex 上有一个工具可以做到这一点:http://mvcsitemap.codeplex.com/ [项目移至 github]
Edit:
编辑:
There is a way to derive a SiteMapProvider from a database: http://www.asp.net/Learn/data-access/tutorial-62-cs.aspx
有一种从数据库派生 SiteMapProvider 的方法:http: //www.asp.net/Learn/data-access/tutorial-62-cs.aspx
You might be able to modify the mvcsitemap tool to use that to get what you want.
您也许可以修改 mvcsitemap 工具以使用它来获得所需的内容。
回答by Sean Haddy
Sitemap's are definitely one way to go... alternatively, you can write one yourself! (of course as long as standard MVC rules are followed)... I just wrote one, I figured I would share here.
站点地图绝对是一种方法……或者,您可以自己编写一个!(当然只要遵循标准的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())
}
Hopefully someone will find this helpful, this is exactly what I was looking for when I searched SO for MVC breadcrumbs.
希望有人会发现这有帮助,这正是我在 SO 中搜索 MVC 面包屑时所寻找的。
回答by vulcan raven
ASP.NET 5 (aka ASP.NET Core), MVC Core Solution
ASP.NET 5(又名 ASP.NET Core),MVC 核心解决方案
In ASP.NET Core, things are further optimized as we don't need to stringify the markup in the extension method.
在 ASP.NET Core 中,事情得到了进一步优化,因为我们不需要在扩展方法中对标记进行字符串化。
In ~/Extesions/HtmlExtensions.cs:
在~/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.csremains the same as below (scroll down to see the MVC5 version).
~/Extensions/StringExtensions.cs保持不变(向下滚动以查看 MVC5 版本)。
In razor view, we don't need Html.Raw, as Razor takes care of escaping when dealing with IHtmlContent:
在 razor 看来,我们不需要Html.Raw,因为 Razor 在处理 时负责转义IHtmlContent:
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
@Html.BuildBreadcrumbNavigation()
<!-- #endregion -->
@RenderBody()
<hr />
...
...
ASP.NET 4, MVC 5 Solution
ASP.NET 4、MVC 5 解决方案
=== ORIGINAL / OLD ANSWER BELOW ===
===下面的原始/旧答案===
(Expanding on Sean Haddy's answer above)
(扩展以上肖恩·哈迪的回答)
If you want to make it extension-driven (keeping Views clean), you can do something like:
如果你想让它由扩展驱动(保持视图干净),你可以这样做:
In ~/Extesions/HtmlExtensions.cs:
在~/Extesions/HtmlExtensions.cs:
(compatible with MVC5 / bootstrap)
(兼容MVC5/引导程序)
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();
}
}
}
In ~/Extensions/StringExtensions.cs:
在~/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, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
}
}
Then use it like (in _Layout.cshtml for example):
然后像这样使用它(例如在 _Layout.cshtml 中):
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
@Html.Raw(Html.BuildBreadcrumbNavigation())
<!-- #endregion -->
@RenderBody()
<hr />
...
...
回答by Larz
I built this nuget package to solve this problem for myself:
我构建了这个 nuget 包来为自己解决这个问题:
https://www.nuget.org/packages/MvcBreadCrumbs/
https://www.nuget.org/packages/MvcBreadCrumbs/
You can contribute here if you have ideas for it:
如果您对此有想法,可以在这里做出贡献:
回答by pimbrouwers
For those using ASP.NET Core 2.0 and looking for a more decoupled approach than vulcan's HtmlHelper, I recommend having a look at using a partial view with dependency injection.
对于那些使用 ASP.NET Core 2.0 并寻找比 vulcan 的 HtmlHelper 更解耦的方法的人,我建议看看使用带有依赖项注入的部分视图。
Below is a simple implementation which can easily be molded to suit your needs.
下面是一个简单的实现,可以很容易地根据您的需要进行塑造。
The breadcrumb service (./Services/BreadcrumbService.cs):
面包屑服务 ( ./Services/BreadcrumbService.cs):
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
namespace YourNamespace.YourProject
{
public class BreadcrumbService : IViewContextAware
{
IList<Breadcrumb> breadcrumbs;
public void Contextualize(ViewContext viewContext)
{
breadcrumbs = new List<Breadcrumb>();
string area = $"{viewContext.RouteData.Values["area"]}";
string controller = $"{viewContext.RouteData.Values["controller"]}";
string action = $"{viewContext.RouteData.Values["action"]}";
object id = viewContext.RouteData.Values["id"];
string title = $"{viewContext.ViewData["Title"]}";
breadcrumbs.Add(new Breadcrumb(area, controller, action, title, id));
if(!string.Equals(action, "index", StringComparison.OrdinalIgnoreCase))
{
breadcrumbs.Insert(0, new Breadcrumb(area, controller, "index", title));
}
}
public IList<Breadcrumb> GetBreadcrumbs()
{
return breadcrumbs;
}
}
public class Breadcrumb
{
public Breadcrumb(string area, string controller, string action, string title, object id) : this(area, controller, action, title)
{
Id = id;
}
public Breadcrumb(string area, string controller, string action, string title)
{
Area = area;
Controller = controller;
Action = action;
if (string.IsNullOrWhiteSpace(title))
{
Title = Regex.Replace(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string.Equals(action, "Index", StringComparison.OrdinalIgnoreCase) ? controller : action), "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
else
{
Title = title;
}
}
public string Area { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public object Id { get; set; }
public string Title { get; set; }
}
}
Register the service in startup.csafter AddMvc():
在startup.cs之后注册服务AddMvc():
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<BreadcrumbService>();
Create a partial to render the breadcrumbs (~/Views/Shared/Breadcrumbs.cshtml):
创建一个局部来渲染面包屑 ( ~/Views/Shared/Breadcrumbs.cshtml):
@using YourNamespace.YourProject.Services
@inject BreadcrumbService BreadcrumbService
@foreach(var breadcrumb in BreadcrumbService.GetBreadcrumbs())
{
<a asp-area="@breadcrumb.Area" asp-controller="@breadcrumb.Controller" asp-action="@breadcrumb.Action" asp-route-id="@breadcrumb.Id">@breadcrumb.Title</a>
}
At this point, to render the breadcrumbs simply call Html.Partial("Breadcrumbs")or Html.PartialAsync("Breadcrumbs").
此时,要呈现面包屑只需调用Html.Partial("Breadcrumbs")或Html.PartialAsync("Breadcrumbs")。
回答by Ronnie Overby
Maarten Balliauw's MvcSiteMapProviderworked pretty well for me.
Maarten Balliauw 的 MvcSiteMapProvider对我来说效果很好。
I created a small mvc app to test his provider: MvcSiteMapProvider Test(404)
我创建了一个小的 mvc 应用程序来测试他的提供者:MvcSiteMapProvider Test(404)
回答by SmartDev
For whoever is interested, I did an improved version of a HtmlExtensionthat is also considering Areas and in addition uses Reflection to check if there is a Default controller inside an Area or a Index action inside a Controller:
对于感兴趣的人,我做了一个改进版本的 aHtmlExtension也考虑了区域,此外还使用反射来检查区域内是否有默认控制器或控制器内是否有索引操作:
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();
}
}
If can definitely can be improved (probably does not cover all the possible cases), but it did not failed me until now.
如果可以肯定可以改进(可能没有涵盖所有可能的情况),但直到现在它并没有让我失望。

