Asp.net mvc 如何使用ASP.net MVC实现动态面包屑?
如何使用ASP.net MVC实现 如果您对面包屑是什么感到好奇: 面包屑是什么?嗯,如果你曾经浏览过网上商店或在论坛上阅读过帖子,你可能会遇到面包屑。它们提供了一种查看站点位置的简单方法。Craigslist等网站使用面包屑来描述用户的位置。在每个页面上的列表上方是如下所示: <旧金山>城市>自行车> < 编辑 我意识到使用SiteMapProvider可以实现什么。我还知道网络上有一些提供商,它们可以让您将站点节点映射到控制器和操作 但是,当您希望面包屑的文本与某个动态值匹配时,该怎么办 家庭>产品>汽车>丰田 主页>产品>汽车>雪佛兰 家居>产品>执行设备>电椅 家庭>产品>执行设备>绞架 。。。其中产品类别和产品是数据库中的记录。有些链接应该静态定义(当然是主链接)Asp.net mvc 如何使用ASP.net MVC实现动态面包屑?,asp.net-mvc,breadcrumbs,Asp.net Mvc,Breadcrumbs,如何使用ASP.net MVC实现 如果您对面包屑是什么感到好奇: 面包屑是什么?嗯,如果你曾经浏览过网上商店或在论坛上阅读过帖子,你可能会遇到面包屑。它们提供了一种查看站点位置的简单方法。Craigslist等网站使用面包屑来描述用户的位置。在每个页面上的列表上方是如下所示: 城市>自行车> < 编辑 我意识到使用SiteMapProvider可以实现什么。我还知道网络上有一些提供商,它们可以让您将站点节点映射到控制器和操作 但是,当您希望面包屑的文本与某个动态值匹配时,该怎么办 家庭>产品>
我正试图弄清楚如何做到这一点,但我确信已经有人用ASP.net MVC做到了这一点。在codeplex:[project]上有一个工具可以做到这一点 编辑: 有一种方法可以从数据库派生SiteMapProvider: 您可以修改mvcsitemap工具以使用它来获得所需的内容。对我来说效果很好
我创建了一个小的mvc应用程序来测试他的提供商:(404)站点地图肯定是一条路要走…或者,你可以自己写一个!(当然,只要遵循标准的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())
}
希望有人会觉得这很有帮助,这正是我搜索MVC面包屑时所寻找的。ASP.NET 5(又名ASP.NET Core),MVC Core解决方案
在ASP.NETCore中,事情会进一步优化,因为我们不需要在扩展方法中字符串化标记
在~/extensions/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>");
}
}
}
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]));
}
}
}
ASP.NET 4、MVC 5解决方案
==以下原始/旧答案===
(以上是肖恩·哈迪的回答)
如果要使其成为扩展驱动的(保持视图干净),可以执行以下操作:
在~/extensions/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>");
}
}
}
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]));
}
}
}
(与MVC5/引导程序兼容)
然后像这样使用它(例如在_Layout.cshtml中):
。。。。
....
@Html.Raw(Html.BuildBreadcrumbNavigation())
@RenderBody()
...
...
我构建这个nuget包是为了自己解决这个问题:
如果您对此有想法,您可以在此作出贡献:
对于感兴趣的人,我做了一个改进版的
HtmlExtension
,它也考虑了区域,并且使用反射来检查区域内是否有默认控制器或控制器内是否有索引操作:
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();
}
}
public静态类
{
公共静态MvcHtmlString BuildBreadcrumbNavigation(此HtmlHelper帮助程序)
{
字符串区域=(helper.ViewContext.RouteData.DataTokens[“area”]??“”).ToString();
string controller=helper.ViewContext.RouteData.Values[“controller”].ToString();
string action=helper.ViewContext.RouteData.Values[“action”].ToString();
//默认情况下,将链接添加到主页
StringBuilder面包屑=新的StringBuilder(@“
“+helper.ActionLink(“主页”、“索引”、“主页”、新{Area=”“}、新{@class=“first”})+@” ”;
//添加指向区域的链接(如果存在)
如果(面积!=“”)
{
面包屑追加();
如果(ControllerExistsInArea(“Default”,area))//按照约定,默认区域控制器应命名为Default
{
Append(helper.ActionLink(area.AddSpaceOnCaseChange(),“Index”,“Default”,new{area=area},new{@class=”“}));
}
其他的
{
Append(area.AddSpaceOnCaseChange());
}
面包屑。追加(“ ”);
}
//如果操作不同,则添加控制器索引的链接
如果((控制器!=“主”和控制器!=“默认”)和操作!=“索引”)
{
if(ActionExistsInController(“索引”、控制器、区域))
{
面包屑追加();
Append(helper.ActionLink(controller.AddSpaceOnCaseChange(),“Index”,controller,new{Area=Area},new{@class=”“}));
面包屑。追加(“ ”);
}
}
//添加操作链接
如果((控制器!=“主”和&controller!=“默认”)| |操作!=“索引”)
{
面包屑追加();
//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());
面包屑。追加(“ ”);
}
返回MvcHtmlString.Create(breadcrumb.Append(“”.ToString());
}
公共静态类型GetControllerType(字符串控制器、字符串区域)
{
字符串currentAssembly=Assembly.GetExecutionGassembly().GetName().Name;
IEnumerable controllerTypes=Assembly.GetExecutionGassembly().GetTypes()。其中(o=>typeof(IController).IsAssignableFrom(o));
string typeFullName=string.Format(“{0}.Controllers.{1}Controller”,currentAssembly,Controller);
如果(面积!=“”)
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();
}
}
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; }
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<BreadcrumbService>();
@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>
}