Breadcrumb Algorithm in ASP.NET MVC

8

I'm writing a system on and the idea is that on the screens there is a Breadcrumb on all screens. Without ideas for a good algorithm, I booted a Helper that returns a list of objects according to the route 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];
        }
    }
}

To get the list of Breadcrumbs according to the route I'm in, it's simple: I put the following code in the common controller code:

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

And in View% with% as follows:

<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 View looks like this for Shared/_Layout.cshtml :

  

Home > Employee Management > List of People

This works fine, but I see two problems:

  • The scheme obviously explodes if I create a new controller and do not add the new routes in http://localhost:12345/Pessoas ;
  • It's easy to see that the organization of the solution is redundant and redundant. I wanted something simpler and more performative, but I could not think of anything better.

Ideas?

    
asked by anonymous 28.02.2014 / 18:42

3 answers

6

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

The solution follows the line suggested by @uristone, but adapted to the reality 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;
        }
    }
}

Controllers \ CommonController.cs

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>
    
10.03.2014 / 23:28
4

I'm going to paste a case in which I created for the assembly of a menu as a sketch.

I declare 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 populate 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 SetParents method:

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 full amount the 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.

    
28.02.2014 / 20:01
2

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

Breadcrumbs on Asp.NET MVC4

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

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

EDIT

BreadCrumbs ASP.NET SiteMapProvider

There is another link in Asp.NET's own site and in StackOverflow talking about it. See the link below:

    
28.02.2014 / 19:52