早上好。
短版;实现我共同定义的IMenuItem接口的所有程序集中的所有具体实现都注入到所有需要IEnumerable<IMenuItem>
的构造函数中。
我正在构建一个允许插件的Windows TrayIcon小应用程序核心。核心会在bin目录中发现所有插件,并创建我的Autofac容器。
我有一个Core.Interfaces
项目,它声明了一个IMenuItem
接口
每个插件都在自己的程序集中定义,并且在该插件内可以有许多功能;每个功能都将声明其菜单项。
在运行时,每个插件都会发现所有功能并查询其菜单项。我遇到的问题是,插件A从插件B接收菜单项,因为所有菜单项都实现了IMenuItem
接口。
我想要实现的是拥有一个通用的IMenuItem
接口,但是当插件A中的构造函数要求IEnumerable<IMenuItem>
时,只能将我们在自己的程序集中发现的具体内容传递给它。
只需说,如果我在每个程序集中都声明了IMenuItem接口,那么这一切都很好,大概是因为然后将注册命名为该接口的名称空间。
我正在努力寻找适用于Google的正确术语,但我认为这是一个注册问题;也许我只能在解决时解决?
答案 0 :(得分:1)
开箱即用,没有什么可以做的。您必须编写自定义代码。
许多自定义代码将取决于您如何解析插件集。这里有一些解决方案,尽管如果它们都不适合您当前的设计,也许您可以使用它们激发更多想法的想象力。
免责声明:我没有通过编译器运行所有这些。它们只是部分示例,我可能会打错字。有些可能是伪代码。 YMMV。
选项1:在单独的生命周期范围内注册每个插件
the multitenant support works就是这样。基本上,根容器中仅包含共享组件,并且每个插件(以及相应的菜单项)都将在子作用域中注册。
var builder = new ContainerBuilder();
// register common stuff that all plugins use
builder.Register<SomethingCommon>().As<ICommonService>();
var container = builder.Build();
// iterate over the assemblies and create scopes per plugin
var pluginScopes = new List<ILifetimeScope>();
foreach(var assembly in GetThePluginAssemblies())
{
var scope = container.BeginLifetimeScope(b =>
{
b.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
.AsImplementedInterfaces();
b.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
.AsImplementedInterfaces();
});
pluginScopes.Add(scope);
}
这时,您有一个单独的作用域列表,可用于解析每个插件,例如是否需要所有插件:
var plugins = pluginScopes.SelectMany(s => s.Resolve<IEnumerable<IPlugin>>());
(我认为是SelectMany的工作方式,我总是感到困惑。关键是您会得到所有作用域中所有插件的扁平列表。)
为简化生活,从技术上讲,您可以使用Autofac.Multitenant
软件包,并“假装”每个插件都是单独的租户。它已经建立了每个租户的所有作用域跟踪和配置。
var builder = new ContainerBuilder();
// register common stuff that all plugins use
builder.Register<SomethingCommon>().As<ICommonService>();
var container = builder.Build();
// You probably won't want to resolve things "as a tenant" from the container,
// so you'd only use the Multitenant.ApplicationContainer (for global/common stuff)
// or individual tenant scopes directly. The tenant ID strategy, ostensibly, won't
// be used, so just make a dummy one that always returns false or something.
var multiPluginContainer = new MultitenantContainer(container, SomeTenantIdentificationStrategy);
// iterate over the assemblies and create tenants per plugin
// where the tenant ID is something like the assembly name
foreach(var assembly in GetThePluginAssemblies())
{
multiPluginContainer.ConfigureTenant(assembly.FullName, b =>
{
b.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
.AsImplementedInterfaces();
b.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
.AsImplementedInterfaces();
});
}
然后,您可以获得插件列表(“租户”)并解决。
var plugins = multiPluginContainer
.GetTenants()
.SelectMany(k =>
multiPluginContainer.GetTenantScope(k).Resolve<IEnumerable<IPlugin>>());
选项2:使用元数据标记项目
Autofac支持parameters during registration,而ResolvedParameter
非常强大。与此相关的一些巧妙的工作可能会大有帮助。
首先,您可以注册所有菜单项并用元数据标记它们。
var builder = new ContainerBuilder();
// register a bunch of stuff and...
foreach(var assembly in GetThePluginAssemblies())
{
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IPlugin))
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetInterfaces().Any(i => i == typeof(IMenuItem))
.WithMetadata("assembly", assembly.FullName)
.AsImplementedInterfaces();
}
好的,现在您已经用程序集名称标记了所有IMenuItem
条目。创建一个模块,该模块自动将解析的参数附加到每个IPlugin
上,以便您的参数可以满足任何IEnumerable<IMenuItem>
的要求。这主要基于log4net module example from the docs。
public class MenuItemModule : Autofac.Module
{
private static void OnComponentPreparing(object sender, PreparingEventArgs e)
{
e.Parameters = e.Parameters.Union(
new[]
{
new ResolvedParameter(
// Only provide values for IEnumerable<IMenuItem> requested
// by IPlugin implementations
(pi, ctx) =>
pi.ParameterType == typeof(IEnumerable<IMenuItem>) &&
pi.Member.DeclaringType.GetInterfaces().Any(i => i == typeof(IPlugin)),
// Resolve the appropriately tagged menu items
// IEnumerable<T> - get all the menu items
// Meta<T> - you want to look at the metadata
// Lazy<T> - don't actually construct them until you want them
// meta.Value = Lazy<T>
// meta.Value.Value resolves the IMenuItem
(pi, ctx) => {
var asmName = pi.Member.DeclaringType.Assembly.FullName;
return ctx.Resolve<IEnumerable<Meta<Lazy<IMenuItem>>>>()
.Where(meta => meta.Metadata["assembly"] == asmName)
.Select(meta => meta.Value.Value);
}
),
});
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += OnComponentPreparing;
}
}
然后您将注册该模块以确保所有分辨率都可以获取该参数。
builder.RegisterModule<MenuItemModule>();
还有其他选项。
您可以想象它上的其他排列。每个插件使用单独的容器(这不是一个坏主意-很好地隔离插件)。每个插件分别使用 AppDomain (即使隔离度更高,也可以封送数据)。基本IPlugin
实现中具有过滤逻辑,而不是ResolvedParameter
中的过滤逻辑。插件实现中的元数据过滤器属性可以进行过滤。
希望这可以帮助您解除封锁。