Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How To Handle Modular Startup Config from Plugins? #143

Open
aloksharma1 opened this issue Apr 9, 2021 · 1 comment
Open

How To Handle Modular Startup Config from Plugins? #143

aloksharma1 opened this issue Apr 9, 2021 · 1 comment

Comments

@aloksharma1
Copy link

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

@Francescolis
Copy link

Francescolis commented Mar 14, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants