zHaytam / SmartBreadcrumbs

A utility library for ASP.NET Core (both MVC and Razor Pages) websites to easily add and customize breadcrumbs.
https://blog.zhaytam.com/2018/06/24/asp-net-core-using-smartbreadcrumbs/
MIT License
161 stars 77 forks source link

Can you provide more real-world examples of successful manual nodes? #80

Closed bunda3d closed 3 years ago

bunda3d commented 3 years ago

I need to make manual nodes because clicking to navigate back to some breadcrumbs with IDs results in error.

Can you point to (or provide) more real-world examples of successful manual nodes?

Your wiki doesn't provide enough context for me to understand how to apply the manual node scheme to my app's controller and actions, and searching through github and google mostly just turns up other people with questions like me (or some pattern that uses your naming convention in a way I don't understand well enough to extrapolate to my code).

for example, I'm not understanding where in my controller to place this code (don't think the code is correct either)...

namespace Example.Mvc.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private IShipmentDetailService ShipmentDetailService { get; }
        private IMappingService MappingService { get; }

        public HomeController(ILogger<HomeController> logger, IShipmentDetailService shipmentDetailService, IMappingService mappingService)
        {
            _logger = logger;
            ShipmentDetailService = shipmentDetailService;
            MappingService = mappingService;
        }

// manual node code 

        public var childNode1 = new MvcBreadcrumbNode("Index", "Home", "My Shipments");

        public var childNode2 = new MvcBreadcrumbNode("Shipment Detail", "Home", "Shipment Details")
        {
            OverwriteTitleOnExactMatch = false,
            Parent = childNode1,
            RouteValues = new { id = idx }
        };

        public var childNode3 = new MvcBreadcrumbNode("Stop Detail", "Home", "Stop Details")
        {
            OverwriteTitleOnExactMatch = false,
            Parent = childNode1,
            RouteValues = new { id = stp }
        };

        ViewData["BreadcrumbNode"] = childNode3;

// manual node code ends

// some actions starts
        [DefaultBreadcrumb("Shipments")] //should this be here with manual nodes?
        public IActionResult Index() => View();

        public IActionResult IndexData()
        {
            var vm = ShipmentDetailService
                .OrderHeaderGetAll()
                .Select(i => new IndexViewModel(i));
            return new JsonResult(vm);
        }

// ... etc. ....
zHaytam commented 3 years ago

Hello,

Constructing manual nodes is always done inside the action where one or more of its history entries are dynamic. This is why ViewData["BreadcrumbNode"] = childNode3; is used, you can only do that inside an action.

Whenever you're setting manual nodes, you are overwriting everything. So all extract breadcrumb nodes (even the default one) won't get added because you basically said to the library: "Hey, here are the breadcrumbs for this page, don't use the extracted ones".

So a somewhat full example would be:

public IActionResult Index()
{
    return Ok();
}

public IActionResult ShipmentDetail(int id)
{
    public var childNode1 = new MvcBreadcrumbNode("Index", "Home", "My Shipments");
    public var childNode2 = new MvcBreadcrumbNode("Shipment Detail", "Home", "Shipment Details")
    {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode1,
        RouteValues = new { id = id }
    };

    ViewData["BreadcrumbNode"] = childNode2;
    return Ok();
}

public IActionResult StopDetail(int id)
{
    public var childNode1 = new MvcBreadcrumbNode("Index", "Home", "My Shipments");
    public var childNode2 = new MvcBreadcrumbNode("Shipment Detail", "Home", "Shipment Details")
    {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode1,
        RouteValues = new { id = idx } // I guess extracted from the stop detail
    };
    public var childNode3 = new MvcBreadcrumbNode("Stop Detail", "Home", "Stop Details")
    {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode2,
        RouteValues = new { id = id }
    };

    ViewData["BreadcrumbNode"] = childNode3;
    return Ok();
}
bunda3d commented 3 years ago

That's the clearest example I've yet seen, as you've added the controller actions as context. Thank you for doing that--I'll try it out later and see if I can get it working.

zHaytam commented 3 years ago

I'm assuming this helped you a bit, otherwise feel free to re-open!

bunda3d commented 3 years ago

I'm assuming this helped you a bit, otherwise feel free to re-open!

It was very helpful, thanks again.

I did still wrestle with it as we use ViewModels often, and I had to realize I can reference different ViewModels for the same breadcrumb in different actions. That wasn't obvious to me at first, as I was trying to reference the same ViewModel properties across different actions, which was a mistake.

Example of using different ViewModels and properties for the same node: I set a node's route to childNode2 = { id = vmA.Id } in one action, then in the next action, same node is set like: childNode2 = { id = vmB.deliveryRouteId }

So if anyone is trying to use custom breadcrumb nodes with view models, maybe this example will help:

Controller Actions represented by Top-Level BreadCrumb Node


   public IActionResult Index() => View(); 

    public IActionResult IndexData()
    {
       // other data for the Index view, this action shown in case you wonder 
       // which related actions to assign as breadcrumb node (choose the view)
    }

Controller Actions represented by 2nd-Level BreadCrumb Node


    public IActionResult Detail(int id)
    {
      // service used to populate view model
      var vm = new DetailViewModel(ShipmentDetailService.OrderHeaderGet(id));

      var childNode1 = new MvcBreadcrumbNode("Index", "Home", "My Shipments");
      var childNode2 = new MvcBreadcrumbNode("Detail", "Home", "Shipment Details")
      {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode1,
        RouteValues = new { id = vm.Id } 
        // notice I will reference a different view model class
        // for the same childNode2's ID in the next action
      };

      ViewData["BreadcrumbNode"] = childNode2;

      return View(vm); 
      // notice: return statement reserved for returning a view, 
      // not the "return Ok();" statement of previous examples
    }

Controller Actions represented by 3rd-Level BreadCrumb Node


    public IActionResult Stop(int id)
    {
      // building different ViewModel for this action, but action assigns 
      // it to some of the same breadcrumb nodes as previous action
      var stp = ShipmentDetailService.StopGet(id);

      // other ViewModel properties, left to show how the 
      // breadcrumb code integrates with action code, 
      // is not in its own action method
      var lat = stp.Company?.Latitude; 
      var lon = stp.Company?.Longitude;

      var childNode1 = new MvcBreadcrumbNode("Index", "Home", "My Shipments");
      var childNode2 = new MvcBreadcrumbNode("Detail", "Home", "Shipment Details")
      {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode1,
        RouteValues = new { id = stp.OrderHeaderId } 
        // notice this is using a different view model than in the other action
      };
      var childNode3 = new MvcBreadcrumbNode("Stop", "Home", "Stop Details")
      {
        OverwriteTitleOnExactMatch = false,
        Parent = childNode2,
        RouteValues = new { id = stp.Id } 
      };

      ViewData["BreadcrumbNode"] = childNode3; 

      return View(new StopViewModel(stp)
      {
         // view stuff
      });
bunda3d commented 3 years ago

Sorry to drag this closed issue out, but it has example code that makes it the best/easiest place to ask this question:

I have been asked to have a breadcrumb showing for the "Home" page (in this case: "My Shipments" on the HomeController). How can I do that with the code shown above, for these custom nodes?

Currently when on that page, no breadcrumbs populate the tags.

zHaytam commented 3 years ago

You need to either add the breadcrumb attribute to Index, or like your other examples, set a single manual node.