首页 文章

如何使用ASP.net MVC实现动态面包屑?

提问于
浏览
42

如何用ASP.net MVC实现dynamic breadcrumbs

如果你对面包屑是什么感到好奇:

什么是面包屑?好吧,如果您曾浏览过在线商店或在论坛中阅读帖子,您可能会遇到面包屑 . 它们提供了一种查看网站位置的简便方法 . 像Craigslist这样的网站使用面包屑来描述用户的位置 . 每页上面的列表上面都是这样的:s.f . bayarea craigslist>旧金山市>自行车

编辑

我意识到SiteMapProvider可以实现的功能 . 我也知道网上的提供商可以让你将sitenodes映射到控制器和动作 .

但是,当你想要一个breadcrumb的文本匹配一些动态值时,如下所示:

主页>产品>汽车>丰田首页>产品>汽车>雪佛兰首页>产品> Actuator 材>电动椅首页>产品> Actuator 材>绞架

...产品类别和产品是数据库中的记录 . 应该静态定义一些链接(Home肯定) .

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

7 回答

  • 1

    我构建了这个nuget包来为我自己解决这个问题:

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

    如果您有想法,可以在这里做出贡献:

    https://github.com/thelarz/MvcBreadCrumbs

  • 51

    在codeplex上有一个工具:http://mvcsitemap.codeplex.com/ project [moved to github]

    编辑:

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

    您可以修改mvcsitemap工具以使用它来获得所需内容 .

  • 2

    站点 Map 绝对是一种方式......或者,你可以自己写一个! (当然只要遵循标准的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面包屑时所寻找的 .

  • 20

    ASP.NET 5(又名ASP.NET Core),MVC核心解决方案

    在ASP.NET Core中,事情进一步优化,因为我们不需要在扩展方法中对标记进行字符串化 .

    ~/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在处理_1194140时会处理转义:

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

    ASP.NET 4,MVC 5解决方案

    ===下面的原始/旧答案===

    (扩展Sean Haddy的答案)

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

    ~/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, "[a-z][A-Z]", 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 />
    ...
    ...
    
  • 22

    Maarten Balliauw's MvcSiteMapProvider对我来说效果很好 .

    我创建了一个小型mvc应用程序来测试他的提供者:MvcSiteMapProvider Test(404)

  • 1

    对于有兴趣的人,我做了 HtmlExtension 的改进版本,它也在考虑区域,另外使用Reflection来检查区域内是否有默认控制器或控制器内是否有索引操作:

    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();
        }
    }
    

    如果可以肯定可以改进(可能不包括所有可能的情况),但它直到现在都没有让我失望 .

  • 2

    对于那些使用ASP.NET Core 2.0并寻找比vulcan的HtmlHelper更加分离的方法的人,我建议看一下使用dependency injection的局部视图 .

    下面是一个简单的实现,可以很容易地模制,以满足您的需求 .

    The breadcrumb service (./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.cs after AddMvc():

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    
        services.AddTransient<BreadcrumbService>();
    

    Create a partial to render the breadcrumbs (~/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>
    }
    

    此时,要渲染面包屑,只需调用 Html.Partial("Breadcrumbs")Html.PartialAsync("Breadcrumbs") .

相关问题