Breadcrumb algorithm in ASP.NET MVC

Asked

Viewed 1,601 times

8

I’m writing a system on And the idea is that there’s a Breadcrumb on every canvas. With no ideas for a good algorithm, I created a Helper that returns a list of objects according to the path accessed. The file is reproduced below:

using System;
using System.Collections.Generic;
using MeuProjeto.ViewModels;

namespace MeuProjeto.Helpers
{
    public static class BreadcrumbHelper
    {
        private static readonly Dictionary<String, List<BreadcrumbViewModel>> Breadcrumbs = 
            new Dictionary<string, List<BreadcrumbViewModel>>
            {
                { "/Pessoas", 
                    new List<BreadcrumbViewModel> {
                        new BreadcrumbViewModel { Title = "Gestão de Colaboradores" },
                        new BreadcrumbViewModel { Title = "Lista de Pessoas" }
                    }
                },
                { "/Pessoas/Index", 
                    new List<BreadcrumbViewModel> {
                        new BreadcrumbViewModel { Title = "Gestão de Colaboradores" },
                        new BreadcrumbViewModel { Title = "Lista de Pessoas" }
                    }
                },
                { "/Pessoas/Create", 
                    new List<BreadcrumbViewModel> {
                        new BreadcrumbViewModel { Title = "Gestão de Colaboradores" },
                        new BreadcrumbViewModel { Title = "Dados Pessoais" }
                    }
                },
                { "/Pessoas/Edit", 
                    new List<BreadcrumbViewModel> {
                        new BreadcrumbViewModel { Title = "Gestão de Colaboradores" },
                        new BreadcrumbViewModel { Title = "Dados Pessoais" }
                    }
                }
            };

        public static IEnumerable<BreadcrumbViewModel> GetBreadcrumbs(String url)
        {
            return Breadcrumbs[url];
        }
    }
}

For me to get the Breadcrumbs list according to the route I’m on, it’s simple: I put in the common controller code the following:

[ChildActionOnly]
public ActionResult Breadcrumb()
{
    return PartialView(BreadcrumbHelper.GetBreadcrumbs(Request.Path));
}

And in View Shared/_Layout.cshtml the following:

<body>
    <!-- Page background -->
    <div id="page-background-gradient">
    </div>
    <div id="page-background-glare">
    </div>
    <!-- /Page background -->
    @Html.Partial("_SectionHeader")
    @Html.Partial("_MainNavigation")
    @Html.Action("Showcase")

    @Html.Action("Breadcrumb")

    <section id="content" class="row">
        @RenderBody()
    </section>
    @Html.Partial("_SectionFooter")
    @Scripts.Render("~/bundles/jqueryui")
    @Scripts.Render("~/bundles/jquery.datatables")

    @RenderSection("scripts", required: false)
</body>
</html>

The result in the View looks like this for http://localhost:12345/Pessoas:

Home > Employee Management > People List

That works well, but I see two problems:

  • The schema obviously explodes if I create a new controller and do not add the new routes in the BreadcrumbHelper;
  • It is easy to see that the organization of the solution is prolific and redundant. I wanted something simpler and more performative, but I couldn’t think of anything better.

Ideas?

  • If everything in the same controller has the same parent in the hierarchy (as in the case of "Employee Management"), you could keep this part as controller property, right? It would be more Coupled, but less repetitive.

  • It’s an idea, but I wanted to leave as much stuff inside BreadcrumbHelper.

3 answers

6


I tried to use the SiteMapProvider, but it is not compatible with MVC5, so I had to keep improving my solution.

The solution follows the line suggested by @iuristona, but adapted to the reality that I have here.

Helpers Breadcrumbhelper.Cs

namespace MeuProjeto.Helpers
{
    public static class BreadcrumbHelper
    {
        private static readonly BreadcrumbDictionary Breadcrumbs =
            new BreadcrumbDictionary
            {
                new BreadcrumbViewModel { 
                    RouteIdentifier = "Pessoas", 
                    Title = "Gestão de Colaboradores",
                    Children = new BreadcrumbDictionary
                    {
                        new BreadcrumbViewModel
                        {
                            RouteIdentifier = "Index",
                            Title = "Lista de Pessoas",
                            Children = null
                        },
                        new BreadcrumbViewModel
                        {
                            RouteIdentifier = "Create",
                            Title = "Dados Pessoais",
                            Children = null
                        },
                        new BreadcrumbViewModel
                        {
                            RouteIdentifier = "Edit",
                            Title = "Dados Pessoais",
                            Children = null
                        }
                    }
                },
                new BreadcrumbViewModel
                {
                    RouteIdentifier = "WelcomeKit",
                    Title = "Kit de Boas Vindas", 
                    Children = new BreadcrumbDictionary
                    {
                        new BreadcrumbViewModel
                        {
                            RouteIdentifier = "ServiceDesk",
                            Title = "Service Desk",
                            Children = null
                        }
                    }
                }
            };

        /// <summary>
        /// Retorna uma lista de Breadcrumbs de acordo com a rota passada como String.
        /// </summary>
        /// <param name="url">Rota</param>
        /// <returns>Lista de Breadcrumbs</returns>
        public static IEnumerable<BreadcrumbViewModel> GetBreadcrumbs(String url)
        {
            var splittedUrl = url.Split('/');
            if (Breadcrumbs.Contains(splittedUrl[1]))
            {
                yield return Breadcrumbs[splittedUrl[1]];
                foreach (var item in
                    GetChildrenBreadcrumb(Breadcrumbs[splittedUrl[1]].Children,
                        String.Join("/", splittedUrl.SubArray(1, splittedUrl.Length - 1))))
                {
                    yield return item;
                }
            }
            else
            {
                yield return new BreadcrumbViewModel();
            }
        }

        /// <summary>
        /// Função recursiva que acumula o breadcrumb atual + os filhos.
        /// </summary>
        /// <param name="childrenList">Lista atual de Breadcrumbs (children da chamada anterior)</param>
        /// <param name="remainingRoute">Rota que falta ser retornada</param>
        /// <returns></returns>
        private static IEnumerable<BreadcrumbViewModel> GetChildrenBreadcrumb(BreadcrumbDictionary childrenList,
            String remainingRoute)
        {
            var splittedUrl = remainingRoute.Split('/');
            if (splittedUrl.Count() == 1) yield break;
            if (splittedUrl[1] == "") yield break;

            if (splittedUrl.Count() > 2)
            {
                foreach (var item in GetChildrenBreadcrumb(childrenList[splittedUrl[1]].Children, String.Join("/", splittedUrl.SubArray(1, splittedUrl.Length - 1))))
                {
                    yield return item;
                }
            }
            else
            {
                if (childrenList != null && childrenList[splittedUrl[1]] != null)
                    yield return childrenList[splittedUrl[1]];
                else
                    yield return new BreadcrumbViewModel();
            }
        }
    }
}

Viewmodels Breadcrumbviewmodel.Cs

namespace MeuProjeto.ViewModels
{
    public class BreadcrumbViewModel
    {
        public String RouteIdentifier { get; set; }
        public String Title { get; set; }
        public BreadcrumbDictionary Children { get; set; }
    }
}

Helpers Keyedcollections Breadcrumbdictionary.Cs

namespace MeuProjeto.KeyedCollections
{
    public class BreadcrumbDictionary : KeyedCollection<String, BreadcrumbViewModel>
    {
        protected override string GetKeyForItem(BreadcrumbViewModel item)
        {
            return item.RouteIdentifier;
        }
    }
}

Commoncontroller.Cs controllers

namespace MeuProjeto.Controllers
{
    public class CommonController : Controller
    {
        [ChildActionOnly]
        public ActionResult Breadcrumb()
        {
            return PartialView(BreadcrumbHelper.GetBreadcrumbs(Request.Path));
        }
    }
}

Views Shared Breadcrumb.cshtml

@model IEnumerable<MeuProjeto.BreadcrumbViewModel>

<section id="breadcrumb" class="row">
    <div class="large-12 columns">
        <a id="breadcrumbs-home" href="/" title="Página inicial"></a>
        <div id="pnlLinks" class="breadcrumbs home">
            @foreach (var breadcrumb in Model)
            {
                <span id="lblMenu2">@breadcrumb.Title</span>
            }
        </div>
    </div>
</section>

4

I’ll paste a case I created for the assembly of a menu as draft.

I’ve declared an auxiliary type:

public class MenuItem
{
    public string Text { get; set; }
    public string Action { get; set; }
    public string Controller { get; set; }
    public string CssIcon { get; set; } // css do ícone
    public string Role { get; set; } // aqui defino a role que restringe a exibição do item no menu ou breadcrumbs
    public object RouteValues { get; set; } // parâmetros para rotas
    public bool HideOnBreadcrumb { get; set; } // em alguns casos não quero listar essa opção no Breadcrumb

    public List<MenuItem> Children { get; set; }

    public MenuItem Parent { get; set; }
}

Then I fill in the menu like this:

    List<MenuItem> menu = new List<MenuItem>()  
        { 
            new MenuItem() 
            {
                Text = "Dashboard",
                CssIcon = "icon-screen-2",
                RouteValues = new { area = "" },
                Controller = "home",
                Action = "index"
            },
            new MenuItem()
            {
                Text = "Clientes",
                CssIcon = "icon-users",
                RouteValues = new { area = "clientes" },
                Controller = "clientes",
                Action = "index",
                Children = new List<MenuItem>()
                {
                    new MenuItem()
                    {
                        Text = "Clientes",
                        CssIcon = "icon-users",
                        RouteValues = new { area = "clientes" },
                        Controller = "clientes",
                        Action = "index",
                        HideOnBreadcum = true
                    },
                    new MenuItem()
                    {
                        Text = "Modelos de Contrato",
                        CssIcon = "icon-file",
                        RouteValues = new { area = "clientes" },
                        Controller = "contratoModelos",
                        Action = "index"
                    }
                }
            }
        };

        foreach (var item in menu)
        {
            SetParents(item); // método recursivo para ajustar os pais
        }

The method SetParents:

private static MenuItem SetParents(MenuItem menu)
{
    if (menu.Children != null)
    {
        foreach (var item in (menu.Childs))
        {
            item.Parent = menu;
            if (item.RouteValues == null) item.RouteValues = item.Parent.RouteValues;
            if (item.Controller == null) item.Controller = item.Parent.Controller;
            if (item.Action == null) item.Action = item.Parent.Action;
            SetParents(item);
        }
    }
    return menu;
}

I decided to use Areas to have well defined 3 menu levels. From this in mount the full menu, with Breadcrumbs and submenus based on the html template of my project.

This may not be the best option for your case, but I’ll leave it here as an outline of ideas.

  • I liked the idea Parent-Children. I think I’ll end up following this line.

2

Searching some time ago about the same doubt, I found the following link that helped me.

Breadcrumbs on Asp.NET MVC4

I’m sure you can help yourself too, even if not 100% of the way he did, but just like in my case, changing things here and there, helped.

Anything, just communicate here that as far as possible, we will try to help

EDIT

Breadcrumbs ASP.NET Sitemapprovider

There is another link on the Asp.NET site itself and on Stackoverflow talking about it. See the link below:

  • I’ll take a look, but the answer gets the Accept only if you have the example in code.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.