up

Development

When developing your search functions you mainly use the SearchRepository class. The query parameter in the search method supports Solr syntax. If just entering the user search term as parameter your configuration settings from the SearchSettings section will be used, but if you use Solr syntax and define which field to search, e.g. "text: umbraco", SearchSettings configuration will be ignored, assuming you define the entire search yourself in the query parameter.

Class library

You can browse base classes and namespaces in our class library documentation

Example search result page

Here is simple Razor macro example for searching and presenting searchresults and also some example facets.

@using System.Globalization
@using SolisSearch.Web
@using SolisSearch.Helpers
@using SolisSearch.Repositories
@inherits umbraco.MacroEngines.DynamicNodeContext

<script type="text/javascript">
    $(document).ready(function() {

        $("#txtSearch").keydown(function (e) {
            if (e.which == "13") {
                $("form").submit();
            }
        });

        $(".facetlink").click(function() {
            var filter = $(this).attr("data-filter");
            $("#filters").append("<input name='fq' type='hidden' value='" + filter + "¤' >");
            $("form").submit();

        });
        
        $("#filters").on("click", "a", function () {
            $(this).prev("input").remove();
            $("form").submit();
        });
    });
</script>
<style>
    .searchResults { float: left;}
    .searchResults em { font-style: italic;font-weight: bold;}
    .searchResults li { margin-bottom: 20px;}
    .facets { float: right;width: 200px;}
    #txtSearch {
        padding: 3px;
        border-radius: 5px;
    }
</style>

@* The fun starts here *@

@{
    var query = HttpUtility.UrlDecode(Request.Form["q"] ?? Request.QueryString["q"]); //formbased search presedence over linked search
    var fq = new List<string>();
    
    <input id="txtSearch" type="search" name="q" value="@query" />
    
    if (!String.IsNullOrEmpty(Request["fq"]))
    {fq.AddRange(Request["fq"].TrimEnd(Convert.ToChar("¤")).Split(new[] { "¤," }, StringSplitOptions.RemoveEmptyEntries));}
    
    <div id="filters">
        @{
            if (fq.Any())
            {

                foreach (var field in fq)
                {
                    <input name="fq" type="hidden" value="@string.Format("{0}¤", field)"/>
                    <a class="btn btn-success" href="#">@Html.Raw(RangeFormatHelper.FormatRange(field)) <i class="icon-remove"></i></a>
                }

            }
        }
            
    </div>
    
    
    if (!String.IsNullOrEmpty(query) || fq.Any())
    {
        var languageName = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;


        var umbracoLogger = new LogFacade(typeof(SearchRepository));
var searchRepo = new SearchRepository(umbracoLogger); var searchResultItems = searchRepo.SearchIndex(query, fq.ToArray(), 1, 0, null, languageName); if (searchResultItems.Any()) { <div class="searchResults" style="width: 70%;"> <p>@string.Format("Total of {0} items found", searchResultItems.NumFound)</p> <ol> @foreach (var searchitem in searchResultItems) { <li> <h4><a href="@Html.Raw(searchitem.LinkUrl ?? searchitem.ResourceName)">@Html.Raw(searchitem.Name ?? searchitem.DocumentTitle.FirstOrDefault() ?? Path.GetFileNameWithoutExtension(searchitem.ResourceName))</a></h4> @{ var highlightedSnippets = searchResultItems.Highlights[searchitem.Id]; if (highlightedSnippets != null && highlightedSnippets.Any()) { foreach (var highlightItem in highlightedSnippets) { @Html.Raw(string.Join(" ", highlightItem.Value)) } } else { var contentString = string.Join(" ", searchitem.Content); @Html.Raw(contentString.Substring(0, Math.Min(contentString.Length, 175))) } } </li> } </ol> </div> <div class="facets"> <ul> @if (searchResultItems.FacetDates.Any()) { foreach (var facetDate in searchResultItems.FacetDates) { foreach (var dateResults in facetDate.Value.DateResults) { <li> <a class="facetlink" data-filter="@dateResults.Key" data-value="@dateResults.Key" href="javascript:void(0);"> @Html.Raw(dateResults.Key.ToShortDateString() + " (" + dateResults.Value + ")")</a> </li> } } } @if (searchResultItems.FacetFields.Any()) { foreach (var facetField in searchResultItems.FacetFields) { var valuelist = facetField.Value; foreach (var keyValuePair in valuelist) { <li><a class="facetlink" data-filter="@Html.Raw(string.Format("{0}:{1}", facetField.Key, keyValuePair.Key))" href="javascript:void(0);">@Html.Raw(keyValuePair.Key + " (" + keyValuePair.Value + ")")</a></li> } } } @if (searchResultItems.FacetQueries.Any()) { foreach (var facetqueries in searchResultItems.FacetQueries) { <li><a class="facetlink" data-filter="@facetqueries.Key" data-value="@facetqueries.Key" href="javascript:void(0);">@Html.Raw(RangeFormatHelper.FormatRange(facetqueries.Key) + " (" + facetqueries.Value + ")")</a></li> } } </ul> </div> } else { <p>No search results</p> } } }

Autocomplete example

Using autocomplete on the search input field is an efficient way to help your users find what they are looking for. Here is an example using the typeahead functions in Twitter Bootstrap and a generic handler.

Example markup

 <script type="text/javascript">     
        $(document).ready(function() {
$("#txtQuickSearch").typeahead({ source: function (query, process) { return $.ajax({ cache: false, dataType: "json", url: "handlers/autocomplete.ashx", data: "searchvalue=" + query, success: function (data) { return process(data); } }); }, items: 20 }); }); </script>
<form action="/search" method="GET"> <input name="q" id="txtQuickSearch" autocomplete="off" /> <button type="submit">Search</button> </form>

Generic handler

using System.Web;
using System.Web.Script.Serialization;
using SolisSearch.Repositories;

 public class AutoComplete : IHttpHandler
{ public void ProcessRequest(HttpContext context)
{ context.Response.ContentType = "application/json"; var searchval = context.Request.QueryString["searchvalue"]; var umbracoLogger = new LogFacade(typeof(SearchRepository));
var searchrepo = new SearchRepository(umbracoLogger); var list = searchrepo.AutoComplete(searchval); var jsserializer = new JavaScriptSerializer(); context.Response.Write(jsserializer.Serialize(list)); } public bool IsReusable { get { return false; }
} }

Customizing and extending Solis Search

Class library

You can browse base classes and namespaces in our class library documenatation

Standard parser

In Solis Search core library there is the Default parser which returns the string value of the property and is used both for Umbraco and EPiServer. This is the parser always used if the parser attribute is not set on the the property in SolisSearch.config.

Umbraco parsers

Related Links Parser, SolisSearch.Umbraco.Parsers.RelatedLinksParser, this parser is used for finding files in the Media section in Umbraco using either the built in Related Links property, uComponents Multi-Url picker or the Digibiz Advanced Media Picker.

Grid Parser, SolisSearch.Umbraco.Parsers.GridParser, this parses the Grid property type in Umbraco 7 which contains a lot of JSON, but this parser only returns the value properties from the JSON.

Creating your own parser

As a developer you can decide how to index the properties on your document types by implementing your own property parser. This is done by implementing the IPropertyParser interface in in SolisSearch.Interfaces.

In the interface you'll find the followning properties:

        /// <summary>
        /// Gets or sets the current cms node.
        /// </summary>
        ICmsContent CurrentCmsNode { get; set; }

        /// <summary>
        /// Gets or sets the current cms property.
        /// </summary>
        ICmsProperty CurrentCmsProperty { get; set; }

        /// <summary>
        /// Gets or sets the current solis property.
        /// </summary>
        Configuration.ConfigurationElements.Property CurrentSolisProperty { get; set; }

These properties will be set by the IndexingRepository and will be available for use when implementing this method:

        /// <summary>
        /// The get property value.
        /// </summary>
        /// <param name="cmsPropertyValue">
        /// The cms property value.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        string GetPropertyValue(object cmsPropertyValue);

This method should return the string representation of how the property should be added to the Apache Solr index. In its simplest version it would just return the input parameter cmsPropertyValue.ToString(). To make Solis Search use your own PropertyParser you configure the property in SolisSearch.config with the attribute "parser", e.g:

<Property name="p8" property="relatedLinks" type="relatedLinks" parser="SolisSearch.Umbraco.Parsers.RelatedLinksParser,SolisSearch.Umbraco" />

Advanced queries

To execute a standard query you call the SearchIndex method in the SearchRepository. Sometimes you need to create more advanced queries so starting from version 2.0.111.455 there is a method called CreateStandardQuery which sets up the query like the SearchIndex method but does not execute it.

SolisSearch uses the SolrNet library to communicate with the Apache Solr server and using the CreateStandardQuery method you can then change, add or remove options in the query using SolrNet functions before executing the query. Learn more about using the SolrNet library on https://github.com/mausch/SolrNet/tree/master/Documentation.

Here is example codesnippet performing a search for content only existing under a node with id 1078 in the content tree:

var standardQuery = searchRepo.CreateStandardQuery(query, searchfilters, page, pageSize, sortOrder, language);

standardQuery.QueryOptions.FilterQueries.Add(new SolrQueryByField("breadcrumbs", "1078;*") { Quoted = false });
var searchResultItems = searchRepo.ExecuteQuery(standardQuery.LocalParams + standardQuery.Query, standardQuery.QueryOptions);