互联网时代桌面开发真是愈来愈少了,不少应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也不多。我以前主要作WPF,今年开始学习Web应用开发,因而就接触到了.NET Core,其中的不少概念很值得在桌面开发中借鉴。例如在.NET Core MVC中,Controller的依赖是经过构造函数注入的,注入的过程由框架实现,咱们在写Controller时只要在构造函数参数中罗列出要依赖的服务便可,进一步的,把服务抽象为接口,那么核心的业务逻辑就完全解耦出来了,依赖的服务能够是任意的实现方式(固然前提是要知足需求)。WPF通常都是用MVVM模式开发,那么是否是可让ViewModel对其它服务的依赖也经过构造函数自动注入,而不是每次都要new出一个ViewModel呢?这篇文章主要就讨论这个问题,并尝试写了个View和ViewModel的容器来实现。git
.NET Core MVC中之因此能作到Controller的依赖自动注入,主要就是由于Controller实例是由MVC框架建立的。咱们要想让ViewModel中的依赖自动注入,那么这个ViewModel确定须要自动建立。考虑到View与ViewModel之间的对应也算是一种依赖关系,那么就能够把View和ViewModel之间的这种对应关系以及其它服务的依赖关系都放到容器里,当须要View的时候,根据View的类型从容器中找到对应的ViewModel,而后根据ViewModel的依赖,从容器中获取服务,而后把View的DataContext设置为ViewModel的实例,最终返回View,那么就实现了ViewModel的自动依赖注入了。github
按照上面那个方案我写了一个简易的依赖注入容器,证实是能够用的。不过要想真正在相对严肃一点的环境中开发,对依赖注入容器的要求就不是那么简单了。我须要花时间去开发一个严谨一点的依赖注入容器,这不只须要时间,关键水平有限,目前市面上已经存在了不少优秀的依赖注入容器,我不必造轮子(为了学习或更深刻理解原理而去造轮子的行为不在此列),但常见的依赖注入容器在配置服务时(例如绑定A和B)通常都限制B对A有继承关系,因此现有的依赖注入容器没法配置View和ViewModel的依赖。所以考虑把View和ViewModel的依赖关系单独存到一个容器中,服务的依赖放到第三方容器,为了可以适配第三方容器,能够提供一个接口,经过接口对第三方容器进行简易的包装便可使用,这样就能够任意选择本身喜欢的强大的第三方依赖注入容器了。浏览器
在开始看代码以前,先说一下存储View和ViewModel关系的容器AvalonContainer(后面简称View容器),使用这个容器的Wire方法能够配置View和ViewModel之间的对应关系,GetView方法能够获取View,同时给View的DataContext配置好了指定的ViewModel,而且ViewModel注入了依赖。要建立一个AvalonContainer须要在构造函数中传入IContainer对象,这个接口用于对第三方依赖注入容器实现包装,以便用于AvalonContainer,第三方依赖注入容器主要做用是从中获取ViewModel的依赖,以及往容器中添加ViewModel(若是须要的话)。框架
我本身写的依赖注入容器太简易了,当时只是用来测试,实际应用中应该都会使用第三方容器,因此示例直接用的第三方容器Ninject。函数
核心的步骤是建立一个Ninject容器,用Ninject容器绑定依赖,而后用Ninject容器建立View容器,配置View和ViewModel依赖。这样须要时就能够直接从View容器建立View,得到的View的DataContext已经设置为ViewModel实例并注入了ViewModel的依赖。学习
ViewModel中通常在构造函数参数中注入依赖。对于不一样的依赖注入容器,也能够经过给属性配置相应的Attribute的方式声明依赖注入,不过这种方式对ViewModel的侵入太强了,并且不一样的依赖注入容器每每提供不一样的Attribute,更换时会比较麻烦,仍是构造函数注入比较好,更换依赖注入容器不会产生影响。下面截图是TestOneView对应的ViewModel,在构造函数中注入了仓储和日志的依赖,感受就像.NET Core MVC中的Controller。测试
当须要OneTestView窗口时,能够以下图所示建立并显示。spa
为了可以适配任意的第三方依赖注入容器,提供了IContainer接口,在使用第三方依赖注入容器时须要经过这个接口适配一下,这种感受就像电脑输出接口能够有HDIM、DVI、VGA,显示器输入接口只有VGA,须要转接头来转换一下。设计
其中Get方法用于从第三方容器中获取ViewModel并注入依赖,Wire<T>()方法用于往第三方容器中添加ViewModel。其中token是针对自带依赖注入容器的,彻底能够忽略无论。日志
其实对于Ninject来讲是彻底不须要Wire这个方法的,由于即便这个类型没有添加到容器中,在Get时Ninject也会建立对象并注入其中的依赖,因此对Ninject的包装以下,Wire方法直接忽略便可。但不能保证全部的第三方依赖注入容器都有这个特性,因此仍是保留了这个接口。
这样依赖注入容器和View容器经过IContainer解耦,更换依赖注入容器不会影响到业务逻辑。
若是由于某些特殊缘由须要给同一个View绑定不一样的ViewModel,能够在Wire时提供token参数,在GetView时使用一样的token参数便可获取相应的ViewModel。
View容器写好后本身用了下感受还能够,但由于ViewModel是动态添加的,因此没法在设计时看到数据,这确实是个问题。另外要说下起名字真的很难,以前大多数都是出于学习/练习的目的,就直接加个Ayx前缀,不过此次想发布一下,考虑到WPF开发代号是Avalon,就把它叫了AvalonDI。最后关于配置View和ViewModel依赖的方法,在NInject中是用的Bind,这个感受比较好理解。不过我以为把接口和接口的实现绑定到一块儿,用装配/组装更贴切。想像一下,电视提供了标准输入接口,咱们能够接录像机、游戏机、电脑。一样游戏机提供了接口,能够插不一样的卡带、不一样的手柄,当把他们连在一块儿时,用Wire感受更合适一点。
Github:https://github.com/durow/AvalonDI
nuget:Install-Package Ayx.AvalonDI
samples里面提供了一个WpfSample,用的自带的依赖注入容器,一个NinjectSample,用的Ninject做为依赖注入容器。