• Search Plugin

Search Plugin

Assembly: SearchPlugin

Description

The search plugin introduces two areas that can be used for searching, they are:

  • Quick Search.
  • Advanced Search

ISearchProvider Interface

The ISearchProvider interface is used to facilitate searching within an ASP Core website, there should only be one instance of this interface registered as a singleton through DI, if a custom implementation is not registered then a default search provider will be registered.

The search provider is responsible for both performing a search and retrieving advanced search options for use within the UI to aid user searching.

ISearchKeywordProvider Interface

At the heart of both quick and advanced searching lies the ISearchKeywordProvider interface which is implemented by each plugin that wishes to expose search facilities to the search plugin.

This interface contians a method for searching which uses the KeywordSearchOptions class to pass search data and returns a list SearchResponseItem's for the search results. It also allows implementations to return advanced search configuration data that can optionally be used to provide custom search facilities within the search plugin home page.

Adding an ISearchKeywordProvider

Each plugin can choose whether to implement the following list contains a few examples of plugins that could provide search facilities

  • Products
  • News articles
  • Blog posts
  • Connect to a Solr instance

There are litterally dozens of plugin types that could implement searching. In this example we will demonstrate both a basic and advanced search for searching blogs.

Creating a blog keyword search provider is relatively simple, the first step is to create a class that descends from ISearchKeywordProvider, this provides the basic building blocks for the blog search:

public class BlogSearchKeywordProvider : ISearchKeywordProvider
{
    private readonly IBlogProvider _blogProvider;
 
    public BlogSearchKeywordProvider(IBlogProvider blogProvider)
    {
        _blogProvider = blogProvider ?? throw new ArgumentNullException(nameof(blogProvider));
    }
 
    public Dictionary<StringAdvancedSearchOptionsAdvancedSearch()
    {
        return null;
    }
 
    public List<SearchResponseItemSearch(in KeywordSearchOptions searchOptions)
    {
        if (searchOptions == null)
            throw new ArgumentNullException(nameof(searchOptions));
 
        List<SearchResponseItemResult = new List<SearchResponseItem>();
 
        List<BlogItemfoundBlogs = new List<BlogItem>();
 
        string[] words = searchOptions.SearchTerm.Split(" "StringSplitOptions.RemoveEmptyEntries);
 
        foreach (string word in words)
        {
            List<BlogItemblogs = _blogProvider.Search(word);
 
            foreach (BlogItem item in blogs)
            {
                if (foundBlogs.Count == searchOptions.MaximumSearchResults)
                    break;
 
                if (!foundBlogs.Contains(item))
                    foundBlogs.Add(item);
            }
        }
 
        foreach (BlogItem blogItem in foundBlogs)
        {
            string url = $"/Blog/{HtmlHelper.RouteFriendlyName(blogItem.Username)}/{blogItem.Id}/" +
                $"{blogItem.LastModified.ToString("dd-MM-yyyy")}/{HtmlHelper.RouteFriendlyName(blogItem.Title)}";
 
            Result.Add(new SearchResponseItem("Blog"blogItem.Title, -1, urlblogItem.Title, null));
        }
 
        return Result;
    }
 
    public List<StringSearchResponseTypes(in Boolean quickSearch)
    {
        return new List<string>() { "Blog" };
    }
}

As can be seen, all blog tags within the blog will be searched, if they match a corresponding SearchResponseItem is created and returned to the ISearchProvider implementation.

Custom View

By default the search plugin will provide it's own view for showing search results, when accessed through the advanced search option. This can be replaced with a custom partial view.

Providing a custom partial view is extremely simple, within your plugin create a partial view, the view model will always be of type SearchResponseItem. Essentially, when you perform a search, the SearchResponseItem you returned can be returned to the partial view for display. Create a simple partial view to display a blog item:

@model Middleware.Search.SearchResponseItem
<a href="@Model.Url.ToString()" style="display:block;height:105px;">
    <img src="~/images/Blog/blog64.png" style="float:left;width:64px;height:64px;" />
    <p style="margin:5px 5px 5px 75px;height:70px;padding-top:5px;">
        @Model.DisplayName
    </p>
</a>

After creating a partial view update this line which is used to create the SearchResponseItem and add the partial view name:

Result.Add(new SearchResponseItem("Blog"blogItem.Title, -1, urlblogItem.Title, null));

Becomes

Result.Add(new SearchResponseItem("Blog"blogItem.Title, -1, urlblogItem.Title, "~/Views/Blog/_BlogSearchResult.cshtml"));

As can be seen in the search results, the search results for a blog item will have a small image to the left. This in iteself is OK, however, the view can be further extended to include extra information, such as author and date published. To achieve this we can add properties to the SearchResponseItem which represent the values we want to later display in the partial view for blog searches.

foreach (BlogItem blogItem in foundBlogs)
{
    string url = $"/Blog/{HtmlHelper.RouteFriendlyName(blogItem.Username)}/{blogItem.Id}/" +
        $"{blogItem.LastModified.ToString("dd-MM-yyyy")}/{HtmlHelper.RouteFriendlyName(blogItem.Title)}";
    SearchResponseItem responseItem = new SearchResponseItem("Blog"blogItem.Title, -1,
        urlblogItem.Title, "~/Views/Blog/_BlogSearchResult.cshtml");
    responseItem.Properties.Add("Author"blogItem.Username);
    responseItem.Properties.Add("Date"blogItem.PublishDateTime.ToString("dd-MM-yyyy"));
    Result.Add(responseItem);
}

Now we have added items to the properties, we can utilise them in the blog search result partial view:

@model Middleware.Search.SearchResponseItem
<a href="@Model.Url.ToString()" style="display:block;height:105px;">
    <img src="~/images/Blog/blog64.png" style="float:left;width:64px;height:64px;" />
    <p style="margin:5px 5px 5px 75px;height:70px;padding-top:5px;">
        @Model.DisplayName
    </p>
    <p>
        <span class="chip">Author: @Model.Properties["Author"]</span>
        <span class="chip">Published: @Model.Properties["Date"]</span>
    </p>
</a>

The author and publish date will now appear within the search row.

Summary

As can be seen, extending any plugin to add search facility is extremely easy to accomplish, the results can be customised to suit the individual plugin and website. The SearchResponseItem is highly customisable and facilitates custom search results. You can implement as many ISearchKeywordProvider concrete classes as you wish, each serving a single search action, if required.