在WPF中使用依赖注入的方式创建视图
在WPF中使用依赖注入的方式创建视图
0x00 问题的产生
互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少。我之前主要做WPF,今年开始学习Web应用开发,于是就接触到了.NET Core,其中的很多概念很值得在桌面开发中借鉴。例如在.NET Core MVC中,Controller的依赖是通过构造函数注入的,注入的过程由框架实现,我们在写Controller时只要在构造函数参数中罗列出要依赖的服务即可,进一步的,把服务抽象为接口,那么核心的业务逻辑就彻底解耦出来了,依赖的服务可以是任意的实现方式(当然前提是要满足需求)。WPF一般都是用MVVM模式开发,那么是不是可以让ViewModel对其它服务的依赖也通过构造函数自动注入,而不是每次都要new出一个ViewModel呢?这篇文章主要就讨论这个问题,并尝试写了个View和ViewModel的容器来实现。
0x01 最初的设计
.NET Core MVC中之所以能做到Controller的依赖自动注入,主要就是因为Controller实例是由MVC框架创建的。我们要想让ViewModel中的依赖自动注入,那么这个ViewModel肯定需要自动创建。考虑到View与ViewModel之间的对应也算是一种依赖关系,那么就可以把View和ViewModel之间的这种对应关系以及其它服务的依赖关系都放到容器里,当需要View的时候,根据View的类型从容器中找到对应的ViewModel,然后根据ViewModel的依赖,从容器中获取服务,然后把View的DataContext设置为ViewModel的实例,最终返回View,那么就实现了ViewModel的自动依赖注入了。

0x02 更进一步的设计
按照上面那个方案我写了一个简易的依赖注入容器,证明是可以用的。不过要想真正在相对严肃一点的环境中开发,对依赖注入容器的要求就不是那么简单了。我需要花时间去开发一个严谨一点的依赖注入容器,这不仅需要时间,关键水平有限,目前市面上已经存在了很多优秀的依赖注入容器,我没必要造轮子(为了学习或更深入理解原理而去造轮子的行为不在此列),但常见的依赖注入容器在配置服务时(例如绑定A和B)一般都限制B对A有继承关系,所以现有的依赖注入容器无法配置View和ViewModel的依赖。因此考虑把View和ViewModel的依赖关系单独存到一个容器中,服务的依赖放到第三方容器,为了能够适配第三方容器,可以提供一个接口,通过接口对第三方容器进行简易的包装即可使用,这样就可以任意选择自己喜欢的强大的第三方依赖注入容器了。

0x03 部分代码和示例
在开始看代码之前,先说一下存储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窗口时,可以如下图所示创建并显示。

为了能够适配任意的第三方依赖注入容器,提供了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。
0x04 写在最后
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作为依赖注入容器。
在WPF中使用依赖注入的方式创建视图的更多相关文章
- dotnetcore3.1 WPF 中使用依赖注入
dotnetcore3.1 WPF 中使用依赖注入 Intro 在 ASP.NET Core 中默认就已经集成了依赖注入,最近把 DbTool 迁移到了 WPF dotnetcore 3.1, 在 W ...
- Spring学习(三)——Spring中的依赖注入的方式
[前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...
- JavaEE开发之Spring中的依赖注入与AOP
上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...
- .NET CORE——Console中使用依赖注入
我们都知道,在 ASP.NET CORE 中通过依赖注入的方式来使用服务十分的简单,而在 Console 中,其实也只是稍微绕了个小弯子而已.不管是内置 DI 组件或者第三方的 DI 组件(如Auto ...
- JavaEE开发之Spring中的依赖注入与AOP编程
上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...
- Asp.net core中的依赖注入
使用服务 在Asp.net core的Controller中,可以通过如下两种方式获取系统注入的服务: 构造函数 可以直接在构造函数中传入所依赖的服务,这是非常常见的DI注入方式. public Va ...
- MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息
MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...
- Spring依赖注入的方式、类型、Bean的作用域、自动注入、在Spring配置文件中引入属性文件
1.Spring依赖注入的方式 通过set方法完成依赖注入 通过构造方法完成依赖注入 2.依赖注入的类型 基本数据类型和字符串 使用value属性 如果是指向另一个对象的引入 使用ref属性 User ...
- Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor
Webservice WCF WebApi 注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...
随机推荐
- 15个关于Chrome的开发必备小技巧[译]
谷歌Chrome,是当前最流行且被众多web开发人员使用的浏览器.最快六周就更新发布一次以及伴随着它不断强大的开发组件,使得Chrome成为你必备的开发工具.例如,在线编辑CSS,console以及d ...
- vmware上网的方式
vmware上网设置 vmware虚拟机上网设置 我的一些心得,如下: 如何使vmware虚拟机中的操作系统能够上网? 第一种情况: 主机使用PPPOE拨号上网 方法一:NAT方式 1.先关闭虚拟机中 ...
- OpenCV人脸识别LBPH算法源码分析
1 背景及理论基础 人脸识别是指将一个需要识别的人脸和人脸库中的某个人脸对应起来(类似于指纹识别),目的是完成识别功能,该术语需要和人脸检测进行区分,人脸检测是在一张图片中把人脸定位出来,完成的是搜寻 ...
- vue双向数据绑定原理探究(附demo)
昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与 ...
- ASP.NET MVC——模型绑定
这篇文章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,大家可能都会产生一个疑问,为什么URL片段最后会转换为例如int型或者其他类型的参数呢?这里就不得不 ...
- 使用po模式读取豆瓣读书最受关注的书籍,取出标题、评分、评论、题材 按评分从小到大排序并输出到txt文件中
#coding=utf-8from time import sleepimport unittestfrom selenium import webdriverfrom selenium.webdri ...
- 免费道路 bzoj 3624
免费道路(1s 128MB)roads [输入样例] 5 7 21 3 04 5 13 2 05 3 14 3 01 2 14 2 1 [输出样例] 3 2 04 3 05 3 11 2 1 题解: ...
- maven依赖查询地址
http://search.maven.org/#search%7Cga%7C1%7C
- BPM费控管理解决方案分享
一.方案概述费用是除经营成本外企业的最主要支出,费用管理是财务管理的核心之一,加强企业内控管理如:费用申请.费用报销.费用分摊.费用审批.费用控制和费用支付等,通过科学有效的管理方法规范企业费用管理, ...
- CocoaPods的安装、使用、以及遇到的问题
CocoaPods是什么? 当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等.可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而 ...