You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Prism 8应用中如何将ViewModel作为ILogEventSink并兼顾导航与依赖注入?

解决方案

核心思路

要让ViewModel同时承担ILogEventSink职责,又不影响Prism导航/区域功能,核心要做到三点:

  1. 让ViewModel实现ILogEventSink并保留依赖注入构造函数
  2. 确保ViewModel在容器中是单例(避免Serilog使用的实例与导航实例不一致)
  3. 通过自定义Serilog扩展方法,从Prism容器解析Sink实例完成注册

步骤1:实现兼具LogSink功能的ViewModel

定义ViewModel时同时实现ILogEventSink,并通过构造函数注入所需依赖(如ITextFormatter):

using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows.Threading;

public class LogViewerViewModel : BindableBase, ILogEventSink
{
    private readonly ITextFormatter _textFormatter;
    private ObservableCollection<string> _logEntries;

    public ObservableCollection<string> LogEntries
    {
        get => _logEntries;
        set => SetProperty(ref _logEntries, value);
    }

    // 依赖注入构造函数,Prism容器会自动注入ITextFormatter
    public LogViewerViewModel(ITextFormatter textFormatter)
    {
        _textFormatter = textFormatter;
        LogEntries = new ObservableCollection<string>();
    }

    // 实现ILogEventSink的日志接收逻辑
    public void Emit(LogEvent logEvent)
    {
        if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
        
        using var writer = new StringWriter();
        _textFormatter.Format(logEvent, writer);
        // 跨线程更新UI需用Dispatcher
        Application.Current.Dispatcher.Invoke(() => 
        {
            LogEntries.Insert(0, writer.ToString());
        });
    }
}

对应的View(如LogViewerView.xaml)需开启ViewModelLocator.AutoWireViewModel:

<UserControl x:Class="YourApp.Views.LogViewerView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <!-- UI布局示例:用ListView绑定日志列表 -->
    <ListView ItemsSource="{Binding LogEntries}" />
</UserControl>

步骤2:注册组件到Prism容器

在Prism应用的RegisterTypes方法中,完成View/ViewModel、依赖项的注册,必须将ViewModel注册为单例:

using Prism.Ioc;
using Serilog.Formatting.Display;

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    // 1. 注册单例ViewModel(确保Serilog和导航用同一个实例)
    containerRegistry.RegisterSingleton<LogViewerViewModel>();
    // 2. 注册View与ViewModel的导航关联
    containerRegistry.RegisterForNavigation<LogViewerView, LogViewerViewModel>();
    // 3. 注册ITextFormatter实例(这里用Serilog自带的模板格式化器)
    containerRegistry.Register<ITextFormatter>(() => 
        new MessageTemplateTextFormatter("{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}"));
}

步骤3:自定义Serilog扩展方法注册Sink

不是必须用扩展方法,但这是Serilog推荐的配置风格。扩展方法的核心是从Prism容器解析已注册的ViewModel实例:

using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Prism.Ioc;

public static class SerilogSinkExtensions
{
    public static LoggerConfiguration PrismViewModelSink(this LoggerSinkConfiguration sinkConfiguration, IContainerProvider containerProvider)
    {
        // 从Prism容器解析ViewModel(即LogSink实例)
        var logSink = containerProvider.Resolve<LogViewerViewModel>();
        // 将解析后的实例作为Serilog的Sink
        return sinkConfiguration.Sink(logSink);
    }
}

步骤4:初始化Serilog并关联Sink

在Prism应用的OnInitialized方法中,获取容器实例并完成Serilog配置:

using Serilog;

protected override void OnInitialized()
{
    // 获取Prism容器实例
    var containerProvider = Container;
    // 配置Serilog并注册自定义Sink
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .WriteTo.PrismViewModelSink(containerProvider)
        .CreateLogger();

    // 正常执行导航逻辑
    NavigationService.NavigateAsync("LogViewerView");
}

关键注意事项

  • 单例注册:如果ViewModel不注册为单例,Serilog使用的实例和导航时创建的实例会是两个,导致日志无法显示在当前View上
  • 线程安全:Emit方法可能在非UI线程执行,必须用Dispatcher回到UI线程更新集合
  • 扩展方法可选性:你也可以直接在Serilog配置中手动从容器解析Sink,比如:
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Sink(Container.Resolve<LogViewerViewModel>())
        .CreateLogger();
    
    但扩展方法让代码更符合Serilog的配置风格,可读性更强

内容的提问来源于stack exchange,提问作者EuroEager

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 10:03:28