As we continue our exploration of the capabilities of Microsoft’s Azure Search service, we will move from the simple, text-based search that we set up in our last post and will move on to faceted search.  Faceted search is a very intuitive way for users to explore data because it presents information in a way that users are familiar – by categories.  Further, when you couple categories and counts within categories, users get a very good feel for the data at-a-glance.

It will be helpful if you read these articles for a primer on the faceted search paradigm:

When setting up facets, you have to understand your data so that you can present the most intuitive path for your users to find their way to the data they care about.  To start this, you need to determine commonalities within your data.  Let’s take a subset of our sample data as an example.  In this example, I have used our simple, text-based search to find all of the airports in Alabama.

P4-0-ALResultSet

Obviously, the state of AL is in each record.  Further, region and chart have pretty similar answers.  It is pretty easy to see that these fields would be good candidates for faceted search fields.  Indeed, if you look at the code that we wrote previously to import our airport data, you will see that those are the three fields that we set as “IsFacetable”:

At this point, we will be using some of the more powerful features of Azure Search, but it is important to understand that all of the rich data descriptions that we will want such as facets, counts, and paging are nothing more than a single query over the search index that we have created in Azure.  As we will see in our example, the view of the data that we present back to the user is the result of a single data query with more and more refinement on each pass.

With Azure Search, facets are really very simple and need to be understood as 1) a way to dissect current search results and 2) a roadmap for building search refinement filters.  In our sample in its current form, we merely provide a string as a search criteria.  We are returned a list of results, in our case, airports.  However, we may want to provide our users with the number of airports in Alabama.  Or maybe we will want to show the user how many airport in Alabama are on the Atlanta chart.  We will use facets to tell Azure Search how we want to describe the data to our user.  We will ask the search for facet information and it will return to us a view of the data that has been modified by our search refinement criteria.

Let’s go wire up facets in our example to demonstrate these concepts of faceted search in Azure.

First, let’s go clean up our routes so that we only have the search page.  In the AFDSearch project, go to AppStart\RouteConfig.cs.  In that file change the default route of home\index to airport\search, like this:

Now that we are pointed to the correct controller for startup, let’s go work on our models.  Let’s add a new model, called FacetInfo, that we will reference in our AirportSearch model.  Right-click on the Models folder and add a new cs file, called FacetInfo.  In this model, we will have a “FacetName” property.  This property will hold our top-level facet name, like Region, State, or Chart.  Next, we’ll add a new dictionary of string and long called “Facets”.  This dictionary will contain the actual results found for each facet.  And, since we don’t want to do a lot of null checking over in our razor template, we will use our constructor to create a default dictionary with 0 elements.  Here is the code for this class:

Now lets go add a reference to this class in our AirportSearch model.  While we are in this file, we will also add “Filter” and “Top” properties that we will use later.

Now that our models are all ready to go, let’s go modify our controller, AirportController to be able to handle facets.  You can scroll down to see the all of the controller code following this explanation.

The first thing that we need to do is handle the fact that this controller action, Search, is now loaded immediately.  When Search is called, we want to return the facets for the entire set of data.  The user hasn’t entered anything, but we will make a query over all of the data on their behalf just to get the facet information.  In order to do this, we need to detect that the SearchText field is null and replace the text with “*” to indicate to Azure Search that we are interested in EVERY document in the index.

Further, if we have no search text or filter we will set the “Top” parameter to 0 to indicate that we don’t want any records returned.  By default, Azure Search will return a maximum of 50 documents.  The Top parameter is used to override this default to get no records back, as we are doing here since we are only interested in facet data.  We’ll discuss Top more in our next post on paging.  Suffice it to say for now, we only want facet data now and we’ll tell Azure Search that very thing by setting top to 0.

We need to define which facet fields we are interested in getting data for on searches.  Normally, your client doesn’t know the field names to put in the facet fields string array.  You will want to add a controller in your production code to return the list of facets so that they will be discoverable.  However, for the sake of our example, we will set the facets ourselves by creating a string list of the facet field names:

Now that we have the list of facets and the top value, we will feed them into the BlackBarLab.Search.Azure search engine.  You will notice that facetfields is sent in, but now we receive the facet information from the query on a callback, which we add to a facetResults list.

Finally, our controller will set the results on and then return the model with our search data:

Here is the entire controller, for context:

The last step that we have is to update our view to display the facet data.  Let’s take a look at the ~\Views\Airport\Search.cshtml file.  To display the facet data, we create a table with two columns.  The first column has a list of facet results that are interactive, meaning that they can be used to refine the search results.  The second column contains the search field, which is still very viable to filter data, and the results table.  Outside of the basic razor code to generate the html, there is one thing going on here that is interesting.

For every facet that is created, a new model is created to be passed to the controller action.  This model is created and the Filter property is set.  This filter contains the OData query that is used to segregate the search by facet.  This filter is concatenated with the prior filter so that we can drill down through the data with progressively specific filters. Here is the entire view:

Now that the route, models, controller, and view are cooked, let’s take a look at how facets work in our example.  When our page loads, the route directs us directly to AirportController.Search.  The query over the entire search index is executed and we receive our facets.  The result looks like this:

P4-1-Startup

Bear in mind that this is just a subset of the A/FD data.  It should not be considered complete and DEFINITELY should not be used as a source for aviation.  Having made that disclaimer, here you can see how we are using Azure Search to segregate our data by Region, State, and Chart.  You can see the total number of results for each facet.

If you click a facet, say, “AL” the controller will be called again with the filter “State eq ‘AL'”.  This will yield all of the airports from Alabama, as partially seen here:

P4-2-ALFacetResults

You will notice that we have airport results from Alabama, but our facets have changed.  From the facets, we see that there are 84 airports in Alabama, 84 of those are in the Southeastern region and that they are scattered across three charts, the Atlanta, New Orleans, and Memphis charts.  Now, click on the Memphis facet.  The view will reload with just the 4 airports in Alabama that are on the Memphis chart.

P4-3-ALandMemphisSearch

You can now hit the clear button and start over.  You can also add search text at any time.  In this example, the search text overrides all facet selections, but you could use the search text to further refine based on your facets.  It just depends on your application.

You can see that adding facets to your search UI can be a huge win for users in terms of better understanding the data they are working with.  In our next post, Microsoft Azure Search – A Practical Introduction – Part 5, Paging, we will take these faceted results and make them more manageable through paging.