Creating WebDAV Server With Search Support (DASL)

IT Hit WebDAV Server Engine supports WebDAV DASL compliant search.

ISearchAsync Interface

To enable search support, you must implement ISearchAsync interface on folder items that support search. This interface provides a single method ISearchAsync.SearchAsync that is called when Engine receives search request passing search phrase and search options:

public interface ISearch : IItemCollection
{
    IEnumerable<IHierarchyItem> Search(string searchString, SearchOptions options);
}

In this method implementation, you will query your storage and return items that correspond to search request. You are free to return items found in a subtree, only in this folder or on the entire server, it totally depends on your implementation.

The SearchOprtions parameter contains 2 boolean flags that indicate where the search should be performed: in file content, file names or both.

When IT Hit Ajax Browser is used as a search client it will request file names search only when displaying pop-up hint:

If( SearchOptions.SearchName && !SearchOptions.SearchContent)
{
// Ajax Browser pop-up hint request
}

When requesting search results both SearchOptions.SearchName and SearchOptions.SearchContent will be set to true.

Discovering DALS Search support

To find out if your server supports search feature, the WebDAV client will submit the OPTIONS request on folder. If the Engine discovers ISearchAsync interface on that folder it will return SEARCH token in Allow header as well as DASL: <DAV:basicsearch> header.

See how IT Hit Ajax Browser is discovering Search support.

Examples of DASL Search Request

The WebDAV Engine supports DASL basic search. It can process fine content search request, file name search or both. Here is an example of search XML that searches for the word starting with “general” contained in file names or in file content:

<?xml version="1.0"?>
<searchrequest xmlns="DAV:">
  <basicsearch>
    <select>
      <prop>
        <resourcetype/>
        <displayname/>
        <creationdate/>
        <getlastmodified/>
        <getcontenttype/>
        <getcontentlength/>
        <supportedlock/>
        <lockdiscovery/>
        <quota-available-bytes/>
        <quota-used-bytes/>
        <checked-in/>
        <checked-out/>
      </prop>
    </select>
    <where>
      <or>
        <like>
          <prop>
            <displayname/>
          </prop>
          <literal>general%</literal>
        </like>
        <contains>general%</contains>
      </or>
    </where>
  </basicsearch>
</searchrequest>

You can find about search IT Hit Ajax Browser here.

Search Phrase Wildcards and Escaping

The DASL search phrase can contain wildcard characters and escape according to DASL rules:

  • ‘%’ – to indicate one or more character.
  • ‘_’ – to indicate exactly one character.

If ‘%’, ‘_’ or ‘\’ characters are used in search phrase they are escaped as ‘\%’, ‘\_’ and ‘\\’.

Example of Full-Text Search on Files Stored in File System

In the example below we will use the Windows file system indexing to search file names and file content. File system indexing can search file content, including Microsoft Office documents as well as any other documents, in case the search filter is installed in file system.

To enable search in file system, you must enable indexing on the folder where your files are located. See here how to enable indexing here.

Below is the ISearchAsync interface implementation that is using SearchAsync.CollatorDSO provider to search file system:  

public class DavFolder : IFolder, ISearch
    {
        private static readonly string windowsSearchProvider = "Provider=Search.CollatorDSO;Extended Properties=\”Application=Windows\”"

        …

        public IEnumerable<IHierarchyItem> Search(string searchString, SearchOptions options)
        {
            const string nameCondition = "System.ItemNameDisplay LIKE '@Name' ";
            const string contentCondition = @"CONTAINS('""@Content""')";
            string condition = null;
            if (options.SearchName && options.SearchContent)
                condition = String.Format("{0} OR {1}", nameCondition, contentCondition);
            else if (options.SearchName) condition = nameCondition;
            else if (options.SearchContent) condition = contentCondition;     
 
            string commandText = String.Format(
                @"SELECT System.ItemPathDisplay FROM SystemIndex " +
                @"WHERE scope ='file:@Path' AND ({0}) " +
                @"ORDER BY System.Search.Rank DESC", condition);     
 
             commandText = PrepareCommand(commandText,
                "@Path", this.dirInfo.FullName,
                "@Name", searchString,
                "@Content", searchString);   
   
           List<string> foundItems = new List<string>();
            try
            {
                using (OleDbConnection connection = new OleDbConnection(windowsSearchProvider))
                using (OleDbCommand command = new OleDbCommand(commandText, connection))
                {
                    connection.Open();
                    using (OleDbDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                            foundItems.Add(reader.GetString(0));
                    }
                }
            }
            catch (OleDbException ex) // explaining OleDbException
            {
                Logger.Instance.LogError(ex.Message, ex);
                switch (ex.ErrorCode)
                {
                    case -2147217900: throw new DavException("Illegal symbols in search phrase.", DavStatus.CONFLICT);
                    default:          throw new DavException("Unknown error.", DavStatus.INTERNAL_ERROR);
                }
            }
            return foundItems.Select(GetRelativePath).Select(context.GetHierarchyItem);
        }
    }

To see the complete code, examine the NtfsStorage sample or generate code using Visual Studio WebDAV wizards.

Example of Full-Text Search on Files Stored in Database

To search file content stored in Microsoft SQL database, including the content of Microsoft Office documents, you can use the Microsoft SQL full-text search.

To find out how to enable full-text search in the code generated by WebDAV Wizard for Visual Studio or in sample provided with SDK with SQL back-end read the Enabling Full-Text search for files stored in Microsoft SQL Database article.

Below you can see an example of the ISearchAsync.SearchAsync implementation that queries SQL server:

 public class DavFolder : IFolder, ISearch
   {
      ... 
       public IEnumerable<IHierarchyItem> Search(string searchString, SearchOptions options)
        {
            string condition = "Name LIKE @Name";  

             if (options.SearchContent)
            {
                condition += " OR CONTAINS(Content, '@Content')";
            } 
            string commandText = String.Format(
                @"SELECT 
                   ItemId, 
                   ParentItemId, 
                   ItemType, 
                   Name,
                   Created, 
                   Modified, 
                   FileAttributes                          
                   FROM Item
                   WHERE ParentItemId = @Parent AND {0}", condition);  
            List<IHierarchyItem> result = new List<IHierarchyItem>();
            GetSearchResults(result, commandText, searchString);     
            return result;
        } 
        /// <summary>
        /// Produces recursive search in current folder.
        /// </summary>
        /// <param name="result">A list to add search results to.</param>
        /// <param name="commandText">SQL command text for search in a folder.</param>
        /// <param name="searchString">A phrase to search.</param>
        private void GetSearchResults(List<IHierarchyItem> result, string commandText, string searchString)
        {
            // search this folder
            IEnumerable<IHierarchyItem> folderSearchResults = Context.ExecuteItem<DavHierarchyItem>(
                Path,
                commandText,
                "@Parent", ItemId,
                "@Name", searchString,
                "@Content", searchString);
            result.AddRange(folderSearchResults);         // search children
            foreach (IHierarchyItem item in GetChildrenFolders())
            {
                DavFolder folder = item as DavFolder;
                if (folder != null)
                    folder.GetSearchResults(result, commandText, searchString);
            }
        }      
        /// <summary>
        /// Gets the children of current folder (non-recursive).
        /// </summary>
        /// <returns>The children folders of current folder.</returns>
        public IEnumerable<IHierarchyItem> GetChildrenFolders()
        {
            string command =
                @"SELECT 
                   ItemId,
                   ParentItemId,
                   ItemType,
                   Name,
                   Created,
                   Modified,
                   FileAttributes
                   FROM Item
                   WHERE ParentItemId = @Parent AND ItemType = 3"; // folders only        
             return Context.ExecuteItem<IHierarchyItem>(
                Path,
                command,
                "@Parent", ItemId);
        }    
}

Note that for the sake of simplicity the SQL code is recursively searching folders under the search subfolder which may not be the best solution from the performance point of view.

See Also: