Skip to content

Creating the pieces needed to query content based on assigned taxonomies

What you will build

In this example, you will build a search tool to filter blog posts based on the assigned taxonomies. Of course, this does not need to be restricted to filtering blogs.

What you will need

You will start from a new Orchard Core site built using the Blog recipe.

You will be using a Lucene query, made dynamic with a Liquid template.

You will use a Razor file to call this query.

Setting things up

To make this sample a little more interesting, we're going to edit the BlogPost content type to allow multiple Category assignments.

Go to Content > Content Definition > Content Types and click to edit Blog Post. Then click Edit next to the "Category" Taxonomy field. Uncheck Unique and click Save. While we're here, since we want to make the 2 taxonomy fields (Category and Tags) searchable, you can do that now. Click to edit Category, then check the box for Include this element in the index.

Now do the same for the Tags field on the Blog Post content type.

Be sure to rebuild the index by navigating to Search > Indexing > Lucene Indices and then clicking "Rebuild."

Then navigate to Content > Content Types > Taxonomy and click to edit Categories. Then click Add Category to add "Politics". Then publish the taxonomy.

You will probably want to create a few more blog posts and assign various compbinations of Tags and Categories so you can try out your filter.

Start by adding a new Lucene query. Name it GetBlogsByFilter. You can leave the schema blank, leave the Index set to the default (search) and check the box to Return Content Items.

The query itself will use Liquid to:

  • check if we have a value for each of the filters
  • build the correct blocks based on the filter data

A quick review

The Blog recipe creates 2 Taxonomy content types for us, Categories and Tags. What we are going to build will allow us to pass 0 or more Categories and 0 or more Tags and get back the set of BlogPosts that are connected to these filters.

A filter object

A good way to model this filter in JSON is like this:

{
  categories: [...],
  tags: [...],
}

A starting point query

{
    "size": 10,
    "query": {
      "term": { "Content.ContentItem.ContentType" : "BlogPost" }
    }
}

The query above will fetch us 10 BlogPosts, without using a filter. This is the query we will use if the filter we receive looks like this:

{
  categories: null,
  tags: null,
}

A smarter query

To create that logic, we can do this:

{
    "size": 10,
    "query": {
{% if categories or tags %}

{% else %}
    "term": { "Content.ContentItem.ContentType" : "BlogPost" }
{% endif %}
    }
}

Notice that the if fails, the else block runs and we get back the 10 BlogPosts.

A functional query

To do the actual filter work, we'll make use of bool, must and match. Match will take care of allowing multiple terms to be joined together by an OR. And Must will manage the need for the 2 different taxonomies to be joined with an AND.

Consider this block:

"match": {
  "BlogPost.Category": {
    "query":"4..a 4..b",
    "operator": "or"
  }
}

It will bring us back BlogPost content items with the Category set to either "4..a" or "4..b".

To create the AND condition across taxonomy fields, we'll be wrapping the match blocks in a must block. We'll use Liquid to include the match block only when needed. And we'll also use Liquid to loop over the values being passed in the array.

"must": [
    {% if categories %}
        {
      "match":
      {
        "BlogPost.Category":
        {
          "query":"{% for cat in categories %}{{cat}} {% endfor %}",
            "operator": "or"
        }
        }
      },
    {% endif %}
    {% if tags %}
        {
      "match":
      {
        "BlogPost.Tags": {
            "query":"{% for tag in tags %}{{tag}} {% endfor %}",
            "operator": "or"
        }
        }
    },
    {% endif %}

Finally, gluing it all together into a usable query:

{
    "size": 10,
    "query": {
        {% if categories or tags %}
    "bool": {
            "must": [
                {% if categories %}
                    {
                        "match": {
                            "BlogPost.Category": {
                                "query": "{% for cat in categories %}{{cat}} {% endfor %}",
                                "operator": "or"
                            }
                        }
                    },
                {% endif %}
                {% if tags %}
                    {
                        "match": {
                            "BlogPost.Tags": {
                                "query":"{% for tag in tags %}{{tag}} {% endfor %}",
                                "operator": "or"
                            }
                        }
                    },
                {% endif %}
            ],
        }
    {% else %}
      "term": { "Content.ContentItem.ContentType" : "BlogPost" }
    {% endif %}
    }
}

Using this Query in Code

This article is not intended to provide a complete implementation. However, as a simple example, you could do something like this in a View:

  var parameters = new Dictionary<string, object>();
  parameters.Add("categories", new string[] { "4pgm1krsy9nmyre41y5dj2p7df" });
  parameters.Add("tags", null);
  var posts = await Orchard.QueryAsync("GetBlogsByFilter", parameters);

  foreach (ContentItem post in posts)
  {
      await Orchard.DisplayAsync(post);
  }

Summary

You just created the pieces needed to search through blogs to find only the ones with specific tags assigned.


Last update: November 9, 2021