it-swarm.com.de

Wie können mit ASP.net MVC dynamische Breadcrumbs erzielt werden?

Wie kann dynamische Breadcrumbs mit ASP.net MVC erreicht werden?

Wenn Sie neugierig sind, was Paniermehl ist:

Was sind Semmelbrösel? Nun, wenn Sie jemals einen Online-Shop besucht oder Beiträge in einem Forum gelesen haben, sind Sie wahrscheinlich auf Breadcrumbs gestoßen. Sie bieten eine einfache Möglichkeit, um zu sehen, wo Sie sich auf einer Website befinden. Websites wie Craigslist verwenden Breadcrumbs, um den Standort des Benutzers zu beschreiben. Über den Auflistungen auf jeder Seite befindet sich etwas, das so aussieht:

s.f. craigslist bayarea> stadt san francisco> fahrräder

BEARBEITEN

Mir ist klar, was mit dem SiteMapProvider möglich ist. Mir sind auch die Anbieter im Netz bekannt, mit denen Sie Sitenodes Controllern und Aktionen zuordnen können.

Aber was ist, wenn der Text eines Breadcrumbs einem dynamischen Wert entsprechen soll?

Zuhause> Produkte> Autos> Toyota

Home> Produkte> Autos> Chevy

Home> Produkte> Execution Equipment> Elektrischer Stuhl

Home> Produkte> Ausführungsausrüstung> Galgen

... wobei die Produktkategorien und die Produkte Datensätze aus einer Datenbank sind. Einige Links sollten statisch definiert werden (Home sicher).

Ich versuche herauszufinden, wie das geht, aber ich bin sicher, dass dies bereits jemand mit ASP.net MVC getan hat.

46
Ronnie Overby

Für Codeplex gibt es ein Tool, mit dem Sie dies tun können: http://mvcsitemap.codeplex.com/ [project moved to github]

Bearbeiten:

Es gibt eine Möglichkeit, einen SiteMapProvider aus einer Datenbank abzuleiten: http://www.asp.net/Learn/data-access/tutorial-62-cs.aspx

Möglicherweise können Sie das Tool mvcsitemap so ändern, dass Sie das Gewünschte erhalten.

22
ICodeForCoffee

Sitemap's sind definitiv eine Möglichkeit ... alternativ können Sie auch selbst eine schreiben! (Natürlich, solange die MVC-Standardregeln eingehalten werden) ... Ich habe gerade eine geschrieben und dachte, ich würde sie hier teilen.

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

Hoffentlich findet das jemand hilfreich. Genau das habe ich gesucht, als ich SO nach MVC-Semmelbröseln gesucht habe.

55
Sean Haddy

ASP.NET 5 (auch bekannt als ASP.NET Core), MVC Core-Lösung

In ASP.NET Core werden die Dinge weiter optimiert, da wir das Markup in der Erweiterungsmethode nicht stricken müssen.

In ~/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 Bleibt wie folgt (scrollen Sie nach unten, um die MVC5-Version anzuzeigen).

In der Razor-Ansicht brauchen wir nicht Html.Raw, Da Razor sich beim Umgang mit IHtmlContent um die Flucht kümmert:

....
....
<div class="container body-content">

    <!-- #region Breadcrumb -->
    @Html.BuildBreadcrumbNavigation()
    <!-- #endregion -->

    @RenderBody()
    <hr />
...
...

ASP.NET 4, MVC 5-Lösung

=== ORIGINAL/ALTE ANTWORT UNTEN ===

(Auf Sean Haddys Antwort oben erweitern)

Wenn Sie es erweiterungsgesteuert machen möchten (um Views sauber zu halten), können Sie Folgendes tun:

In ~/Extesions/HtmlExtensions.cs:

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

In ~/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]));
        }
    }
}

Dann benutze es wie folgt (in _Layout.cshtml zum Beispiel):

....
....
<div class="container body-content">

    <!-- #region Breadcrumb -->
    @Html.Raw(Html.BuildBreadcrumbNavigation())
    <!-- #endregion -->

    @RenderBody()
    <hr />
...
...
24
vulcan raven

Für alle Interessenten habe ich eine verbesserte Version von HtmlExtension erstellt, die auch Bereiche berücksichtigt. Außerdem prüfe ich mithilfe von Reflection, ob sich ein Standard-Controller in einem Bereich oder eine Index-Aktion in einem Controller befindet:

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

Wenn kann definitiv verbessert werden (wahrscheinlich deckt nicht alle möglichen Fälle), aber es hat mich bis jetzt nicht gescheitert.

2
SmartDev

Für diejenigen, die ASP.NET Core 2.0 verwenden und einen entkoppelteren Ansatz als HtmlHelper von Vulcan suchen, empfehle ich die Verwendung einer Teilansicht mit Dependency Injection .

Im Folgenden finden Sie eine einfache Implementierung, die problemlos an Ihre Anforderungen angepasst werden kann.

Der 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; }
  }
}

Registrieren Sie den Dienst in startup.cs Nach AddMvc():

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddTransient<BreadcrumbService>(); 

Erstellen Sie einen Teil, um die Semmelbrösel zu rendern (~/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>
}

Zum Rendern der Semmelbrösel rufen Sie an dieser Stelle einfach Html.Partial("Breadcrumbs") oder Html.PartialAsync("Breadcrumbs") auf.

2
pim

Ich habe dieses Nuget-Paket erstellt, um dieses Problem für mich selbst zu lösen:

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

Sie können hier einen Beitrag leisten, wenn Sie Ideen dazu haben:

https://github.com/thelarz/MvcBreadCrumbs

2
Larz

MvcSiteMapProvider von Maarten Balliauw hat bei mir ganz gut funktioniert.

Ich habe eine kleine MVC-App erstellt, um seinen Provider zu testen: MvcSiteMapProvider-Test (404)

2
Ronnie Overby