How To Handle Modular Startup Config from Plugins?
please read below scenario:
in a plugin based application built with Plugin Library DotnetCorePlugins where a single host application serves with micro plugins everything is working well and good. But i need to do startup service registration from these plugins as needed so say plugin1 introduces a middleware, plugin2 a pipeline, plugin3 some mediatr service etc...
i dig into OrchardCore & Orchard does that by using a StartupBase class but i am unable to find out how they are doing it [if my assumption is correct orchard uses msbuild for plugins unlike loadcontext of this library].
my requirements and structure is different from orchard, but i like the idea of having a StartupBase class where i can define configuration order and service init order and it gets called on main host app initilization can someone guide me to the right way to do this, i am ok with even minimal flow steps as long as its clear to understand. the Plugin Startup files must handle the defined order in host and be injected into main startup bus.
i found this lib and it seems to work similar to what i want in microsoft docs, all i want to know how to handle services in defined order?
thanks
You can use MEF (Managed Extensibility Framework) to implement this process. I have done it myself using an interface as follows :
/// <summary>
/// Provides with an interface that allows external libraries to register types to the services collection.
/// This interface is used with MEF : Managed Extensibility Framework.
/// The implementation class must be decorated with the attribute <see cref="System.ComponentModel.Composition.ExportAttribute"/> attribute,
/// with <see cref="IAddServiceExport"/> type as contract type.
/// </summary>
public interface IAddServiceExport
{
/// <summary>
/// When implemented, this method should add types to the services collection.
/// </summary>
/// <param name="services">The services collection to act on.</param>
/// <param name="configuration">The application configuration.</param>
void AddServices(IServiceCollection services, IConfiguration configuration);
}
The I define options for export ...
/// <summary>
/// Defines options to configure export services.
/// </summary>
public sealed class ExportServiceOptions
{
/// <summary>
/// Initializes a default instance of <see cref="ExportServiceOptions"/> class.
/// </summary>
public ExportServiceOptions() { }
/// <summary>
/// Gets or sets the path to the directory to scan for assemblies to add to the catalog.
/// if not defined, the system will look to the application current directory.
/// </summary>
public string Path { get; set; } = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!;
/// <summary>
/// Gets or sets the pattern to search with. The format of the pattern should be the same as specified for GetFiles.
/// If not defined, the system will use the <see langword="*.dll"/> pattern.
/// </summary>
public string SearchPattern { get; set; } = "*.dll";
/// <summary>
/// Gets or sets whether or not to search in sub-directories.
/// The default value is <see langword="false"/>.
/// </summary>
public bool SearchSubDirectories { get; set; }
}
used by an extension method :
/// <summary>
/// Adds and configures registration of services using the<see cref="IAddServiceExport"/> implementations found in the path.
/// This method uses MEF : Managed Extensibility Framework.
/// </summary>
/// <param name="services">The collection of services.</param>
/// <param name="configuration">The application configuration.</param>
/// <param name="configureOptions">A delegate to configure the <see cref="ExportServiceOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> instance.</returns>
/// <exception cref="ArgumentNullException">The <paramref name="services"/> is null.</exception>
/// <exception cref="ArgumentNullException">The <paramref name="configureOptions"/> is null.</exception>
/// <exception cref="InvalidOperationException">The operation failed. See inner exception.</exception>
public static IServiceCollection AddXServiceExport(
this IServiceCollection services,
IConfiguration configuration,
Action<ExportServiceOptions> configureOptions)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(configureOptions);
var definedOptions = new ExportServiceOptions();
configureOptions.Invoke(definedOptions);
services.AddServiceExport(configuration, definedOptions);
return services;
}
private static void AddServiceExport(
this IServiceCollection services,
IConfiguration configuration,
ExportServiceOptions options)
{
try
{
using var directoryCatalog = options.SearchSubDirectories
? new RecursiveDirectoryCatalog(options.Path, options.SearchPattern)
: (ComposablePartCatalog)new DirectoryCatalog(options.Path, options.SearchPattern);
var importDefinition = BuildAddImportDefinition();
using var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(directoryCatalog);
using var compositionContainer = new CompositionContainer(aggregateCatalog);
var exportServices = compositionContainer
.GetExports(importDefinition)
.Select(def => def.Value)
.OfType<IAddServiceExport>();
foreach (var export in exportServices)
export.AddServices(services, configuration);
}
catch (Exception exception) when (exception is NotSupportedException
|| exception is System.IO.DirectoryNotFoundException
|| exception is UnauthorizedAccessException
|| exception is ArgumentException
|| exception is System.IO.PathTooLongException
|| exception is ReflectionTypeLoadException)
{
throw new InvalidOperationException("Adding exports failed. See inner exception.", exception);
}
}
private static ImportDefinition BuildAddImportDefinition()
=> new(
_ => true,
typeof(IAddServiceExport).FullName,
ImportCardinality.ZeroOrMore,
false,
false);
}
You can find the complete code here : https://github.com/Francescolis/Xpandables.Net/tree/Net6.0/Xpandables.Net/Cqrs/Extensibility