The default implementation of loading the application assemblies in a Kephas application is to include all assemblies found in the application folder. What if I need to provide a different folder, or if I want to support plugins? How can I specify a different folder, or a list of folders to include in search?
Almost everything in Kephas is configurable, and the IAppRuntime
service is no exception. However, unlike the common application services, this is not registered through the [AppServiceContract]
attribute, instead it is set and configured during the bootstrap procedure. The main reason behind this behavior is that the IAppRuntime
service is required before the composition/IoC container is initialized.
You can find a possible implementation below:
/// <summary>
/// An application runtime supporting plugins.
/// Each plugin is found in its own folder in the Plugins sub directory of the application root folder.
/// </summary>
public class PluginsAppRuntime : DefaultAppRuntime
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginsAppRuntime"/> class.
/// </summary>
/// <param name="assemblyLoader">Optional. The assembly loader.</param>
/// <param name="logManager">Optional. The log manager.</param>
/// <param name="assemblyFilter">Optional. A filter for loaded assemblies.</param>
/// <param name="appLocation">Optional. The application location. If not specified, the current
/// application location is considered.</param>
public PluginsAppRuntime(
IAssemblyLoader assemblyLoader = null,
ILogManager logManager = null,
Func<AssemblyName, bool> assemblyFilter = null,
string appLocation = null)
: base(assemblyLoader, logManager, assemblyFilter, appLocation)
{
pluginsFolder = Path.Combine(this.GetAppLocation(), "Plugins");
this.PluginsFolder = Path.GetFullPath(pluginsFolder);
}
/// <summary>
/// Gets the pathname of the plugins folder.
/// </summary>
public string PluginsFolder { get; }
/// <summary>
/// Enumerates the application directories containing assemblies to be loaded.
/// </summary>
protected override IEnumerable<string> GetAppAssemblyDirectories()
{
var rootDirectory = this.GetAppLocation();
var appDirectories = new List<string> { rootDirectory };
appDirectories.AddRange(this.EnumeratePluginFolders());
var logger = this.GetLogger();
logger.Info($"Loading application from directories: {string.Join(", ", appDirectories)}");
return appDirectories;
}
/// <summary>
/// Enumerates the root bin folders for the plugins.
/// </summary>
public IEnumerable<string> EnumeratePluginFolders()
{
var binPluginsFolder = this.PluginsFolder;
if (Directory.Exists(binPluginsFolder))
{
var pluginsDirectories = Directory.EnumerateDirectories(binPluginsFolder);
return pluginsDirectories;
}
return new string[0];
}
}
/// <summary>
/// For the sake of simplicity, add an extension method to the ambient services builder to make use of this new service.
/// </summary>
public static class PluginsAmbientServicesBuilderExtensions
{
/// <summary>
/// Sets the plugins-enabled application runtime to the ambient services.
/// </summary>
/// <param name="ambientServicesBuilder">The ambient services builder.</param>
/// <param name="assemblyFilter">Optional. A filter specifying the assembly (optional).</param>
/// <param name="appLocation">Optional. The application location (optional). If not specified, the
/// assembly location is used.</param>
/// <returns>
/// The provided ambient services builder.
/// </returns>
public static AmbientServicesBuilder WithPluginsAppRuntime(
this AmbientServicesBuilder ambientServicesBuilder,
Func<AssemblyName, bool> assemblyFilter = null,
string appLocation = null)
{
Requires.NotNull(ambientServicesBuilder, nameof(ambientServicesBuilder));
var ambientServices = ambientServicesBuilder.AmbientServices;
var assemblyLoader = new DefaultAssemblyLoader();
ambientServices.RegisterService<IAssemblyLoader>(assemblyLoader);
return ambientServicesBuilder.WithAppRuntime(
new PluginsAppRuntime(
assemblyLoader,
ambientServices.LogManager,
assemblyFilter: assemblyFilter,
appLocation: appLocation));
}
}
/// <summary>
/// Last, but not least, define the application root object.
/// </summary>
public class App : AppBase
{
/// <summary>Configures the ambient services asynchronously.</summary>
/// <param name="appArgs">The application arguments.</param>
/// <param name="ambientServicesBuilder">The ambient services builder.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The asynchronous result.</returns>
protected override async Task ConfigureAmbientServicesAsync(
string[] appArgs,
AmbientServicesBuilder ambientServicesBuilder,
CancellationToken cancellationToken)
{
ambientServicesBuilder
.WithAppConfiguration(new DefaultAppConfiguration())
.WithPluginsAppRuntime()
.WithMefCompositionContainer();
}
}
/// <summary>
/// Now everything is ready to go!
/// </summary>
public static class Program
{
private static async Task<int> Main(string[] args)
{
try
{
var result = await new App().BootstrapAsync(args).PreserveThreadContext();
return 0;
}
catch (Exception ex)
{
return 100;
}
}
}