使用 PRISM、MEF 和 Reactive Extensions 的 WPF 日志框架





3.00/5 (3投票s)
使用 PRISM、MEF 和 Reactive Extensions 展示 WPF 日志框架的起点。
引言
我编写和注释了许多 WPF 模式,包括多线程,但仍然经常看到其他人提供关于同一主题的信息,而没有 WPF 功能的全部范围。特别是在多线程的情况下,没有谈论如何使用 Reactive Extensions 轻松地在 Dispatcher 上调用多线程工作。所以在这个演示中,我们将通过 Reactive Extensions 向 WPF 中的 UI 添加日志消息。
您可能在想,为什么要显示 WPF 中的日志?这难道不只是推送到日志文件或数据库,除非有问题才需要担心吗?为什么不让用户打开日志阅读器呢?
除了作为一个非常简单的业务示例和演示的横切关注点之外,一些应用程序通过能够向用户显示某种消息而具有很大的价值。在我处理金融行业和工程公司的经验中,用户一直要求能够看到更多关于系统中正在发生的事情的信息。除了数据往往过于冗长,无法持续显示在应用程序中。
在应用程序内查看和管理您看到的内容比处理外部源要容易得多。您可以创建一个完全独立的模型,称为“UserMessage”,以便向用户传达更细粒度的信息;但是,日志仍然需要处理横切关注点,例如如何向用户显示错误,并且它们之间会有很多重复。
演示中还有大量其他的 WPF 框架基础架构信息/示例。例如,使用契约程序集来正确创建和实现依赖注入接口。所以看看代码,开始玩更多功能吧!
背景
本项目涵盖了初级和中级示例,但读者至少应该熟悉
- PRISM
- IOC (控制反转)
- MEF
- Reactive Extensions
- 多线程
使用代码
让我们首先看一下解决方案资源管理器,以了解正确依赖注入的结构
这一切的关键在于一个契约程序集。这就是所有全局级别模型和接口将去的地方。它遵循 Martin Fowler 关于企业架构的建议,模型应该是相对简单的对象。这本身就是一个关于架构的完整对话,所以现在请相信我……
因此,ILoggingService
接口位于契约中,服务上的实现位于 NSanity.Logging.Services
程序集中。这种真正的依赖注入在于,任何使用 ILoggingService
的东西都没有对 NSanity.Logging.Services
程序集的程序集依赖。
注意:在您自己运行代码之前,必须构建整个解决方案。由于对 NSanity.Logging.Services 项目没有直接依赖,因此仅单击“开始”不会导致项目构建。
在此演示中,ILoggingService
通过 IOC 提供——特别是 MEF。您可以使用任何创建模式来获取实现,但这是它在 MEF 中的样子
[Export(typeof(ILoggingService))]
[Export(typeof(ILoggerFacade))]
public class LoggingService : ILoggingService, ILoggerFacade
{ ... }
[Export]
public class LogFeedViewModel : BaseViewModel
{
[Import]
private ILoggingService _logger;
private ObservableCollection<Log> _logs;
public ObservableCollection<Log> Logs
{
get
{
if (_logs == null)
{
_logs = new ObservableCollection<Log>();
_logger
.LogFeed
.ObserveOnDispatcher()
.Subscribe(async (l) =>
{
await OnLogRecieved(l);
});
}
return _logs;
}
}
...
}
ILoggingService
由类实现并导出。这将类注册到 MEF 容器。
接下来需要导入该接口以供使用。为了导入执行导入的类,必须从容器中初始化它。这意味着在示例中,如果您有一个直接初始化的模型或视图模型,导入项仍将为 null。
注意:如果这还不清楚,您可以跳过它,或者查阅 MSDN 关于 MEF 的文章。
现在我们有了日志服务,与之交互的视图模型,剩下的就是实际编写日志文件了。在这里,我们将查看 Observable
类以及它们与 Dispatcher
的交互。
[Export]
public class ShellViewModel
{
[Import]
private ILoggingService _loggingService;
[Import]
private LogFeedViewModel _logFeed = null;
public LogFeedViewModel LogFeed
{
get { return _logFeed; }
}
public ShellViewModel()
{
Observable
.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
// NOTE: The '_' is the often used symbol for "don't care".
// In this case we don't care what the ticks/time is.
_loggingService.Log(new Log("Debug log message created in ShellViewModel"));
});
Observable
.Interval(TimeSpan.FromSeconds(5))
.Subscribe(_ =>
{
_loggingService.Log(new Log("Erro log message created in ShellViewModel", LogSeverity.Error, new Exception("Bad bad things happened.")));
});
}
为了测试目的,它将简单地使用 Observable.Interval
每秒生成一条消息,每五秒生成一条消息,以显示两个不同的严重级别。
在视图中……
<Window x:Class="NSanity.Wpf.LoggingDemo.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ShellView" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="Clear"
Command="{Binding LogFeed.ClearLogsCommand}"/>
<DataGrid Grid.Row="1"
ItemsSource="{Binding LogFeed.Logs}"/>
</Grid>
</Window>
目前非常简单……只需更新网格,它就使用自动生成的列。关键在于 LogFeedViewModel
,通过使用 ObserveOnDispatcher
,底层机制正在使用 Task
类为我们连接到 Dispatcher
,不仅减少了代码并使其简单,而且还帮助使用了最佳实践。这意味着我们将向 Dispatcher
上的 Logs
集合添加内容。
有一个使用 PRISM DelegateCommand
和 async
/await
的命令的简单示例,以显示连接命令。它将调用 Task.Delay
以显示在命令执行被阻塞时 UI 仍然响应。
完成了!
关注点
这个演示中有很多值得探索的地方。那些想要一些高级架构概念基本示例的人会在这里找到它们。请深入研究,并且无论如何都要扩展它以供您个人使用。
我称之为框架,但它实际上是一个框架的骨架……代码重用到您公司任何应用程序作为框架的能力绝对存在。您可能需要添加日志严重级别过滤器、您自己的永久日志存储(例如 log4net)等。
显然,您可以将所有内容都变得很漂亮。我更喜欢 DevExpress 的 WPF 控件,其中有一些很棒的网格控件功能以及用于通知的控件。