Building multi-level menu for Orchard

Tags: orchard, menu, autofac, english

I was developing Orchard modules from nearly the very beginning. Design choice for the project I’m currently working on - to use Orchard as a base framework – pushed me into this direction and I have to admit that it was a very good choice. But… As with every project in it’s infancy there are lots of obstacles you have to jump over. One of the first I had to deal with was the menu

By default, Orchard delivers only a single-level menu – too bad if you want to build a larger site:/ But… There’s nothing impossible if you are a developer at heart:) - if it’s not available out-of-the-box you have to build one yourself! And so the adventure started…

Orchard docs tell you straight how to build a module – everything you need to start is there. What I had to do was to grab the latest source, build it and scaffold the default module structure via command-line code generation tool. And then the documentation ends… So, how to replace the default menu? To answer this question I had to look into the framework code. And that’s what I found:

  • To add your own menu items you have to create your own implementation of INavigationProvider interface. It looks like that:
    public interface INavigationProvider : IDependency { string MenuName { get; } void GetNavigation(NavigationBuilder builder); }
  • I found out that MenuName has to return value “main” (by looking into Orchard.Core.Navigation.Services.MainMenuNavigationProvider class, which is the default menu provider) and the GetNavigation method has to call the provided NavigationBuilder builder.Add(…) method to create the appropriate menu.
  • Orchard will automatically discover my implementation and pass it to the object which creates the menu (I’ll leave the details for next article).

 

Simple, isn’t it? Implement one interface and voila! You have your menu. At first sight it may look like this, but the toughest part had just begun…

The default menu provider creates a simple, flat menu, by fetching the existing MenuPart objects and iteratively calls builder.Add() method to create the menu. Ok, but I want a hierarchy! In Orchard, every menu item has a property Position which holds an information about  its position. You can specify hierarchy by using dot-notation (1, 1.1, 1.1.1, 1.1.2 and so on), but the default menu provider simply ignores it although the NavigationBuilder object has the ability to create nested items. Just have to build a tree-like call: builder.Add(nb => nb.Add(nb => nb.Add(…))). to have my menu. You can pass a lambda expression (or any delegate that takes one argument of type NavigationItemBuilder) to the Add method, call Add() method of provided object inside the lambda, and so on. But how the hell I’m supposed to create nested lambda expressions from database-fetched data!?

I recalled my CS course and a well-known simple data structure – prefix tree (or trie). It fits perfectly! Half an hour of writing, testing and got it working.

Summarizing, I now had to fetch menu items from the database just like in the default, and add them one-by-one to the trie. Done. Next step was to use some recursion (menus are usually not deep, so I didn’t bother about the recursion drawbacks) to build the appropriate nested lambda expression to pass it to the builder.Add() method. Done. It was my intention not to get into much detail here, as the most important (for Orchard module developer) part hasn’t happened yet…

Ok, I have my menu provider, it creates the hierarchy, but why the hell the old default menu provider is still working!?!?!

This question and trying to find a workaround worried me for a couple of days… Why? There is a class called Orchard.UI.Navigation.NavigationManager. This is the one that is responsible for building menus and it takes as an input all of the INavigationProvider implementations found in IoC container (or more specific – IEnumerable of INavigationProvider)! So the default is taken into account too… That is bad:/ Basically, it takes all INavigationProviders implementations with a given MenuName property and merges their outputs. And that is why my menu had items duplicated (the top level of my menu was merged with the default).

I had to figure out how to get rid of the old main menu provider so that the NavigationManager doesn’t get it.

This part involves some Autofac knowledge. This is an IoC container used by Orchard under-the-hood. If you are not used to working with Autofac, I’d recommend reading the introduction and the part about modules first.

Underlying Autofac container allows developers to plug-in to the registration pipeline. How? Use Autofac modules! First of all you have to create your own module by extending the Autofac.Module class and override the AttachToComponentRegistration method. Orchard will automagically find your implementation and register your module in Autofac, so you don’t have to bother about thatUĊ›miech

Inside AttachToComponentRegistration method you can modify any registration you want (eg. changing object’s lifetime, subscribe to events, provide custom object implementation…). Too bad you cannot unregister types previously registered in the Autofac container….:// But wait, didn’t I say that you can “provide custom object implementation”?  Maybe if I cannot remove the old menu provider registration maybe I’d replace it with an empty stub? And that’s my final solution that made the menu work – the details below:

public class HierarchicalMenuModule : Module { protected override void Load(ContainerBuilder builder) {  } protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { if (typeof (MainMenuNavigationProvider).IsAssignableFrom(registration.Activator.LimitType)) { registration.Activating += (obj, e) => e.Instance = new EmptyMainMenuNavigationProvider(); } } [UsedImplicitly] protected class EmptyMainMenuNavigationProvider : INavigationProvider { public string MenuName { get { return "main"; } } public void GetNavigation(NavigationBuilder builder) { } } }

Hooray!:D

The menu is freely available for download from Orchard Gallery, where I happily contributed it:)

I’m in the process of creating series of articles about more advanced Orchard module development. If you are interested I’d encourage you to take a look at my blog in couple of days!

Cheers!

blog comments powered by Disqus