在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平台下, ...
随机推荐
- Javascript - Promise学习笔记
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
- JavaScript var关键字、变量的状态、异常处理、命名规范等介绍
本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...
- C#多线程之线程同步篇1
在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...
- node.js学习(二)--Node.js控制台(REPL)&&Node.js的基础和语法
1.1.2 Node.js控制台(REPL) Node.js也有自己的虚拟的运行环境:REPL. 我们可以使用它来执行任何的Node.js或者javascript代码.还可以引入模块和使用文件系统. ...
- WPF 捕获键盘输入事件
最近修改的一个需求要求捕获键盘输入的 Text,包括各种标点符号. 最开始想到的是 PreviewKeyDown 或者 PreviewKeyUp 这样的键盘事件. 但是这两个事件的对象 KeyEven ...
- Java类变量和成员变量初始化过程
一.类的初始化 对于类的初始化:类的初始化一般只初始化一次,类的初始化主要是初始化静态成员变量. 类的编译决定了类的初始化过程. 编译器生成的class文件主要对定义在源文件中的类进行了如下的更改: ...
- StrategyPattern (策略模式)
/** * 策略模式 * @author TMAC-J * 根据环境的不同选择不同的策略,把策略用接口抽象出来 */ public class StrategyPattern { interface ...
- 基于select的python聊天室程序
python网络编程具体参考<python select网络编程详细介绍>. 在python中,select函数是一个对底层操作系统的直接访问的接口.它用来监控sockets.files和 ...
- 一键生成APP官网
只需要输入苹果下载地址,安卓市场下载地址,或者内测下载地址,就能一键生成APP的官网,方便在网上推广. 好推APP官网 www.hotapp.cn/app
- .Net中的AOP读书笔记系列之AOP介绍
返回<.Net中的AOP>系列学习总目录 本篇目录 AOP是什么? Hello,World! 小结 本系列的源码本人已托管于Coding上:点击查看,想要注册Coding的可以点击该连接注 ...