在混合使用 MEF(插件发现) 和 Microsoft DI(核心服务管理) 的架构中,MEF 默认无法直接解析 DI 容器中的服务。但可以通过 “服务桥接(Service Bridge / Adapter)” 实现。
✅ 优势
插件不依赖 MEF 或 DI 容器细节;
主程序完全控制传入的服务;
易于单元测试(可 mock IAnalyzerContext);
无服务定位(Service Locator)反模式。
📁 项目结构(延续之前的模板)
1
2
3
4
5
6
7
8
9
10
11
12
SharpBoxes.Host/
├── Services/
│ ├── Core/
│ │ ├── IHostLogger.cs
│ │ └── ConsoleLogger.cs
│ └── Extensibility/
│ ├── IAnalyzer.cs
│ ├── IAnalyzerContext.cs ← 新增
│ ├── AnalyzerContext.cs ← 新增
│ └── PluginManager.cs
└── Plugins/
└── ExamplePlugin.dll
1. 定义上下文接口(IAnalyzerContext)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Services/Extensibility/IAnalyzerContext.cs
using SharpBoxes.Host.Services.Core ;
namespace SharpBoxes.Host.Services.Extensibility
{
/// <summary>
/// 分析器执行上下文,由主程序提供,包含所需服务
/// </summary>
public interface IAnalyzerContext
{
IHostLogger Logger { get ; }
string WorkingDirectory { get ; }
// 可扩展:IConfiguration, IFileService 等
}
}
2. 实现上下文(AnalyzerContext)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Services/Extensibility/AnalyzerContext.cs
using SharpBoxes.Host.Services.Core ;
namespace SharpBoxes.Host.Services.Extensibility
{
/// <summary>
/// 上下文实现(由主程序创建)
/// </summary>
public class AnalyzerContext : IAnalyzerContext
{
public IHostLoader Logger { get ; }
public string WorkingDirectory { get ; }
public AnalyzerContext ( IHostLogger logger , string workingDirectory = null )
{
Logger = logger ?? throw new ArgumentNullException ( nameof ( logger ));
WorkingDirectory = workingDirectory ?? Environment . CurrentDirectory ;
}
}
}
3. 更新分析器契约(IAnalyzer)
1
2
3
4
5
6
7
8
9
10
11
12
13
// Services/Extensibility/IAnalyzer.cs
namespace SharpBoxes.Host.Services.Extensibility
{
public interface IAnalyzer
{
string Name { get ; }
/// <summary>
/// 分析代码,通过上下文获取服务
/// </summary>
string Analyze ( string code , IAnalyzerContext context );
}
}
4. 插件实现(无需任何 DI/MEF 服务注入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ExamplePlugin/MyAnalyzer.cs
using System.ComponentModel.Composition ;
using SharpBoxes.Host.Services.Extensibility ;
namespace ExamplePlugin
{
[Export(typeof(IAnalyzer))]
[ExportMetadata("Name", "Context-Based Analyzer")]
[ExportMetadata("Version", "1.0.0")]
[ExportMetadata("Author", "Zheng")]
[ExportMetadata("Priority", 80)]
public class MyAnalyzer : IAnalyzer
{
public string Name => "Context-Based Analyzer" ;
// ✅ 不需要 [Import],不依赖 MEF 或 DI
public string Analyze ( string code , IAnalyzerContext context )
{
// 使用上下文中的服务
context . Logger . Info ( $"[MyAnalyzer] Starting analysis of {code.Length} chars" );
// 可访问工作目录等上下文信息
context . Logger . Info ( $"Working directory: {context.WorkingDirectory}" );
return $"Analyzed by {Name}: '{code}' has {code.Length} characters." ;
}
}
}
✅ 插件完全解耦 :只需引用 IAnalyzer 和 IAnalyzerContext(可放在共享契约库中)。
5. PluginManager(无需改动)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// PluginManager.cs(保持之前实现,不再需要传入 IServiceProvider)
using System.ComponentModel.Composition ;
using System.ComponentModel.Composition.Hosting ;
using System.Reflection ;
namespace SharpBoxes.Host.Services.Extensibility
{
public class PluginManager
{
private readonly CompositionContainer _container ;
public PluginManager ( string pluginsDirectory = @".\Plugins" )
{
if (! Directory . Exists ( pluginsDirectory ))
Directory . CreateDirectory ( pluginsDirectory );
var catalog = new AggregateCatalog ();
catalog . Catalogs . Add ( new AssemblyCatalog ( Assembly . GetExecutingAssembly ()));
catalog . Catalogs . Add ( new DirectoryCatalog ( pluginsDirectory , "*.dll" ));
_container = new CompositionContainer ( catalog );
}
public IEnumerable < Lazy < IAnalyzer , IAnalyzerMetadata >> GetAnalyzers ()
{
return _container . GetExports < IAnalyzer , IAnalyzerMetadata >();
}
}
}
6. 主程序(创建上下文并调用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Program.cs
using Microsoft.Extensions.DependencyInjection ;
using SharpBoxes.Host.Services.Core ;
using SharpBoxes.Host.Services.Extensibility ;
class Program
{
static void Main ( string [] args )
{
// 1. 设置 DI
var services = new ServiceCollection ();
services . AddSingleton < IHostLogger , ConsoleLogger >();
var serviceProvider = services . BuildServiceProvider ();
// 2. 设置 MEF 插件管理
var pluginManager = new PluginManager ();
var analyzers = pluginManager . GetAnalyzersByPriority (). ToList ();
// 3. 创建上下文(从 DI 获取服务)
var logger = serviceProvider . GetRequiredService < IHostLogger >();
var context = new AnalyzerContext ( logger , Environment . CurrentDirectory );
// 4. 调用插件
logger . Info ( "Running analyzers with context..." );
foreach ( var lazyAnalyzer in analyzers )
{
try
{
var analyzer = lazyAnalyzer . Value ;
var result = analyzer . Analyze ( "var x = 42;" , context );
logger . Info ( $"[RESULT] {result}" );
}
catch ( Exception ex )
{
logger . Info ( $"[ERROR] Analyzer '{lazyAnalyzer.Metadata.Name}' failed: {ex.Message}" );
}
}
Console . ReadKey ();
}
}
🖨️ 输出示例
1
2
3
4
[ INFO ] Running analyzers with context ...
[ INFO ] [ MyAnalyzer ] Starting analysis of 12 chars
[ INFO ] Working directory : C : \SharpBoxes . Host \bin \Debug
[ INFO ] [ RESULT ] Analyzed by Context - Based Analyzer : 'var x = 42;' has 12 characters .
🧪 单元测试示例(插件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// MyAnalyzerTests.cs
[TestClass]
public class MyAnalyzerTests
{
[TestMethod]
public void Analyze_ShouldReturnCodeLength ()
{
// Arrange
var mockLogger = new Mock < IHostLogger >();
var context = new AnalyzerContext ( mockLogger . Object );
var analyzer = new MyAnalyzer ();
// Act
var result = analyzer . Analyze ( "hello" , context );
// Assert
Assert . IsTrue ( result . Contains ( "5" ));
mockLogger . Verify ( x => x . Info ( It . IsAny < string >()), Times . Exactly ( 2 ));
}
}
✅ 无需启动 MEF 或 DI,纯 C# 测试。
✅ 总结
特性
实现
插件无容器依赖
✅ 仅依赖 IAnalyzerContext
服务安全传递
✅ 由主程序控制
易于测试
✅ 可 mock 上下文
兼容 .NET Framework 4.8
✅ 使用 MEF 1 + Microsoft DI
支持元数据 + 延迟加载
✅ Lazy<IAnalyzer, IAnalyzerMetadata>
为什么我要使用上下文的方式而不是把ServiceProvider作为参数传入?
这种方式 避免插件持有 IServiceProvider,更安全、更清晰、更易测试。
✅ 优势
插件不依赖 MEF 或 DI 容器细节;
主程序完全控制传入的服务;
易于单元测试(可 mock IAnalyzerContext);
无服务定位(Service Locator)反模式。
Licensed under CC BY-NC-SA 4.0