Sunday, 10 November 2013

Orchard CMS Taxonomy Term Autoroute Token Slug v2.0

This is a new and improved solution to my original post

The purpose of this post is to provide a method of returning a full taxonomy term path slug as a token in Orchard CMS. I am currently using this solution in Orchard 1.7.1 as of changeset 91d0048f2796f093a8cefe2111ff013818d220fb.

The problem with the previous solution was that it only rendered the 1 selected taxonomy term into the token result, which was great if you were working with the root terms, but as soon as you went deeper it did not render the full taxonomy term path into the token result.

The following solution allows a full taxonomy term path to be slugified and rendered into a token result.

using System;
using System.Linq;
using Orchard.Taxonomies.Fields;
using Orchard.Localization;
using Orchard.Tokens;

namespace Orchard.Taxonomies.Tokens {
    public class TaxonomyTokens : ITokenProvider {

        public TaxonomyTokens() {
            T = NullLocalizer.Instance;

        public Localizer T { get; set; }

        public void Describe(DescribeContext context) {
            // Usage:
            // Content.Fields.Article.Categories.Terms -> 'Science, Sports, Arts'
            // Content.Fields.Article.Categories.Terms:0 -> 'Science'

            // When used with an indexer, it can be chained with Content tokens
            // Content.Fields.Article.Categories.Terms:0.DisplayUrl -> http://...

            context.For("TaxonomyField", T("Taxonomy Field"), T("Tokens for Taxonomy Fields"))
                   .Token("Terms", T("Terms"), T("The terms (Content) associated with field."))
                   .Token("Terms[:*]", T("Terms"), T("A term by its index. Can be chained with Content tokens."))
                   .Token("TermPathSlug", T("Terms"), T("Selected taxonomy term full path slugified."))

        public void Evaluate(EvaluateContext context) {

                   .Token("Terms", field => String.Join(", ", field.Terms.Select(t => t.Name).ToArray()))
                       token => token.StartsWith("Terms:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Terms:".Length) : null,
                       (token, t) => {
                           var index = Convert.ToInt32(token);
                           return index + 1 > t.Terms.Count() ? null : t.Terms.ElementAt(index).Name;
                   .Token("TermPathSlug", field => context.For<TaxonomyField>("TaxonomyField").Data.TermsField.Value.ElementAt(0).Slug)
                // todo: extend Chain() in order to accept a filter like in Token() so that we can chain on an expression
                   .Chain("Terms:0", "Content", t => t.Terms.ElementAt(0))
                   .Chain("Terms:1", "Content", t => t.Terms.ElementAt(1))
                   .Chain("Terms:2", "Content", t => t.Terms.ElementAt(2))
                   .Chain("Terms:3", "Content", t => t.Terms.ElementAt(3))

An example of an autoroute pattern that works is {Content.Fields.BlogPost.Category.TermPathSlug}, where [BlogPost] is the name of your content type, and [Category] is the name of the field within our content type, not the taxonomy or term.

This could be used is reflecting a taxonomy term hierarchy within a URL using an AutoRoute pattern.

Please be aware that this solution grabs the first selected taxonomy term, so your taxonomy field should be configured so that only 1 term can be selected.

Refer to regarding updates on the Orchard work request relating to this.

Hope this helps! Any questions or if you have an improved solution let me know.


  1. I wonder if it is even possible for the holy grail for Taxonomy urls: multiple URLs, one for each term.

    But then we'd also need to include some type of metadata in the page to indicate to Google that it's not duplicated content.


  2. Could perhaps attach the same taxonomy again to the content type but only allow 1 term to be selected. This term would be the 'primary link' and with some work sprinkle some canonical url sweetness into the head tag.

  3. I show the work item as being resolved, but when I try your autoroute pattern it does not work. Do you know what the pattern should be in the current Orchard version?

  4. I figured it out: {Content.Fields.ContentPage.Taxo.Terms:0.Path}/{Content.Slug}

  5. {Content.Fields.Song.Band.TermPathSlug}/{Content.Slug}
    This worked for me. I think the orchard team should take another look at it. I'm on 1.8.1.
    Thank you very much :)