在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中使用依赖注入的方式创建视图的更多相关文章

  1. dotnetcore3.1 WPF 中使用依赖注入

    dotnetcore3.1 WPF 中使用依赖注入 Intro 在 ASP.NET Core 中默认就已经集成了依赖注入,最近把 DbTool 迁移到了 WPF dotnetcore 3.1, 在 W ...

  2. Spring学习(三)——Spring中的依赖注入的方式

    [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...

  3. JavaEE开发之Spring中的依赖注入与AOP

    上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...

  4. .NET CORE——Console中使用依赖注入

    我们都知道,在 ASP.NET CORE 中通过依赖注入的方式来使用服务十分的简单,而在 Console 中,其实也只是稍微绕了个小弯子而已.不管是内置 DI 组件或者第三方的 DI 组件(如Auto ...

  5. JavaEE开发之Spring中的依赖注入与AOP编程

    上篇博客我们系统的聊了<JavaEE开发之基于Eclipse的环境搭建以及Maven Web App的创建>,并在之前的博客中我们聊了依赖注入的相关东西,并且使用Objective-C的R ...

  6. Asp.net core中的依赖注入

    使用服务 在Asp.net core的Controller中,可以通过如下两种方式获取系统注入的服务: 构造函数 可以直接在构造函数中传入所依赖的服务,这是非常常见的DI注入方式. public Va ...

  7. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  8. 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平台下, ...

  9. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

随机推荐

  1. ubuntu14.04 安装系统

    p { margin-bottom: 0.1in; line-height: 120% } code.cjk { font-family: "Droid Sans Fallback" ...

  2. ndk学习16: unix domain socket

    一.UNIX Domain Socket 概念: UNIX Domain Socket是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC) 特点: 1. 它不需要经过网络协议栈,不需要 ...

  3. 滑雪(dp好题)

    题目描述:贝西去科罗拉多州去滑雪,不过还她不太会玩,只是个能力为 1 的渣渣.贝西从 0 时刻进入滑雪场,一到 T 时刻就必须离开.滑雪场里有 N 条斜坡,第 i 条斜坡滑行一次需要 Di 分钟,要求 ...

  4. MyEclipse 编写 ExtJS 卡死问题解决方法

    MyEclipse 8.6  在 jsp 中编写 ExtJS时,会出现卡死现象,让人甚是头疼.网上找了很多方法,折腾半天,还是不管用. 什么MyEclipse 优化,Validation 取消,MyE ...

  5. maven之ssh项目搭建

    1:新建maven-archetupe-webapp项目 2:web.xml配置文件如下 <?xml version="1.0" encoding="UTF-8&q ...

  6. maven下载,安装与eclipse中maven配置

    1.maven下载.安装与环境变量配置 http://blog.csdn.net/jiuqiyuliang/article/details/45390313 2.Eclipse中maven的配置 ht ...

  7. 7_linux用户及权限(2)和管理

    useradd:useradd [options] USERNAME -u UID -g GID(基本组) -G GID,...(附加组) //省略号表示可以有多个,彼此之间用逗号隔开 -c &quo ...

  8. Django 学习笔记(二) --- HTML 模版

    人生苦短 ~ Tips:仅适用于 Python 3+(反正差别不大,py2 改改也能用).因为据 Python 之父 Guido van Rossum 说会在 2020 年停止对 Python 2 的 ...

  9. [SoapUI] 判断工程下某个文件是否存在,存在就删除

    def excelName = "AllTests-Fails" String projectPath = context.expand( '${projectDir}' ) St ...

  10. 20169207《linux内核原理与分析》第二周作业

    第一部分:学习MOOC网Linux内核分析的课程. 首先对冯诺依曼体系结构和存储程序计算机工作模型进行了了解,查阅资料,对冯诺依曼体系的特点与课堂上的相结合,真实明白了模型的特点. 在汇编C语言程序时 ...