C# Plugins Developer Guide

  • October 30, 2020

C# Plugins Developer Guide

Note - this is an early preview of the plugins and might change very soon

Prerequisites

  • .NET 5.0 Download
  • Basic C# Knowledge
  • Rider / Visual Studio / Visual Studio Code / any C# IDE
  • Blast Nuget packages nuget.org, the package version aligns with Fluent Search version
  • Fluent Search Version >= 0.9.21.0 Download

Getting Started

Fluent Search uses Search Applications to search through many resources.

In this guide we will write a Search Application that converts numbers to Hex/Binary format.

Source Code

You can find an up-to-date code of the example below in my github.

Create new class library project

Open Powershell in any directory -

dotnet new classlib -n "NumberConverter.Fluent.Plugin"

NOTE - The plugin DLL has to end with the suffix Fluent.Plugin.dll, if not Fluent Search will not try to load it.

Go to NumberConversionSearchApp directory and edit the NumberConversionSearchApp.csproj to-

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0-windows10.0.19041</TargetFramework>
  </PropertyGroup>

</Project>

Add the nuget packages

In the Powershell run the commands -

cd NumberConverter.Fluent.Plugin

dotnet add package Blast.API --prerelease

dotnet restore

Implementation

You will need to add the following files to the NumberConverter.Fluent.Plugin directory.

Create a search result

Each Search Application in Fluent Search returns ISearchResult object that contains all the relevant information of the search result. (the left side of Fluent Search)

Add a new file called NumberConversionSearchResult.cs with the following code -

using System.Collections.Generic;
using Blast.Core.Interfaces;
using Blast.Core.Objects;
using Blast.Core.Results;

namespace NumberConverter.Fluent.Plugin
{
    public sealed class NumberConversionSearchResult : SearchResultBase
    {
        public NumberConversionSearchResult(int number, string searchAppName, string convertedNumber, string resultName, string searchedText,
            string resultType, double score, IList<ISearchOperation> supportedOperations, ICollection<SearchTag> tags,
            ProcessInfo processInfo = null) : base(searchAppName,
            resultName, searchedText, resultType, score,
            supportedOperations, tags, processInfo)
        {
            Number = number;
            ConvertedNumber = convertedNumber;
            // You can add Machine Learning features to improve search predictions
            MlFeatures = new Dictionary<string, string>
            {
                ["ConvertedNumber"] = ConvertedNumber
            };
        }

        public int Number { get; }
        
        public string ConvertedNumber { get; set; }

        protected override void OnSelectedSearchResultChanged()
        {
        }
    }
}

The Blast.API gives the pre-implemented SearchResultBase so you won't need to implement ISearchResult.

Create the search operation

Every search results contains a list of ISearchOperation (the right side of Fluent Search)

The user can select any operation to trigger it.

Add a new file called NumberConversionSearchOperation.cs with the following code -

using Blast.Core.Results;

namespace NumberConverter.Fluent.Plugin
{
    public enum ConversionType
    {
        Any,
        Hex,
        Binary
    }

    public class ConversionSearchOperation : SearchOperationBase
    {
        public ConversionType ConversionType { get; }

        public static ConversionSearchOperation HexConversionSearchOperation { get; } =
            new ConversionSearchOperation(ConversionType.Hex);

        public static ConversionSearchOperation BinaryConversionSearchOperation { get; } =
            new ConversionSearchOperation(ConversionType.Binary);

        public ConversionSearchOperation(ConversionType conversionType) : base($"Convert more {conversionType} in web",
            $"Opens a {conversionType} conversion website", "\uE8EF")
        {
            ConversionType = conversionType;
        }
    }
}

This is a basic implementation of the two supported operations - Hex / Binary.

We used static code to not create the same object each time.

Create the search application

You must add a search application, which is a class that implements ISearchApplication.

Add a new file called NumberConversionSearchApp.cs with the following code -

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Blast.API.Core.Processes;
using Blast.API.Processes;
using Blast.Core;
using Blast.Core.Interfaces;
using Blast.Core.Objects;
using Blast.Core.Results;

namespace NumberConverter.Fluent.Plugin
{
    public class NumberConversionSearchApp : ISearchApplication
    {
        private const string SearchAppName = "NumberConvertor";
        private readonly List<SearchTag> _searchTags;
        private readonly SearchApplicationInfo _applicationInfo;
        private readonly List<ISearchOperation> _supportedOperations;

        public NumberConversionSearchApp()
        {
            // For icon glyphs look at https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font
            _searchTags = new List<SearchTag>
            {
                new SearchTag
                    {Name = ConversionType.Hex.ToString(), IconGlyph = "\uE8EF", Description = "Convert to hex"},
                new SearchTag
                    {Name = ConversionType.Binary.ToString(), IconGlyph = "\uE8EF", Description = "Convert to binary"}
            };
            _supportedOperations = new List<ISearchOperation>
            {
                ConversionSearchOperation.HexConversionSearchOperation,
                ConversionSearchOperation.BinaryConversionSearchOperation
            };
            _applicationInfo = new SearchApplicationInfo(SearchAppName,
                "This apps converts hex to decimal", _supportedOperations)
            {
                MinimumSearchLength = 1,
                IsProcessSearchEnabled = false,
                IsProcessSearchOffline = false,
                ApplicationIconGlyph = "\uE8EF",
                SearchAllTime = ApplicationSearchTime.Fast,
                DefaultSearchTags = _searchTags
            };
        }

        public ValueTask LoadSearchApplicationAsync()
        {
            // This is used if you need to load anything asynchronously on Fluent Search startup
            return ValueTask.CompletedTask;
        }

        public SearchApplicationInfo GetApplicationInfo()
        {
            return _applicationInfo;
        }

        public async IAsyncEnumerable<ISearchResult> SearchAsync(SearchRequest searchRequest,
            [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested || searchRequest.SearchType == SearchType.SearchProcess)
                yield break;
            string searchedTag = searchRequest.SearchedTag;
            string searchedText = searchRequest.SearchedText;
            ConversionType conversionType = ConversionType.Any;
            // Check that the search tag is something this app can handle

            if (!string.IsNullOrWhiteSpace(searchedTag))
            {
                if (!searchedTag.Equals(SearchAppName, StringComparison.OrdinalIgnoreCase) &&
                    !Enum.TryParse(searchedTag, true, out conversionType))
                    yield break;
            }

            if (!int.TryParse(searchedText, out int number))
                yield break;

            if (conversionType == ConversionType.Any || conversionType == ConversionType.Hex)
            {
                string convertedNumber = number.ToString("X");
                yield return new NumberConversionSearchResult(number, SearchAppName, convertedNumber,
                    $"Hex {searchedText} is {convertedNumber}", searchedText, "Hex", 2,
                    _supportedOperations, _searchTags);
            }

            if (conversionType == ConversionType.Any || conversionType == ConversionType.Binary)
            {
                string convertedNumber = Convert.ToString(number, 2);
                yield return new NumberConversionSearchResult(number, SearchAppName, convertedNumber,
                    $"Binary {searchedText} is {convertedNumber}", searchedText, "Binary", 2,
                    _supportedOperations, _searchTags);
            }
        }

        public ValueTask<ISearchResult> GetSearchResultForId(string serializedSearchObjectId)
        {
            // This is used to calculate a search result after Fluent Search has been restarted
            // This is only used by the custom search tag feature
            return new ValueTask<ISearchResult>();
        }

        public ValueTask<IHandleResult> HandleSearchResult(ISearchResult searchResult)
        {
            if (!(searchResult is NumberConversionSearchResult numberConversionSearchResult))
            {
                throw new InvalidCastException(nameof(NumberConversionSearchResult));
            }

            if (!(numberConversionSearchResult.SelectedOperation is ConversionSearchOperation conversionSearchOperation)
            )
            {
                throw new InvalidCastException(nameof(ConversionSearchOperation));
            }

            // Get Fluent Search process manager instance
            IProcessManager managerInstance = ProcessUtils.GetManagerInstance();
            switch (conversionSearchOperation.ConversionType)
            {
                case ConversionType.Hex:
                    managerInstance.StartNewProcess(
                        $"https://www.hexadecimaldictionary.com/hexadecimal/{numberConversionSearchResult.Number:X}");
                    break;
                case ConversionType.Binary:
                    managerInstance.StartNewProcess(
                        $"https://www.binary-code.org/binary/16bit/{Convert.ToString(numberConversionSearchResult.Number, 2)}");
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return new ValueTask<IHandleResult>(new HandleResult(true, false));
        }
    }
}

This Search Application converts the searched text to hex/binary based on the searched tag.

For example if you receive -

Searched Text - "10"

Searched Tag - ""

The results will be "Hex 10 is A" and "Binary 10 is 1010".

And for -

Searched Text - "10"

Searched Tag - "hex"

The results will be only "Hex 10 is A".

Load the search app

Compile

First you should compile your plugin for release.

In the Powershell you opened eariler run -

dotnet publish -c Release -r win10-x64

or if you have any dependencies

dotnet publish -c Release -r win10-x64 --self-contained=true

And copy all the files (you can copy only your dlls, in this case it's NumberConversionSearchApp.dll) from-

{YourDir}\NumberConversionSearchApp\bin\Release\net5.0-windows10.0.19041\win10-x64\publish

to -

If installed through Microsoft Store - C:\Users\{your_user_name}\AppData\Local\Packages\21814BlastApps.BlastSearch_pdn8zwjh47aq4\LocalCache\Roaming\Blast\FluentSearchPlugins\NumberConversionSearchApp\

If installed through sideload - C:\Users\{your_user_name}\AppData\Local\Packages\FluentSearch.SideLoad_4139t8dvwn2ka\LocalCache\Roaming\Blast\FluentSearchPlugins\NumberConversionSearchApp\

You will need to create these directories.

Plugins info file

In addition you will need to add a file called pluginsInfo.json to your plugin directory, with the following information -

{
  "IsEnabled": true,
  "InstalledVersion": "1.0.0.0",
  "Name": "NumberConverter",
  "DisplayName": "Number Converter",
  "Description": "Use hex/binary tags to convert numbers",
  "PublisherName": "Blast Apps",
  "Version": "1.0.0.0",
  "URL": "https://github.com/adirh3/NumberConverter.Fluent.Plugin/",
  "IconGlyph": "\uE8EF"
}

You can find icon glyphs here.

Restart Fluent Search and check if your search application is working!

Troubleshooting

If the Search Application does not load, please check for logs in the plugin directory, in this case -

..\FluentSearchPlugins\NumberConversionSearchApp

Usually a plugin will fail to load when one or more of it dependencies are missing, so make sure you copy all the relevant DLLs to the plugin directory.

For help please send a mail to support@fluentsearch.net

About Fluent Search

Fluent Search feature showcase, updates, news and more