65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Ninject 构建松散耦合的模块化 WPF 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (7投票s)

2014年9月29日

CPOL

6分钟阅读

viewsIcon

42366

downloadIcon

1071

本文讨论了如何使用 Ninject 和 Prism 框架来生成松散耦合的模块化 WPF 应用程序。

引言

模块化设计采用 乐高 的方法来构建应用程序。有很多小的模块松散地耦合在一起,每个模块对其邻居的了解很少,只知道如何与它们连接。这极大地促进了项目的维护和测试,因为每个模块都可以轻松地替换而不会扰乱其他模块。本文讨论了如何使用 NinjectPrism 框架来生成松散耦合的模块化 WPF 应用程序。

松散耦合。

实现松散耦合的一种方法是使用控制反转 (IOC) 设计模式。通过这种模式,被反转的部分是类插入或连接依赖对象的方式。类不再通过使用对象的 New 方法插入对象,而是将对象插入到使用类中,通常通过类的构造函数。依赖注入框架负责对象的组装并使其可供使用它的类使用。类的构造函数通常将接口作为参数,以便具体类型可以由框架确定,并且所有对象创建都可以在中心位置完成。

Ninject 依赖注入框架。

Ninject 的 Kernel 类创建对象。该类有一个容器,使用 Dictionary 类型列表存储对对象的引用。引用使用这种语法存储在容器中。

Kernel.Bind<Idirector>().To<director>();

然后使用以下方式组装对象

Kernel.Get<IDirector>();

返回的对象将是 Director 类。这背后的想法是,您应该根据接口而不是具体类进行编程,并让 Kernel 确定返回的具体类。通过简单地更改 Kernel 中的绑定,可以返回不同的具体类以用于测试或更新目的。在示例应用程序中,HeadViewViewModelIDirector 类型类作为其构造函数中的参数。HeadViewViewModel 像这样添加到 Kernel

//This binds the ViewModel to itself rather than an Interface.
Kernel.Bind<HeadViewViewModel>().ToSelf();

设置好所有上述绑定后,调用 Kernel.Get<HeadViewViewModel>(); 将运行类的构造函数。Ninject 将看到构造函数需要 IDirector 类型的参数,然后它将搜索其容器以查找适当的绑定并将 Director 类注入到构造函数中。

为什么要使用 Ninject?

选择哪个依赖注入框架主要取决于个人喜好,Ninject 只是众多框架之一,它们都有优点和缺点。除了受到良好支持和直观易用之外,Ninject 还有几个特别吸引人的功能。首先,无需用修饰符污染依赖方法;其次,错误消息全面,详细说明了出了什么问题、可能的原因和建议的补救措施。以下段落描述了一种使用 Ninject 与 Windows Presentation Foundation (WPF) 的简单方法。

配置主窗口

“开箱即用”的 WPF 应用程序有一个 MainWindow 作为视图,可以在其上放置许多控件。控件的启用代码位于窗口的代码隐藏或其 DataContext 中。一种更清晰的方法是拥有一个只在其中定义区域的 Shell 窗口。模块(dlls)然后可以在特定区域中安装视图,而无需了解 Shell 的任何信息,只知道区域的名称。需要一个 RegionManager 来实现这个技巧。这就是 Ninject 派上用场的地方,因为它可以将 RegionManager 提供给任何需要它的模块。以下是一个定义了两个区域(HeadRegion 和 BodyRegion)的 Shell 窗口示例。

<Window x:Class="WpfNinjectMef.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism" 
        Title="Shell"  SizeToContent="WidthAndHeight"   WindowStartupLocation="CenterScreen">
    <Grid>
        <StackPanel >
            <ItemsControl  cal:RegionManager.RegionName="HeadRegion"  />
            <ItemsControl Margin="0,20,0,0"  cal:RegionManager.RegionName="BodyRegion"   />
          
        </StackPanel>

    </Grid>
    </Window>

有了 Shell 窗口,是时候定义一些可以与它以及彼此交互的模块了。

模块

模块只不过是应用程序解决方案中在其自己的项目中定义的类库(Dll)。使用 Ninject,模块有一个派生自 NinjectModule 的 Module 类。该类有一个 Load 方法,允许模块在首次加载时进行初始化。

激活视图区域。

RegionManager 类用于将模块中的视图注入到 Shell 窗口中的区域。这通常在模块的 Load 方法中完成。当模块加载时,该方法会被调用。在这个例子中,HeadView 被注入到 Shell 窗口的 HeadRegion 中。视图只是一个 WPF UserControl

            var regionManager = this.Kernel.Get<IRegionManager>();
            var headView = this.Kernel.Get<HeadView>();
            IRegion region = regionManager.Regions["HeadRegion"];
            region.Add(headView, "HeadView");
            region.Activate(headView);

模块间通信。

通常需要在模块之间传递对象以进行进一步处理或发送事件已发生的通知。示例中使用的模式采用了 Microsoft.Practices.Prism.PubSubEvents EventAggregator。其使用过程相当简单。首先,通过子类化 PubSubEvent<T> 类来定义事件

  //The message sent here is of Type string but it can be any object
    public class HeadMessageSentEvent : PubSubEvent<string> 
    {
        
    }

发布者然后如下发布事件

aggregator.GetEvent<HeadMessageSentEvent>().Publish(this.message);

观察者使用类似以下内容订阅事件

aggregator.GetEvent<HeadMessageSentEvent>()
                .Subscribe(this.MessageReceived);
......
private void MessageReceived(string msg)
        {
            this.ReceivedMessage = msg;
        }

就这样,无需担心必须用属性修饰方法或取消订阅的麻烦。

设置 DataContext。

Ninject 提供了一种巧妙的方法来设置视图的 DataContext。可以将类类型绑定到方法,以便在调用 Kernel.Get<classType>() 时,该方法会运行。以下是实现方式。

  Kernel.Bind<HeadView>()
                .ToMethod(context => new HeadView { DataContext = this.Kernel.Get<HeadViewViewModel>() });

通过此绑定,调用 Kernel.Get<HeadView>(); 将创建一个 new HeadView 并使用 对象初始化器 将其 DataContext 设置为 HeadViewViewModel 类。

启动应用程序

应用程序运行之前,必须配置 Ninject Kernel 容器,初始化模块并创建 Shell 窗口。这是 Bootstrapper 类的工作,该类以 NinjectBootstrapper 类为基类。NinjectBootstrapper 类是 Ninject 针对 Prism 的扩展的一部分,可在此处获取。

  public class WpfNinjectBootstrapper : NinjectBootstrapper
    {
        #region Methods

        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();
//The aggregator needs to be a singleton otherwise each module will get a different copy
            this.Kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
        }

        protected override DependencyObject CreateShell()
        {
            return new Shell();
        }

        protected override void InitializeModules()
        {
            base.InitializeModules();
//Load all NinjectModule type dlls in the current directory into the Kernel
// and call  each module's Load method
//Need to add references in this project to the modules that are situated in other 
//projects for this to work.
            Kernel.Load("*.dll");
            }

        protected override void InitializeShell()
        {
            base.InitializeShell();
            Application.Current.MainWindow = (Shell)this.Shell;
            Application.Current.MainWindow.Show();
        }

        #endregion
    }

配置 App.Xaml 以运行引导程序。

默认的 App.xaml 在开头的 Application 标签中包含以下内容。

StartupUri="MainWindow.xaml"

这需要删除,并将以下方法插入到 App.xaml.cs 中。

 protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var bootstrapper = new WpfNinjectBootstrapper();
            bootstrapper.Run(true);

        }

一切准备就绪。在调试模式下运行时,最好密切关注输出窗口。有些错误可能导致功能严重丧失,但不会实际破坏应用程序。在输出窗口中追踪它们可以省去很多挠头的时间。

示例应用程序。

有两个示例演示,一个使用 WPF,另一个使用 Silverlight。每个应用程序都非常基础,它们只有两个 NinjectModules。每个模块都在 Shell 窗口的不同区域中挂载一个视图。当按下两个按钮中的任何一个时,模块之间会交换一条无意义的消息并显示在窗口中。应用程序使用 Prism.NinjectExtension。此扩展似乎依赖于 Prism 和 Ninject 的历史版本,因此已将扩展源代码导入到解决方案中,以便可以使用其依赖项的最新版本。我感谢 rhyswalden 提供了代码。

结论。

Ninject 和模块化设计模式的内容远不止于此,但希望这个简短且有些过于简单的介绍能为进一步阅读奠定基础。

© . All rights reserved.