This project is read-only.

Override the list of contentItems generated by Taxonomy Term

Topics: Writing modules
Aug 4, 2013 at 5:35 PM
Edited Aug 4, 2013 at 5:36 PM

I´m developing an ECommerce website and i'm using taxonomies in order to classify products with categories.
I´m using the Taxonomy Menu Link in order to generate the categories Menu.
I´m facing a problem here, because my client wants the list of products generated when someone hits the category menu link ordered by price or by another field.
I believe the best way i can do this, is to write my own controller actions and querying the content manager and then do the ordering and filtering there, but i´m a bit stuck here on how to achieve this, a here is my question:
  • The generation of the taxonomy term contentITems list is delegated to the TermPartDriver Display method, how can i delegate this to my own controller in my module instead of the TermPartDriver.
Aug 5, 2013 at 1:24 AM
Taxonomy Menu Link is gone from 1.7. Use projections instead, or your own controller action. The code that works in a driver should work fine in a controller with a few modifications. How exactly did it not work when you tried?
Aug 5, 2013 at 1:38 AM
Edited Aug 5, 2013 at 1:40 AM
Hello Bertand, i´m using Orchard 1.7 and the Taxonomy Menu Link is there. I´m also generating a widget menu based on a query that filters by this taxonomy. But my dificulty is about "sending" or "pointing" the link of the taxonomy term to a controller action in my own module instead of the Taxonomies Module TermPart driver.
Aug 5, 2013 at 1:41 AM
You're right, I was confusing it with something else. Still, don't use it in that case, use a projection instead. This way, you can point it at anything you want.
Aug 5, 2013 at 1:44 AM
Edited Aug 5, 2013 at 1:45 AM
Ok, i´ve tryied that also, but i´m not figuring out how to make a query that has a dynamic filtering on the taxonomy term. eg probably query string would work? but how?
Aug 5, 2013 at 1:52 AM
Edited Aug 5, 2013 at 1:59 AM
Sorry i think i misunderstand what you wrote. Do you mean creating a projection with the taxonomomy terms and rewrite the urls to point to my controller action and passing for example a query string parameter with the taxonomy. Or do you mean creating a projection to list my products filtered by the taxonomy term?
Aug 5, 2013 at 2:15 AM
Edited Aug 5, 2013 at 2:18 AM
I'm going for the first one adding to the main menu a query that filters by taxonomy. Which bindings should i use in the query layout in order to rewite the url that will show in the menu, so that i can point it to my controller
eg. /onlinestore/?productCategory={productCategory}
Aug 5, 2013 at 2:24 AM
Yes, I meant a projection with the terms, with a rewrite to point to your controller. Not sure about the details of the rewrite. Try it.
Aug 5, 2013 at 2:28 AM
Edited Aug 5, 2013 at 2:29 AM
Ok i´m going to try that, if i find the solution i´ll post it so that others can benefit from it, if i don´t i´ll keep asking.

Thank´s Bertrand
Aug 6, 2013 at 7:48 PM
Edited Aug 6, 2013 at 7:49 PM
Hello again Bertrand,
I´ve been struggling with this for a while and can´t figure out how to rewrite the taxonomy term url to point somewhere else, in this case my controller.
Even if i created a query layout with the url rewrite (which i can't figure out which tokens to use, i tried every available, even in the bindings) when creating a menu with this query, the menu will ignore the the layout and get the url from the Term autoroute.

Then after googling for a while i found this:

And since what i need is to have the ability of ordering by a certain criteria the list of of content items that have the taxonomy term (in this case product categories).
I was thinking of creating another driver for Term in my module, and in that new Driver Display method get the ordered list and then adding it to the shape.

I this post you wrote, you mention about creating a new part instead of just creating a new driver, probably it would be better since i only need this type of sorting in this specific taxonomy, a not afecting other taxonomy fields on other content types.

Can you give me some directions? Is this the right way to acomplish this.

Aug 6, 2013 at 7:51 PM
Sounds interesting. Not sure what guidance I can provide however, sorry.
Aug 6, 2013 at 7:59 PM
Edited Aug 6, 2013 at 8:07 PM
Wait, i just remembered one thing, the code on the driver just executes if the shape gets placed in, right?
that way probably i could just create another driver for the Term in my module and use it or not (depending on the needs) by placing the shape in

Is this right? or am i thinking right?
Aug 6, 2013 at 9:06 PM
mmh, yes, that sounds about right.
Aug 7, 2013 at 8:25 PM
Edited Aug 7, 2013 at 8:32 PM

Yes it sounds and it works like a charm!

The keys to solve this was your blog post "more than one driver per part" and in the module i´m developing.
With these 2 you can extend a core feature but you also can 'override' an existing feature, getting the best of both worlds.

What I did in my module was:
  • Created a new TermPartDriver.cs file, with a DisplayMethod. In this method i queried the contentManager with the sorting criteria that i wanted and added the resulting list to the shape (by the way, i gave another name to the shape).
  • On the placement. info file i created an entry to match the Term Content Type in this case "ProductCategoryTerm", and in there hide the original core Parts_TermPart and place my new shape Parts_ProductCategoryList.
This way i can "override" the core behaviour just for this Term and the rest is unafected.

After this all doors were open, and then i added code in the shape view to call another controller in order to do other filtering and price ordering.

Thank´s Bertrand, what you revealed in you blog really helped me and also give another perspective on Orchard development.
Aug 7, 2013 at 9:38 PM
Feb 25, 2014 at 9:15 AM

Could you share your code? I'm also looking for a way to use taxonomies to filter products and combine the filters, I'm developing a site for someone who sells wine.

Just like an ecommerce site I want to display all the bottles and use filtering on the left like this:



So when a user clicks on the radiobutton red and France he should only see the red wines from France.

Kind regards,

Feb 25, 2014 at 12:54 PM
Hi borrierulez,

What you´re trying to achieve is a bit more complex than what did, i just wanted to sort by a certain criteria the list of content items associated with a specific taxonomy term. In your case, you want to combine diferent terms, i would do it using controllers, and creating a service that query the content items and do the filtering there.
This is the aproach i would take on this one, but that´s just my opinion.

I hope it helps,

Feb 25, 2014 at 1:04 PM

Can you post or send me your code that you made for your commerce from the controller and stuf? Maybe I can build from there, I'll post back my code also.

I've seen some people asking also for multi checkbox or radiobutton filtering but I couldn't find an answer anywhere.

Feb 25, 2014 at 1:30 PM

I didn´t build the logic using controllers, i just override the TermPartDriver to query the list of contentitems by a specific ordering criteria. what you are trying to acomplish is more complex and i´m just trying to help you giving ideas/directions based on what i think i would do.

This is what I did.
Below you have the code for the TermPartDriver (you can find in the Taxonomies Module):
        protected override DriverResult Display(TermPart part, string displayType, dynamic shapeHelper) {
            return Combined(
                ContentShape("Parts_TermPart_Feed", () => {
                    // generates a link to the RSS feed for this term
                    _feedManager.Register(part.Name, "rss", new RouteValueDictionary { { "term", part.Id } });
                    return null;
                ContentShape("Parts_TermPart", () => {
                    var pagerParameters = new PagerParameters();
                    var httpContext = _httpContextAccessor.Current();
                    if (httpContext != null) {
                        pagerParameters.Page = Convert.ToInt32(httpContext.Request.QueryString["page"]);
                    var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
                    var taxonomy = _taxonomyService.GetTaxonomy(part.TaxonomyId);
                    var totalItemCount = _taxonomyService.GetContentItemsCount(part);

                    // asign Taxonomy and Term to the content item shape (Content) in order to provide 
                    // alternates when those content items are displayed when they are listed on a term
                    var termContentItems = _taxonomyService.GetContentItems(part, pager.GetStartIndex(), pager.PageSize)
                        .Select(c => _contentManager.BuildDisplay(c, "Summary").Taxonomy(taxonomy).Term(part));

                    var list = shapeHelper.List();


                    var pagerShape = shapeHelper.Pager(pager)

                    return shapeHelper.Parts_TermPart(ContentItems: list, Taxonomy: taxonomy, Pager: pagerShape);
you get the list of content items associated with the term using the _taxonomyService.GetContentItemsCount(part), you just have to change that logic there, creating a service that querys the contentManager with your specific needs (check the TaxonomyService.cs to see how the query is built ).

Feb 25, 2014 at 1:41 PM
Hey man, thanks a lot already!! I've been searching the web (i'm not a hardcore developer :) ) And seems the best way to do this is using Ajax so you can select multiple items without a refresh.

I've also posted it here (the question) if you want to subscribe to it:

I'll get to work using your code and ajax. This will take me a long time though :)

Aug 25, 2014 at 5:43 PM
Hi all,
linked to this article "" I have a question:

How should I override and not add a driver for an existing part? Is it possible?
I am using Orchard 1.7.2 and this is my scenario:
  1. I have a ContentType (Product) with a LocalizationPart and a TaxonomyField (e.g taxonomy: producttype )
  2. producttype taxonomy also have a LocalizationPart in order to have localized URL and different terms based on different taxonomies (e.g. en-US taxomomy: producttype, it-IT: tipoprodotto)
  3. I would like to take terms of a localized taxonomy (based on LocalizationPart of my ContentItem)
  4. So I have overrided the driver for TaxnomyFieldDriver (now it takes desired terms) but the effect was that my field (Fields_TaxonomyField_Edit) is rendered twice
  5. So I tried to use Placement.Info of my custom module in order to prevent rendering Fields_TaxonomyField_Edit but without effects
  6. And I created a new ContentShape Fields_TaxonomyField_Localized_Edit
  <Place Fields_TaxonomyField_Edit="-" Fields_TaxonomyField_Localized_Edit="Content:9" />
In my module manifest I also declared dependency from Orchard.Taxonomy and Orchard.Localization

Someone have Any Idea of why I cannot make it working?
Any alternative seggestion?

Thank you in advance for your support.
Aug 25, 2014 at 8:23 PM
Can you post the driver code and
Aug 26, 2014 at 7:34 AM
Hi nduarte,
thank you for your fast answer.
here's my code

My driver (taken from original TaxonomyFieldDriver and modified to use localization)
public class TaxonomyFieldExtensionDriver : ContentFieldDriver<TaxonomyField> {
        private readonly ITaxonomyService _taxonomyService;
        private readonly ILocalizationService _localizationService;
        public IOrchardServices Services { get; set; }

        public TaxonomyFieldExtensionDriver(
            IOrchardServices services,
            ITaxonomyService taxonomyService,
            IRepository<TermContentItem> repository,
            ILocalizationService localizationService) {
            _taxonomyService = taxonomyService;
            Services = services;
            T = NullLocalizer.Instance;
            _localizationService = localizationService;

        public Localizer T { get; set; }

        private static string GetPrefix(ContentField field, ContentPart part) {
            return part.PartDefinition.Name + "." + field.Name;

        private static string GetDifferentiator(TaxonomyField field, ContentPart part) {
            return field.Name;
        protected override DriverResult Display(ContentPart part, TaxonomyField field, string displayType, dynamic shapeHelper) {

            return ContentShape("Fields_TaxonomyField_Localized", GetDifferentiator(field, part),
                () => {
                    var settings = field.PartFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>();
                    var terms = _taxonomyService.GetTermsForContentItem(part.ContentItem.Id, field.Name).ToList();
                    var taxonomy = _taxonomyService.GetTaxonomyByName(settings.Taxonomy);

                    return shapeHelper.Fields_TaxonomyField(
                        ContentField: field,
                        Terms: terms,
                        Settings: settings,
                        Taxonomy: taxonomy);

        protected override DriverResult Editor(ContentPart part, TaxonomyField field, dynamic shapeHelper) {
            return ContentShape("Fields_TaxonomyField_Localized_Edit", GetDifferentiator(field, part), () => {
                var settings = field.PartFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>();
                var taxonomyName = settings.Taxonomy;
                var taxonomyLocalizedName = "";
                var taxonomy = _taxonomyService.GetTaxonomyByName(settings.Taxonomy);
                /*Localized context*/
                if (part.ContentItem.As<LocalizationPart>() != null && part.ContentItem.As<LocalizationPart>().MasterContentItem != null) {
                    taxonomyLocalizedName = _localizationService.GetLocalizedContentItem(taxonomy, part.ContentItem.As<LocalizationPart>().Culture.Culture).As<TitlePart>().Title;
                    taxonomy = _taxonomyService.GetTaxonomyByName(taxonomyLocalizedName);
                var appliedTerms = _taxonomyService.GetTermsForContentItem(part.ContentItem.Id, field.Name).Distinct(new TermPartComparer()).ToDictionary(t => t.Id, t => t);
                var terms = taxonomy != null
                    ? _taxonomyService.GetTerms(taxonomy.Id).Where(t => !string.IsNullOrWhiteSpace(t.Name)).Select(t => t.CreateTermEntry()).ToList()
                    : new List<TermEntry>(0);

                terms.ForEach(t => t.IsChecked = appliedTerms.ContainsKey(t.Id));

                var viewModel = new TaxonomyFieldViewModel {
                    DisplayName = field.DisplayName,
                    Name = field.Name,
                    Terms = terms,
                    Settings = settings,
                    SingleTermId = terms.Where(t => t.IsChecked).Select(t => t.Id).FirstOrDefault(),
                    TaxonomyId = taxonomy != null ? taxonomy.Id : 0

                var templateName = settings.Autocomplete ? "Fields/TaxonomyField.Autocomplete" : "Fields/TaxonomyField";
                return shapeHelper.EditorTemplate(TemplateName: templateName, Model: viewModel, Prefix: GetPrefix(field, part));

        protected override DriverResult Editor(ContentPart part, TaxonomyField field, IUpdateModel updater, dynamic shapeHelper) {
            var viewModel = new TaxonomyFieldViewModel { Terms = new List<TermEntry>() };

            if (updater.TryUpdateModel(viewModel, GetPrefix(field, part), null, null)) {
                /*Localized context*/
                /*Se il contenuto è localizzato arrivano già la tassonomia e i termini corretti e localizzati quindi non devo fare nulla di diverso rispetto al driver originale*/
                var checkedTerms = viewModel.Terms
                    .Where(t => (t.IsChecked || t.Id == viewModel.SingleTermId))
                    .Select(t => GetOrCreateTerm(t, viewModel.TaxonomyId, field))
                    .Where(t => t != null).ToList();

                var settings = field.PartFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>();
                if (settings.Required && !checkedTerms.Any()) {
                    updater.AddModelError(GetPrefix(field, part), T("The field {0} is mandatory.", T(field.DisplayName)));
                } else
                    _taxonomyService.UpdateTerms(part.ContentItem, checkedTerms, field.Name);

            return Editor(part, field, shapeHelper);

        protected override void Exporting(ContentPart part, TaxonomyField field, ExportContentContext context) {
            var appliedTerms = _taxonomyService.GetTermsForContentItem(part.ContentItem.Id, field.Name);

            // stores all content items associated to this field
            var termIdentities = appliedTerms.Select(x => Services.ContentManager.GetItemMetadata(x).Identity.ToString())

            context.Element(XmlConvert.EncodeLocalName(field.FieldDefinition.Name + "." + field.Name)).SetAttributeValue("Terms", String.Join(",", termIdentities));

        protected override void Importing(ContentPart part, TaxonomyField field, ImportContentContext context) {
            var termIdentities = context.Attribute(XmlConvert.EncodeLocalName(field.FieldDefinition.Name + "." + field.Name), "Terms");
            if (termIdentities == null) {

            var terms = termIdentities
                            .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                            .Where(contentItem => contentItem != null)

            _taxonomyService.UpdateTerms(part.ContentItem, terms.Select(x => x.As<TermPart>()), field.Name);

        private TermPart GetOrCreateTerm(TermEntry entry, int taxonomyId, TaxonomyField field) {
            var term = entry.Id > 0 ? _taxonomyService.GetTerm(entry.Id) : default(TermPart);

            if (term == null) {
                var settings = field.PartFieldDefinition.Settings.GetModel<TaxonomyFieldSettings>();

                if (!settings.AllowCustomTerms || !Services.Authorizer.Authorize(Permissions.CreateTerm)) {
                    Services.Notifier.Error(T("You're not allowed to create new terms for this taxonomy"));
                    return null;

                var taxonomy = _taxonomyService.GetTaxonomy(taxonomyId);
                term = _taxonomyService.NewTerm(taxonomy);
                term.Container = taxonomy.ContentItem;
                term.Name = entry.Name.Trim();
                term.Selectable = true;

                Services.ContentManager.Create(term, VersionOptions.Published);
                Services.Notifier.Information(T("The {0} term has been created.", term.Name));

            return term;

    internal class TermPartComparer : IEqualityComparer<TermPart> {
        public bool Equals(TermPart x, TermPart y) {
            return x.Id.Equals(y.Id);

        public int GetHashCode(TermPart obj) {
            return obj.Id.GetHashCode();
    } (the interesting portion of it)
  <Place Fields_TaxonomyField_Edit="-" Fields_TaxonomyField_Localized_Edit="Content:9" />
  <Place Parts_TermPart="-" Parts_TermPart_Localized="Content:5" />
Thank you in advance
Aug 26, 2014 at 3:08 PM
Edited Aug 26, 2014 at 3:09 PM
The problem is in the get method of the editor in your driver (i´m assuming that you have the views in place as well).
There you are returning the template of the original TaxonomyField instead of TaxonomyField_Localized :
                var templateName = settings.Autocomplete ? "Fields/TaxonomyField.Autocomplete" : "Fields/TaxonomyField"; 
                return shapeHelper.EditorTemplate(TemplateName: templateName, Model: viewModel, Prefix: GetPrefix(field, part));
Aug 26, 2014 at 3:25 PM
To use original view is exactly what I want to do. I don't want to override the views too, I want to use original views.
In the meantime I found a solution (less pretty but it works)
I added this two Attrubutes to my driver
    public class TaxonomyFieldExtensionDriver : ContentFieldDriver<TaxonomyField> {
        private readonly ITaxonomyService _taxonomyService;
In this case the original dirver is not fired and my custom driver fires original views.

Is there a better alternative? Is there any negative effect to this solution?
Thank you.
Aug 26, 2014 at 3:38 PM
Well that works too.
In my case i needed to override the views also, and since a driver only gets hit when there is an entry on, it suited well my needs.
OrchardSuppressDependency is also a documented way to override something in orchard.
About negative aspects, i know none, but probably that answer could be better answered by the core developers.
Aug 28, 2014 at 7:20 AM
Edited Aug 28, 2014 at 7:21 AM
thank you for your advices and your time spent to answer.