简单注入器插件

4
我正在.NET 4.5中构建一个系统,它将具有不同的实现(即在不同客户端本地实现)。每个客户都有自己的基础架构和数据库结构,因此我正在严格依赖洋葱架构来构建系统,而这种架构本身则依赖于接口和DI。以这种方式,我可以使用特定于客户的“Repository”和“Service”实现。
我的目标是,在不重新编译的情况下,能够在客户服务器上安装系统(系统入口点基本上是包含业务逻辑的Windows服务,定期触发,并且还托管WCF服务)。为了使其工作,我想到的是一些"Dependencies"或"Plugins"文件夹,作为包含Windows服务可执行文件的子文件夹,其中包含客户特定的DLL,该DLL具有实现应用程序所依赖的所有必要接口的具体类。
我正在尝试使用Simple Injector来实现这一点。我已经查看了SimpleInjector.Packaging程序集,以及关于“动态注册插件”的段落here,但我仍然有些困惑,不知道从哪里开始,例如应该在哪个程序集中定义什么。
我需要一些具体的样例来实现这一点。
SimpleInjector Packaging程序集应用于此目的吗,还是我看错了?如果是这样,该如何操作?
请有人指导我。
谢谢 ps:为了100%清楚,请注意接口和具体实现明显分离到不同的程序集中。此问题是关于如何使用Simple Injector动态连接所有事物的。

这个问题仅限于Simple Injector吗? - aevitas
你有没有阅读这个维基页面?我认为它更接近于你想要实现的内容。 - Steven
@aevitas 不,我目前在开发阶段使用的是Simple Injector,但如果您有其他DI/IOC工具的建议可以更轻松地实现这一点,请随时提出。我是一个DI/IOC初学者,所以我对任何东西都持开放态度。 - tjeuten
@Steven 是的,我做了,但不确定我是否正确理解了它。这是否意味着例如我的具体“PersonRepository”,它实现了我的IPersonRepository接口,也必须实现一个自定义的IPlugin接口? - tjeuten
@tjeuten 这些详细的答案都没有解决你的问题吗? - aevitas
@aevitas 抱歉,我还没有时间来检查和测试。今晚会处理。 - tjeuten
3个回答

5
使用IoC容器(如Simple Injector)与此相结合的优点在于,可以轻松地向所有插件添加通用逻辑。我最近编写了一个批量图像转换工具,允许插入新的图像转换器。
这是接口:
public interface IImageConverter : IDisposable
{
    string Name { get; }
    string DefaultSourceFileExtension { get; }
    string DefaultTargetFileExtension { get; }
    string[] SourceFileExtensions { get; }
    string[] TargetFileExtensions { get; }
    void Convert(ImageDetail image);
}

注册操作可以像这样进行(请注意检查插件依赖项是否为非.NET程序集)

private void RegisterImageConverters(Container container)
{
    var pluginAssemblies = this.LoadAssemblies(this.settings.PluginDirectory);

    var pluginTypes =
        from dll in pluginAssemblies
        from type in dll.GetExportedTypes()
        where typeof(IImageConverter).IsAssignableFrom(type)
        where !type.IsAbstract
        where !type.IsGenericTypeDefinition
        select type;

    container.RegisterAll<IImageConverter>(pluginTypes);
}

private IEnumerable<Assembly> LoadAssemblies(string folder)
{
    IEnumerable<string> dlls =
        from file in new DirectoryInfo(folder).GetFiles()
        where file.Extension == ".dll"
        select file.FullName;

    IList<Assembly> assemblies = new List<Assembly>();

    foreach (string dll in dlls) {
        try {
            assemblies.Add(Assembly.LoadFile(dll));
        }
        catch { }
    }

    return assemblies;
}

所有插件操作所需的常规逻辑由装饰器处理。

container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterChecksumDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterDeleteAndRecycleDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterUpdateDatabaseDecorator));
container.RegisterDecorator(
    typeof(IImageConverter), 
    typeof(ImageConverterLoggingDecorator));

这个最终的类更具体,但我将其添加为一个示例,展示如何在不直接依赖于容器的情况下找到所需的插件。

public sealed class PluginManager : IPluginManager
{
    private readonly IEnumerable<IImageConverter> converters;

    public PluginManager(IEnumerable<IImageConverter> converters) {
        this.converters = converters;
    }

    public IList<IImageConverter> List() {
        return this.converters.ToList();
    }

    public IList<IImageConverter> FindBySource(string extension) {
        IEnumerable<IImageConverter> converters = this.converters
            .Where(x =>
                x.SourceFileExtensions.Contains(extension));

        return converters.ToList();
    }

    public IList<IImageConverter> FindByTarget(string extension) {
        IEnumerable<IImageConverter> converters = this.converters
            .Where(x =>
                x.TargetFileExtensions.Contains(extension));

        return converters.ToList();
    }

    public IList<IImageConverter> Find(
        string sourceFileExtension, 
        string targetFileExtension)
    {
        IEnumerable<IImageConverter> converter = this.converters
            .Where(x =>
                x.SourceFileExtensions.Contains(sourceFileExtension) &&
                x.TargetFileExtensions.Contains(targetFileExtension));

        return converter.ToList();
    }

    public IImageConverter Find(string name) {
        IEnumerable<IImageConverter> converter = this.converters
            .Where(x => x.Name == name);

        return converter.SingleOrDefault();
    }
}

您在容器中注册 IPluginManager,Simple Injector 将处理其余内容:

container.Register<IPluginManager, PluginManager>();

我希望这能帮到您。

谢谢。然而我不太理解装饰器的使用及其目的。你能详细解释一下吗? - tjeuten
2
@tjeuten 装饰器是一种面向切面编程的形式——一种为多个实现相同抽象(例如接口)添加横切关注点的技术。在这个例子中,我使用装饰器将通用代码添加到所有插件中。图像转换管理器是数据驱动的,无论它是调用插件从“tif”转换为“bmp”还是从“jpg”转换为“gif”,管理器总是需要基于每个图像更新数据库。 - qujck
我要向你推荐一篇文章,它是由Simple Injector的创始人@Steven撰写的,并且他也回答了这个问题(不知道为什么会被踩!)- 这篇文章可以在这里找到 - 它向我介绍了装饰器模式,也许对你有所帮助。 - qujck

2
您可能正在寻找的是这个:

您可能需要的是这个:

public class TypeLoader<T> : List<T>
{
    public const BindingFlags ConstructorSearch =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance |
        BindingFlags.Instance;

    private void Load(params Assembly[] assemblies)
    {
        foreach (
            Type t in
                assemblies.SelectMany(
                    asm =>
                        asm.GetTypes()
                            .Where(t => t.IsSubclassOf(typeof (T)) || t.GetInterfaces().Any(i => i == typeof (T)))))
        {
            Add((T) Activator.CreateInstance(t, true));
        }
    }
}

你需要做的就是调用Assembly.ReflectionOnlyLoad从你的插件目录中加载程序集并将它们传递给这个方法。

如果你在你的程序集中的所有插件类中声明IPlugin,那么你会像这样使用它:new TypeLoader<IPlugin>().Load(assemblies);,然后你将得到一个干净的列表,其中包含所有实现IPlugin的对象。


1

您需要在启动时探测插件目录,并使用.NET反射API从动态加载的程序集中获取存储库类型。这可以通过许多方式完成,具体取决于您的需求。Simple Injector 中没有专门的API来实现此功能,因为有很多方法可以实现它,编写一些自定义LINQ语句通常更易读、更灵活。

以下是一个示例:

// Find all repository abstractions
var repositoryAbstractions = (
    from type in typeof(ICustomerRepository).Assembly.GetExportedTypes()
    where type.IsInterface
    where type.Name.EndsWith("Repository")
    select type)
    .ToArray();

string pluginDirectory =
    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");

// Load all plugin assemblies
var pluginAssemblies =
    from file in new DirectoryInfo(pluginDirectory).GetFiles()
    where file.Extension.ToLower() == ".dll"
    select Assembly.LoadFile(file.FullName);

// Find all repository abstractions
var repositoryImplementationTypes =
    from assembly in pluginAssemblies
    from type in assembly.GetExportedTypes()
    where repositoryAbstractions.Any(r => r.IsAssignableFrom(type))
    where !type.IsAbstract
    where !type.IsGenericTypeDefinition
    select type;

// Register all found repositories.
foreach (var type in repositoryImplementationTypes)
{
    var abstraction = repositoryAbstractions.Single(r => r.IsAssignableFrom(type));
    container.Register(abstraction, type);
}

上面的代码是从Simple Injector文档中动态注册插件wiki页面的代码示例进行变化的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接