StructureMap类库主要应用于IOC方面,为了在WPF程序中充分利用依赖注入的优势,可以使用依赖注入容器StructureMap来简化开发工作。其基本的使用步骤为:
(1)下载
(2)创建BootStrapper
(3)通过继承Registry类来创建配置类 XXXRegistry
(4)在程序中应用
以下分别介绍:
(一)下载
直接利用Nuget进行下载。
(二)IOC的入口(我们用BootStrapper类来建立入口点)尽量靠近程序的启动位置,对于简单的MVVM WPF程序,通常的入口点在View层。因此我们在View层中创建类BootStrapper如下:
using IContainer = StructureMap.IContainer;
using Container = StructureMap.Container;
namespace View
{
public class BootStrapper
{
public MainWindowViewModel MainWindowViewModel
{
get
{
return _container.GetInstance<MainWindowViewModel>(); //获取对象用Container.GetInstance<T>()泛型方法。对于需要输入自定义构造参数的情况,用
//Container.With("参数名称").EqualTo(参数实例).GetInstance<T>()来获取
}
}
StructureMap.IContainer _container;
public BootStrapper()
{
_container = Container.For<RepositoryRegistry>(); //直接用Container类的静态方法For<T>来创建容器实例,这个T需要是继承于Registry类的派生类
}
}
}
以上BootStrapper中有一个构造方法和一个普通方法。通过构造方法,可以建立起StructureMap容器。另一个方法是利用容器获取MainWindow的ViewModel对象的普通方法,可以根据自己的程序自由设置这种类似的getter方法。
(三)通过继承Registry类来创建注册配置类 XXXRegistry
注册配置类主要是用于配置容器的,可以让其对程序集进行扫描,可以直接指定接口及其实现类,可以指定某个类为单例类等等。由于注册配置类需要依赖于ViewModel和Model以及其他一些服务相关的程序集,因此可将其放在ViewModel项目中(如果放在View项目中,则需要View引用Model项目,会造成View对Model的依赖)。以下是RepositoryRegistry的代码,主要利用Scan方法来扫描指定命名空间中的类,并为接口指定实现类,并采用Singleton()方法指定单实例对象。这里关键用到的是For<InterfaceT>().Singleton().User<T>()系列方法。
using StructureMap;
namespace ViewModel
{
public class RepositoryRegistry : Registry
{
public RepositoryRegistry()
{
Scan(scan =>
{
scan.IncludeNamespace("ViewModel"); //对命名空间ViewModel中进行扫描注册
scan.IncludeNamespace("Application"); //对命名空间Application中进行扫描注册
scan.IncludeNamespace("Model");
scan.WithDefaultConventions();
});
For<IToolManager>().Singleton().Use<ToolManager>(); //为接口IToolManager指定实现类ToolManager,并指定其为单实例对象
For<IUIDataProvider>().Singleton().Use<UIDataProvider>();
For<IOrdersViewModelFactory>().Singleton().Use<OrdersViewModelFactory>();
For<ICustomerDetailsViewModelFactory>().Singleton().Use<CustomerDetailsViewModelFactory>();
For<IOrderViewModelFactory>().Singleton().Use<OrderViewModelFactory>();
For<IOrderDetailsViewModelFactory>().Singleton().Use<OrderDetailsViewModelFactory>();
}
}
}
(四)应用
以上就是一个简单的StructureMap的配置,下面就可以进行实际应用。分为两步,第一,在Application.xaml中将BootStrapper定义为资源。第二,在MainWindow.xaml中利用BootStrapper来获取MainWindow对象实例并将其设置为DataContext。
<Application x:Class="NorthWind.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Application.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="BootStrapper" ObjectType="local:BootStrapper" /> //在Application.xaml中将BootStrapper定义为资源
</ResourceDictionary>
</Application.Resources>
</Application>
//以下是MainWindow.Xaml文件
<Window x:Class=" MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:ViewModel;assembly=ViewModel"
DataContext="{Binding Path=MainWindowViewModel,Source={StaticResource BootStrapper}}">
<Grid>
</Grid>
</Window>
到目前为止,StructureMap已经可以成功地提供MainWindowViewModel实例对象。当然,仅仅为了获取MainWindowViewModel对象来使用StructureMap肯定是得不偿失的,但这仅仅是简单例子,来介绍以下StructureMap的最基本使用方式。实际上,本例子中写的ICustomerDetailsViewModelFactory、IOrderViewModelFactory、IOrderDetailsViewModelFactory都是工厂类,在工厂类的构造函数中可以写入IContainer参数,这样每一个工厂类实例对象都包含了容器对象的引用,在创建对象时就可以直接利用容器来获取对象了(IContainer.GetInstance<T>())。工厂类构造函数如下:
public CustomerDetailsViewModelFactory(IContainer container)
{
_container = container;
}
利用工厂类实例化对象(实际上最终还是委托容器来创建对象的):
public CustomerDetailsViewModel CreateInstance(string customerID)
{
return _container.With("customerID").EqualTo(customerID).GetInstance<CustomerDetailsViewModel>();
//请求容器创建对象,并将创建对象所需要的customerID
//参数传给对象。这里最神奇的就是,在创建CustomerDetailsViewModel时依赖的其它服务或工具类(如xxxService,XXXManager),
//如果已经在容器中注册了,则此处不用为期注入参数,而是由容器(_container)自动注入
}
距离实例类的构造函数原型如下:
public CustomerDetailsViewModel(IUIDataProvider dataProvider,
string customerID, IOrdersViewModelFactory ordersViewModelFactory,
IToolManager toolManager) : base(toolManager)
{
.........
}
需要指出的是,该构造函数依赖多个参数,包括dataProvider,ordersViewModelFactory,toolManager和customerID,其中前三个都是服务的类对象,只有最后一个CustomerID是数据对象。在上面工厂类中,创建实例对象时使用_container.With("customerID").EqualTo(customerID).GetInstance<CustomerDetailsViewModel>()语法,它将创建对象所需要的数据对象customerID直接指出来,但没有告知其它几个参数对象。这里就是神奇的DI容器发挥作用的时候,由于其它几个类在RepositoryRegistry类中已经注册了(如For<IToolManager>().Singleton().Use<ToolManager>()的几个语句 ),因此容器就直接从容器内部取出这些对象来作为构造参数,完成对象的实例化工作。
当然还有几个问题需要考虑:
(1)如果把BootStrapper中的IContainer作为全局静态字段,是否可以不用再各个工厂类的构造函数中注入IContainer,直接使用全局静态对象来实例化具体类呢?
(2)由Container创建的Singleton好像都是由容器管理的,而非Singleton似乎是直接交给应用程序,而容器不会继续管理这些非Singleton对象的?如果对于需要作为Singleton对待的工具类,注册时没有指明其为单实例,则容器每次使用到的时候都会重新创建一个,会造成程序的异常。