An alternate way of managing your tabs in EPiServer 7.5; adding and changing

I needed to add a few extra tabs to our EPiServer 7.5 installation last week, and found Per Magne Skuseth‘s comment on Tabs and sort index to be really useful. Of course I managed to alter the piece of code beyond recognition while integrating it into my current project’s architecture. Here is a StructureMap twist on adding and changing tabs.

Easy way of adding and changing tabs through code in EPiServer 7.5

The main thought is to have a custom interface, say IMyProjectTab exposing a method returning a TabDefinition object as below.

IMyProjectTab.cs

public interface IMyProjectTab
{
  TabDefinition GetTabDefinition();
}

Then for every tab that we wish to add, or change for that matter, we simply create a small class implementing this interface. For instance, if you would like to add a tab containing meta data fields, you could create a class like this.

MetaDataTab.cs

public class MetaDataTab : IMyProjectTab
{
  public TabDefinition GetTabDefinition()
  {
    return new TabDefinition
      {
        // Name property would be a string like "Metadata"
        Name = Constants.ContentTypes.Tabs.MetaData,
        RequiredAccess = AccessLevel.Read,
        SortIndex = 900
      };
  }
}

And if you would like to change an already existing EPiServer System tab you would just set the Name property to something from the EPiServer.DataAbstraction.SystemTabNames namespace, and then alter the other properties as you desire.

EPiServerContentTab.cs

public class EPiServerContentTab : IMyProjectTab
{
  public TabDefinition GetTabDefinition()
  {
    return new TabDefinition
      {
        Name = EPiServer.DataAbstraction.SystemTabNames.Content,
        RequiredAccess = AccessLevel.Create, // Old value 'Read'
        SortIndex = 999 // Old value '10'
      };
  }
}

Setting up the wiring for the tabs to be added and altered as you execute the code is not all that difficult; it just requires a bit of StructureMap configuration together with code from the afore mentioned forum thread.

Making StructureMap add all types implementing our interface

In order to make all of our concrete tab classes available through the StructureMap container we will need to do a bit of plumbing. If you are unfamiliar with how you set up StructureMap for adding own things to the container there are loads of information online; or you could have a look at this article. The key for getting this Registry example below working is the LookForRegistries line in the mentioned article’s code snippets.

Here is the CoreRegistry class from my current project.

CoreRegistry.cs

public class CoreRegistry : Registry
{
  public CoreRegistry()
  {
    Scan(a =>
      {
        a.TheCallingAssembly();
        a.AddAllTypesOf<IMyProjectTab>();
        a.RegisterConcreteTypesAgainstTheFirstInterface();
        a.WithDefaultConventions();
        a.SingleImplementationsOfInterface();
      });
  }
}

The interesting part is line 13; AddAllTypesOf. This tells StructureMap to include all concrete classes that implements the IMyProjectTab interface. Assuming that you have some sort of IConfigurableModule bootstrapping StructureMap (also covered in the previously mentioned article) you should be able to add a tab initializer like the one below. The important part is that it is not run before the container actually contains the tabs; i.e. after the StructureMapInitializer has looked for registries and in turn added all types of our interface.

MyProjectTabInitializer.cs

[ModuleDependency(typeof(StructureMapInitializer))]
[InitializableModule]
public class MyProjectTabInitializer : IInitializableModule
{
  private IEnumerable<IMyProjectTab> _myProjectTabs;
  private ITabDefinitionRepository _tabDefinitionRepository;

  public void Initialize(InitializationEngine context)
  {
    _myProjectTabs = ServiceLocator.Current.GetAllInstances<IMyProjectTab>();
    _tabDefinitionRepository = ServiceLocator.Current.GetInstance<ITabDefinitionRepository>();

    foreach (var myProjectTab in _myProjectTabs)
    {
      RegisterTab(myProjectTab.GetTabDefinition());
    }
  }

  public void Uninitialize(InitializationEngine context) { }
  public void Preload(string[] parameters) { }

  private void RegisterTab(TabDefinition tabDefinition)
  {
    var existingTab = GetExisting(tabDefinition);
    if (existingTab != null)
    {
      tabDefinition.ID = existingTab.ID;
    }
    _tabDefinitionRepository.Save(tabDefinition);
  }

  private TabDefinition GetExisting(TabDefinition definition)
  {
    return _tabDefinitionRepository
            .List()
            .FirstOrDefault(t => t.Name.Equals(definition.Name, StringComparison.InvariantCultureIgnoreCase));
  }
}

Parts of the code above are pretty much nicked from the forum thread with a few changes. We still make use of the ITabDefinitionRepository for adding and changing tabs, but the tabs themselves are retrieved through EPiServer’s ServiceLocator, looped through, and then registered with EPiServer.

Note that if you use the ServiceLocator you will need to go through the method GetAllInstances<IMyProjectTab>(). If you would like to retrieve all the implementing classes through a constructor injection you would go with an IEnumerable<IMyProjectTab> as below.

private readonly IEnumerable<IMyProjectTab> _tabs;

public SomeConstructor(IEnumerable<IMyProjectTab> tabs)
{
  _tabs = tabs;
}

Tabs and EPiServer Language Globalization

If you have multiple languages on your site you can translate the tab labels by adding the Name string value to your language files; under /languages/language/groups/sometabname. More on language support in this previous article.

4 Comments

  1. Mark Everard August 30, 2014
    • Mathias Kunto August 30, 2014
  2. Per Magne Skuseth September 1, 2014
    • Mathias Kunto September 2, 2014