Files
2026-06-08 12:18:58 +08:00

17 KiB
Raw Permalink Blame History

IPluginContext 详解

IPluginContext 是插件与宿主应用交互的主要接口,提供对宿主服务、日志、设置等的访问。

接口定义

namespace LanMountainDesktop.PluginSdk;

/// <summary>
/// 插件上下文接口
/// </summary>
public interface IPluginContext
{
    /// <summary>
    /// 插件根目录
    /// 包含插件的所有文件DLL、资源等
    /// </summary>
    string PluginDirectory { get; }
    
    /// <summary>
    /// 插件数据目录
    /// 用于存储插件的持久化数据(缓存、数据库等)
    /// </summary>
    string DataDirectory { get; }
    
    /// <summary>
    /// 服务提供者
    /// 用于获取宿主提供的服务
    /// </summary>
    IServiceProvider Services { get; }
    
    /// <summary>
    /// 日志记录器
    /// 用于记录插件运行日志
    /// </summary>
    ILogger Logger { get; }
    
    /// <summary>
    /// 设置服务
    /// 用于读写插件配置
    /// </summary>
    ISettingsService Settings { get; }
}

属性详解

PluginDirectory

类型: string

说明: 插件的根目录,包含插件的所有文件。

典型路径:

%LOCALAPPDATA%\LanMountainDesktop\plugins\{PluginId}\

用途:

  • 加载插件资源文件
  • 读取配置文件
  • 访问插件自带的数据文件

示例:

public async Task InitializeAsync(IPluginContext context)
{
    // 加载插件自带的数据文件
    var dataFile = Path.Combine(context.PluginDirectory, "data", "cities.json");
    if (File.Exists(dataFile))
    {
        var json = await File.ReadAllTextAsync(dataFile);
        var cities = JsonSerializer.Deserialize<List<City>>(json);
    }
    
    // 加载图标
    var iconPath = Path.Combine(context.PluginDirectory, "Assets", "icon.png");
    
    // 加载资源文件(使用 avares 方案更好)
    // avares://MyPlugin/Assets/icon.png
}

注意事项:

  • 只能读取,不要在此目录写入文件
  • 使用 Path.Combine 构建路径
  • 不要硬编码路径
  • 不要依赖目录结构(可能变化)

DataDirectory

类型: string

说明: 插件的数据目录,用于存储插件生成的持久化数据。

典型路径:

%LOCALAPPDATA%\LanMountainDesktop\plugin-data\{PluginId}\

用途:

  • 存储缓存文件
  • 存储本地数据库
  • 存储临时文件
  • 存储下载的文件

示例:

public async Task InitializeAsync(IPluginContext context)
{
    // 确保数据目录存在
    Directory.CreateDirectory(context.DataDirectory);
    
    // 缓存文件路径
    var cacheFile = Path.Combine(context.DataDirectory, "weather-cache.json");
    
    // SQLite 数据库路径
    var dbPath = Path.Combine(context.DataDirectory, "todos.db");
    
    // 下载文件路径
    var downloadPath = Path.Combine(context.DataDirectory, "downloads");
}

最佳实践:

public class MyPlugin : IPlugin
{
    private string? _cacheDirectory;
    private string? _logsDirectory;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        // 创建子目录组织数据
        _cacheDirectory = Path.Combine(context.DataDirectory, "cache");
        _logsDirectory = Path.Combine(context.DataDirectory, "logs");
        
        Directory.CreateDirectory(_cacheDirectory);
        Directory.CreateDirectory(_logsDirectory);
    }
    
    public async Task SaveCacheAsync(string key, string data)
    {
        var cacheFile = Path.Combine(_cacheDirectory!, $"{key}.json");
        await File.WriteAllTextAsync(cacheFile, data);
    }
}

清理数据:

public async Task ShutdownAsync()
{
    // 清理旧的缓存文件
    if (_cacheDirectory != null)
    {
        var files = Directory.GetFiles(_cacheDirectory);
        foreach (var file in files)
        {
            var fileInfo = new FileInfo(file);
            if (DateTime.Now - fileInfo.LastWriteTime > TimeSpan.FromDays(7))
            {
                File.Delete(file);
            }
        }
    }
}

Services

类型: IServiceProvider

说明: 服务提供者,用于获取宿主提供的服务。

常用服务:

服务接口 说明
IComponentRegistry 组件注册表
ISettingsPageRegistry 设置页注册表
IEventBus 事件总线
INotificationService 通知服务
IDialogService 对话框服务
IThemeService 主题服务
ILocalizationService 本地化服务
IHttpClientFactory HTTP 客户端工厂

使用方法:

public async Task InitializeAsync(IPluginContext context)
{
    // 获取服务
    var componentRegistry = context.Services
        .GetService<IComponentRegistry>();
    
    var eventBus = context.Services
        .GetService<IEventBus>();
    
    var themeService = context.Services
        .GetService<IThemeService>();
    
    // 检查服务是否可用
    if (componentRegistry != null)
    {
        // 使用服务
        componentRegistry.RegisterComponent<MyComponent>();
    }
    else
    {
        context.Logger.LogWarning("IComponentRegistry not available");
    }
}

泛型扩展方法:

// 使用 Microsoft.Extensions.DependencyInjection 的扩展方法
using Microsoft.Extensions.DependencyInjection;

var componentRegistry = context.Services.GetService<IComponentRegistry>();
var eventBus = context.Services.GetRequiredService<IEventBus>(); // 不存在会抛异常

服务定位器模式:

public class MyPlugin : IPlugin
{
    private IServiceProvider? _services;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        _services = context.Services;
    }
    
    private void SomeMethod()
    {
        // 运行时获取服务
        var notificationService = _services?
            .GetService<INotificationService>();
        
        notificationService?.ShowNotification(
            "标题",
            "内容",
            NotificationType.Information
        );
    }
}

Logger

类型: ILogger

说明: 日志记录器,用于记录插件运行日志。

日志级别:

级别 方法 用途
Trace LogTrace 最详细的信息,用于诊断
Debug LogDebug 调试信息
Information LogInformation 一般信息
Warning LogWarning 警告信息
Error LogError 错误信息
Critical LogCritical 严重错误

基本用法:

public async Task InitializeAsync(IPluginContext context)
{
    var logger = context.Logger;
    
    // 信息日志
    logger.LogInformation("Plugin is initializing");
    
    // 警告日志
    logger.LogWarning("Configuration is missing, using defaults");
    
    // 错误日志
    try
    {
        await LoadDataAsync();
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Failed to load data");
    }
    
    // 调试日志
    logger.LogDebug("Loaded {Count} items", items.Count);
}

结构化日志:

// ✅ 好:使用参数化日志
logger.LogInformation(
    "User {UserId} requested weather for {City}",
    userId,
    city
);

// ❌ 差:字符串拼接
logger.LogInformation(
    $"User {userId} requested weather for {city}"
);

异常日志:

try
{
    await FetchWeatherAsync();
}
catch (HttpRequestException ex)
{
    // 记录异常和上下文
    logger.LogError(
        ex,
        "Failed to fetch weather for {City}. Retry count: {RetryCount}",
        city,
        retryCount
    );
}
catch (Exception ex)
{
    // 严重错误
    logger.LogCritical(
        ex,
        "Unexpected error in weather service"
    );
}

条件日志:

// 检查日志级别以避免不必要的计算
if (logger.IsEnabled(LogLevel.Debug))
{
    var expensiveDebugInfo = CalculateDebugInfo(); // 只在启用 Debug 时计算
    logger.LogDebug("Debug info: {Info}", expensiveDebugInfo);
}

日志作用域:

using (logger.BeginScope("WeatherFetch-{City}", city))
{
    logger.LogInformation("Starting fetch");
    await FetchWeatherAsync(city);
    logger.LogInformation("Fetch completed");
}
// 所有日志会包含作用域信息

Settings

类型: ISettingsService

说明: 设置服务,用于读写插件配置。详见 设置系统

快速示例:

public async Task InitializeAsync(IPluginContext context)
{
    var settings = context.Settings;
    
    // 读取设置
    var apiKey = settings.GetValue("ApiKey", "");
    var refreshRate = settings.GetValue("RefreshRate", 10);
    var cities = settings.GetValue<List<string>>(
        "FavoriteCities",
        new List<string>()
    );
    
    // 保存设置
    settings.SetValue("LastStartTime", DateTime.Now);
    
    // 监听设置变更
    settings.SettingChanged += (sender, e) =>
    {
        if (e.Key == "ApiKey")
        {
            // 响应变更
        }
    };
}

使用模式

保存上下文引用

public class MyPlugin : IPlugin
{
    private IPluginContext? _context;
    private ILogger? _logger;
    private ISettingsService? _settings;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        // 保存引用供后续使用
        _context = context;
        _logger = context.Logger;
        _settings = context.Settings;
        
        // 后续可以在任何方法中使用
    }
    
    private void SomeMethod()
    {
        _logger?.LogInformation("Doing something");
        
        var value = _settings?.GetValue("Key", "Default");
    }
}

依赖注入模式

public class MyComponent : ComponentBase
{
    private readonly INotificationService? _notificationService;
    
    public MyComponent()
    {
        // 组件构造时注入依赖
        _notificationService = Services.GetService<INotificationService>();
    }
    
    public void NotifyUser(string message)
    {
        _notificationService?.ShowNotification(
            "提醒",
            message,
            NotificationType.Information
        );
    }
}

服务包装

public class MyPlugin : IPlugin
{
    private WeatherService? _weatherService;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        // 创建服务包装类
        _weatherService = new WeatherService(
            context.Logger,
            context.Settings,
            context.Services.GetService<IHttpClientFactory>()
        );
        
        await _weatherService.InitializeAsync();
    }
}

public class WeatherService
{
    private readonly ILogger _logger;
    private readonly ISettingsService _settings;
    private readonly HttpClient _httpClient;
    
    public WeatherService(
        ILogger logger,
        ISettingsService settings,
        IHttpClientFactory? httpFactory)
    {
        _logger = logger;
        _settings = settings;
        _httpClient = httpFactory?.CreateClient() ?? new HttpClient();
    }
    
    public async Task InitializeAsync()
    {
        var apiKey = _settings.GetValue("ApiKey", "");
        _logger.LogInformation("Weather service initialized");
    }
}

最佳实践

检查服务可用性

// ✅ 好:检查服务是否存在
var notificationService = context.Services
    .GetService<INotificationService>();

if (notificationService != null)
{
    notificationService.ShowNotification(...);
}
else
{
    context.Logger.LogWarning("Notification service not available");
}

// ❌ 差:不检查直接使用
var notificationService = context.Services
    .GetRequiredService<INotificationService>(); // 可能抛异常

使用结构化日志

// ✅ 好:参数化日志
logger.LogInformation(
    "Processed {Count} items in {Duration}ms",
    count,
    duration
);

// ❌ 差:字符串插值
logger.LogInformation(
    $"Processed {count} items in {duration}ms"
);

正确处理路径

// ✅ 好:使用 Path.Combine
var dataFile = Path.Combine(
    context.DataDirectory,
    "cache",
    "data.json"
);

// ❌ 差:字符串拼接
var dataFile = context.DataDirectory + "\\cache\\data.json"; // Windows 专用

清理资源

public class MyPlugin : IPlugin
{
    private ISettingsService? _settings;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        _settings = context.Settings;
        _settings.SettingChanged += OnSettingChanged;
    }
    
    public async Task ShutdownAsync()
    {
        // 取消订阅
        if (_settings != null)
        {
            _settings.SettingChanged -= OnSettingChanged;
        }
    }
}

完整示例

using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace MyPlugin;

public class WeatherPlugin : IPlugin
{
    public string Id => "com.example.weatherplugin";
    public string Name => "天气插件";
    public string Version => "1.0.0";
    
    // 上下文引用
    private IPluginContext? _context;
    private ILogger? _logger;
    private ISettingsService? _settings;
    private string? _dataDirectory;
    
    // 服务引用
    private INotificationService? _notificationService;
    private IHttpClientFactory? _httpFactory;
    
    public async Task InitializeAsync(IPluginContext context)
    {
        // 1. 保存上下文引用
        _context = context;
        _logger = context.Logger;
        _settings = context.Settings;
        _dataDirectory = context.DataDirectory;
        
        _logger.LogInformation(
            "{PluginName} v{Version} initializing from {Directory}",
            Name,
            Version,
            context.PluginDirectory
        );
        
        // 2. 获取宿主服务
        _notificationService = context.Services
            .GetService<INotificationService>();
        
        _httpFactory = context.Services
            .GetService<IHttpClientFactory>();
        
        // 3. 创建数据目录
        Directory.CreateDirectory(_dataDirectory);
        
        var cacheDir = Path.Combine(_dataDirectory, "cache");
        Directory.CreateDirectory(cacheDir);
        
        _logger.LogDebug("Data directory: {Directory}", _dataDirectory);
        
        // 4. 加载配置
        var apiKey = _settings.GetValue("ApiKey", "");
        if (string.IsNullOrEmpty(apiKey))
        {
            _logger.LogWarning("API Key not configured");
        }
        
        // 5. 注册组件和服务
        RegisterComponents(context);
        RegisterSettingsPage(context);
        
        // 6. 订阅事件
        SubscribeEvents(context);
        
        _logger.LogInformation("{PluginName} initialized successfully", Name);
    }
    
    private void RegisterComponents(IPluginContext context)
    {
        var registry = context.Services.GetService<IComponentRegistry>();
        if (registry != null)
        {
            registry.RegisterComponent<WeatherComponent>();
            _logger?.LogDebug("Components registered");
        }
    }
    
    private void RegisterSettingsPage(IPluginContext context)
    {
        var settingsRegistry = context.Services
            .GetService<ISettingsPageRegistry>();
        
        if (settingsRegistry != null)
        {
            settingsRegistry.RegisterPage(
                title: Name,
                category: "插件",
                pageFactory: () => new WeatherSettingsPage(
                    _settings!,
                    _logger!
                )
            );
            _logger?.LogDebug("Settings page registered");
        }
    }
    
    private void SubscribeEvents(IPluginContext context)
    {
        var eventBus = context.Services.GetService<IEventBus>();
        if (eventBus != null)
        {
            eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
            _logger?.LogDebug("Event subscriptions created");
        }
    }
    
    private void OnThemeChanged(ThemeChangedEvent evt)
    {
        _logger?.LogInformation("Theme changed to: {Theme}", evt.NewTheme);
    }
    
    public async Task ShutdownAsync()
    {
        _logger?.LogInformation("{PluginName} shutting down", Name);
        
        // 取消订阅
        var eventBus = _context?.Services.GetService<IEventBus>();
        if (eventBus != null)
        {
            eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
        }
        
        _logger?.LogInformation("{PluginName} shutdown completed", Name);
        await Task.CompletedTask;
    }
}

相关文档