Appi.Infrastructure
1.3.0
dotnet add package Appi.Infrastructure --version 1.3.0
NuGet\Install-Package Appi.Infrastructure -Version 1.3.0
<PackageReference Include="Appi.Infrastructure" Version="1.3.0" />
paket add Appi.Infrastructure --version 1.3.0
#r "nuget: Appi.Infrastructure, 1.3.0"
// Install Appi.Infrastructure as a Cake Addin #addin nuget:?package=Appi.Infrastructure&version=1.3.0 // Install Appi.Infrastructure as a Cake Tool #tool nuget:?package=Appi.Infrastructure&version=1.3.0
Appi
<img src="https://github.com/jordi1988/Appi/blob/master/logo.png" alt="Logo" width="150">
What is Appi
? Appi is short for App information.
The goal is to query your sources for information through one tool; all at once, in groups or individually, highly extensible. Accessible from your favorite shell with easy-to-remeber commands from your keyboard without even touching your mouse.
Because your information sources will be different from mine, go start building your first plugin.
Table of Contents
Features
- Asynchronously fetch all your sources matching your query
- Easily provide your custom plugin based on pre-built infrastructure classes
- Set up your preferences (e.g. culture, colors, and paths)
- Make use of the given toolset (e.g. for backward navigation)
- Make use of the DI container
- Localized messages und UI
Installation
Choose your desired way:
- Install via NuGet:
dotnet tool install --global Appi
- Download binaries from GitHub Releases
- Build on your own
- Clone repository
- Restore dependencies
- Build solution (example plugins will get copied into )
Take a look at the GitHub Releases page for updates and release notes.
Set up your preferences in %AppData%\Appi\preferences.json
. This file is expected at the specified location. Any settings inside can be changed:
| Setting | Description | Default value |
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
| AppDataDirectory | The application directory of Appi. Can be viewed with appi config open
command.. | %AppData%\Appi\ |
| SourcesFilename | The full filename for the file containing the sources configuration. | %AppData%\Appi\sources.json |
| UiCulture | The culture Appi is displayed in. | Current UI culture |
| PageSize | The size of the page. | 35 |
| AccentColor | The color of the accent. See all available colors. | Red |
| Legend.Visible | Value indicating whether the legend is printed or not. | true |
| Legend.SourceColors | The source colors for the legend. See all available colors. | ["SkyBlue", "Magenta", ...] |
Examples
Demo
This demo shows the find
command searching for st
within all active sources (Customers SQLite database
, Address MySQL database
, User SQL Server database
, Demo Assembly
, and More
), selecting and displaying one user result (UserId and Username - properties based on DetailViewColumnAttribute
), goes back, selecting a customer result, and finally taking some action on it (Copy address
).
Next actions searches for joh
inside the single cust
source (alias for customer - can be defined in settings), picks one, and calls its phone number.
Screenshot of multiple results
This screenshot shows the find
command searching for e
within all active sources (4). This is for demonstration purposes only, do not try to find any logic in it ๐
Supported commands
find
command (default, can be omitted)
DESCRIPTION:
Query all (default) or only the specified sources.
USAGE:
Appi find <query> [OPTIONS]
EXAMPLES:
Appi god
Appi god -s poetry
Appi god -g demo
Appi find god
Appi find god --source poetry
Appi find god --group demo
ARGUMENTS:
<query> Search for the given query
OPTIONS:
DEFAULT
-h, --help Prints help information
-g, --group all Search within a group
-s, --source Search within a single source
-c, --case-sensitive The query parameter will be case-sensitive
list
command
List all your installed sources with Appi list
and see how they can be queried using the --source
or --group
option.
โโโโโโโโโโโโโโโโโโโโคโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโโโโโโโโโคโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Name โ Description โ Active โ Source alias (-s / --source) โ Group aliases (-g / --group) โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโข
โ scraped.txt File โ Contents of the file. โ โ scrapedtxtfile โ โ
โ Poetry โ by poetrydb.org โ X โ poetry โ demo โ
โ More โ Non-contextual options โ X โ more โ โ
โ Demo Assembly โ Returns hard-coded hello world. โ X โ external โ demo โ
โโโโโโโโโโโโโโโโโโโโงโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโงโโโโโโโโโงโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโงโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
config
command
Need to install a your newly created plugin or open the configuration file? Here is how:
DESCRIPTION:
Configure Appi.
USAGE:
Appi config [OPTIONS] <COMMAND>
EXAMPLES:
Appi config open
Appi config allow-libs true
Appi config register-lib E:\my-own-appi-plugin.dll
OPTIONS:
-h, --help Prints help information
COMMANDS:
open Opens the app's configuration directory
allow-libs <allowed: true|false> Allow or disallow external libraries to be loaded
register-lib <path> Register a new library which is copied to application directory and registred in sources.json
See an example of the configuration file here.
Plugins
This app is highly extensible by adding own plugins. You can fetch data from any source you can imagine, e. g. from your SharePoint Server, Active Directory or any database.
Just follow these simple steps:
- Create a .NET 7 class library
- Add the
Appi.Core
NuGet package as a dependencyPM> Install-Package Appi.Core
for plugin development from scratch orPM> Install-Package Appi.Infrastructure
for plugin development with pre-built infrastructure like File access, MySQL/MariaDB, SQLite or Microsoft SQL Server
- Create classes that implement
ISource
andResultItemBase
(see GitHub examples) - Register the new assembly by calling
appi config register-lib "pathToAssembly.dll"
- Use the
--copy-only
parameter if you are updating your plugin, so that it is not registered again. - Use the
--register-only
parameter if you want to debug your plugin. See details right below.
- Use the
- If applicable: change connection string(s) in sources.json (
appi config open
)
Having trouble developing a plugin?
It should help to clone and open this repository, create your plugin project as described above, and edit your *.csproj
as follows:
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<BaseOutputPath>..\..\src\Ui.Appi\bin</BaseOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<BaseOutputPath>bin</BaseOutputPath>
</PropertyGroup>
That way, your plugin gets copied into the app's directory on debug
builds and can therefore be debugged.
Note that your plugin must be registered in the sources.json
file, but should only be included once in ..\..\src\Ui.Appi\bin
then (not in %AppData%\Appi
, too).
Infrastructure
Some infrastructure classes are already provided. Fetching data from your database is as easy as writing the SQL query for it. You can build up from given classes like:
- File (see
sources.json
after runningappi config open
and change the path of your text file) - SQL Server (MSSQL)
- MySQL / MariaDB
- SQLite
- More to come out of the box (want to collaborate?)
Dependency Injection (DI)
You can inject every service into your own plugin that is registered in Program.cs
.
Examples of custom services are IHandler
, IHandlerHelper
, IResultStateService<>
, ISettingsService
, or IPluginService
.
Of course you can also use the built-in services like IStringLocalizerFactory
, IStringLocalizer<>
, ILoggerFactory
, ILogger<>
, IOtions<>
, ...
So just define its type in your constructor. In case of multiple constructors, the first one with the most parameters will be chosen.
Example for implementing ISource
The class implementing ISource
can have any constructor. The services will be constructed using the DI container. A service will be null
if it is not registered.
The ReadAsync()
method must pass the FindItemsOptions
object which contains the query and returns the found data.
The initial values of the properties are used to create the entry in the sources.json
config file when installing your plugin.
using Core.Abstractions;
using Core.Models;
namespace Infrastructure.ExternalSourceDemo
{
public class ExternalDemoSource : ISource
{
private readonly IHandlerHelper _handlerHelper;
public string TypeName { get; set; } = typeof(ExternalDemoSource).Name;
public string Name { get; set; } = "Demo Assembly";
public string Alias { get; set; } = "external";
public string Description { get; set; } = "Returns hard-coded hello world.";
public bool IsActive { get; set; } = true;
public int SortOrder { get; set; } = 50;
public string? Path { get; set; }
public string? Arguments { get; set; }
public bool? IsQueryCommand { get; set; } = true;
public string[]? Groups { get; set; } = new[] { "demo" };
public ExternalDemoSource(IHandlerHelper handlerHelper)
{
_handlerHelper = handlerHelper ?? throw new ArgumentNullException(nameof(handlerHelper));;
}
public async Task<IEnumerable<ResultItemBase>> ReadAsync(FindItemsOptions options)
{
var output = new List<ExternalDemoResult>()
{
new ExternalDemoResult(_handlerHelper) {
Name = "Hello",
Description = options?.Query ?? "World"
}
};
return await Task.FromResult(output);
}
}
}
The example above will create the following entry in sources.json
file using the command Appi config register-lib "C:\...\Appi.Infrastructure.ExternalDemoSource.dll"
:
[
{
"TypeName": "ExternalDemoSource",
"Name": "Demo Assembly",
"Alias": "external",
"Groups": [
"demo"
],
"Description": "Returns hard-coded hello world.",
"IsActive": true,
"SortOrder": 50,
"Path": null,
"Arguments": null,
"IsQueryCommand": true
}
]
Your source can now be queried using the find
command, e. g. Appi god -s external
or along with other sources inside the demo
group with Appi god -g demo
Of course, the file can be edited to your needs afterwards, e. g. to change the alias or group name.
Example for implementing ResultItemBase
This class controls the output of an item by overriding the ToString()
method and displays the possible actions with the result of GetActions()
method if an item of this source gets selected. You can easily interact with predefined services like using the ClipboardService
or ProcessService
for most frequent used actions.
By using the DetailViewColumn
attribute with generic target type for converting the result to another type (former ResultAttribute
) you can define the displayed properties in the output table:
[DetailViewColumn] // no type defaults to `string`
public string Title { get; set; } = string.Empty;
[DetailViewColumn<string>]
public string Lines { get; set; } = string.Empty;
[DetailViewColumn<int>]
public int LineCount { get; set; }
Make use of IHandlerHelper
if you'd like to EscapeMarkup()
or use pre-defined actions like Back()
or Exit()
.
using Core.Abstractions;
using Core.Attributes;
using Core.Models;
namespace ExternalSourceDemo
{
public class ExternalDemoResult : ResultItemBase
{
private readonly IHandlerHelper _handlerHelper;
[DetailViewColumn]
public override string Name => base.Name;
[DetailViewColumn]
public override string Description => _handlerHelper.EscapeMarkup(base.Description);
public ExternalDemoResult(IHandlerHelper handlerHelper)
{
_handlerHelper = handlerHelper ?? throw new ArgumentNullException(nameof(handlerHelper));
}
public override IEnumerable<ActionItem> GetActions()
{
var actions = new List<ActionItem>
{
_handlerHelper.Back(),
_handlerHelper.Exit()
};
return actions;
}
public override string ToString()
{
return $"{Name} {Description}!";
}
}
}
Localization
Appi
is localized (english and german language as of now). If you like, you can contribute additional languages.
Each layer or custom plugin has its own Localization
folder and files using the default way of implementing string translations by using IStringLocalizer<T>
class.
This class is registered in DI container, so you can gain access to it by using DI in your constructor.
Here is an example of implementing localization in your own plugin:
- Inject the service
IStringLocalizer<TWHATEVER>
into your constructor - Place the
[assembly: RootNamespace("PROJECTNAME")]
attribute at the top of yourTWHATEVER
member - Create a folder named
Localization
in your plugin's root - Create a
resx
file named as follows:FullNamespaceOfYourTWhateverFile.TWhateverFilename.Locale.resx
e.g.Infrastructure.FileDemoExample.FileDemoSource.de.resx
- Use it like this: _customLocalizer["Line"] where
Line
is the key
[assembly: RootNamespace("FileDemo")]
namespace Infrastructure.FileDemoExample
{
public class FileDemoSource : ISource
{
// ...
private readonly IStringLocalizer<FileDemoSource> _customLocalizer;
public FileDemoSource(IStringLocalizer<FileDemoSource> customLocalizer)
{
_customLocalizer = customLocalizer ?? throw new ArgumentNullException(nameof(customLocalizer));
}
public override async Task<IEnumerable<ResultItemBase>> ReadAsync(FindItemsOptions options)
{
// ...
}
public IServiceCollection AddCustomServices(IServiceCollection services)
{
return base.AddCustomServices(services);
}
private FileResult Parse(string row, int rowNumber)
{
return new FileDemoResult(_customLocalizer)
{
Id = rowNumber,
Name = $"{_customLocalizer["Line"]} {rowNumber}",
Description = row
};
}
}
}
Take a further look at the example plugin File Demo Plugin. In this demo there are two localizer
s injected, one from the apps Infrastructure layer and a custom one.
Up next
- Localized app examples
- Unit tests
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net7.0
- Appi.Core (>= 1.3.0)
- ArgumentString (>= 1.0.4)
- Dapper (>= 2.1.15)
- Microsoft.Data.SqlClient (>= 5.1.1)
- Microsoft.Data.Sqlite.Core (>= 7.0.12)
- MySqlConnector (>= 2.2.7)
- SQLitePCLRaw.core (>= 2.1.6)
- SQLitePCLRaw.lib.e_sqlite3 (>= 2.1.6)
- SQLitePCLRaw.provider.e_sqlite3 (>= 2.1.6)
- TextCopy (>= 6.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
see info on GitHub