模块概述
谈到模块大家应该都不会感到陌生,不管是前端还是后端都有模块的概念,XAF中的模块概念与大多数框架中的模块概念是相通的。XAF模块首先是一个.NET类库,同时它还包含一个继承自ModuleBase的Module类,Module类主要用于收集及对外暴露模块中的一些资源(BusinessObject、Controller、Action、Model、PropertyEditort等),同时XAF模块间的依赖与.NET类库间的依赖也是一致的,比如:A模块依赖B模块,B模块依赖C模块,那A模块同时拥有了B模块与C模块所提供的资源。XAF模块涵盖的功能很多,当前只对模块的部分功能进行介绍,后续还会与其它的概念一起介绍。
XAF默认提供了大量的基础功能模块(审计、图表、仪表盘、验证、报表、状态机等),你可以根据需要将这些模块添加到你的项目中。
项目结构
XAF项目主要包含应用程序项目与模块项目,当然也可以包含普通的.NET项目。应用程序项目是负责项目的启动,所以它需要指定平台(WinForm与Blazor),模块项目在不依赖特定平台时,是可以被其它应用程序项目或其它模块项目引用,依赖特定平台后,只能被特定平台的项目引用。
在XAF22.1之前的版本中,XAF项目中包含两种模块项目类型,一种是与平台相关的,一种是不相关的,在之后的版本中,将平台相关的模块代码移到了应用程序项目中,目的是为了简化项目结构。如果你想创建独立的模块,并且模块中包含了不同平台的代码,你还是需要创建平台相关的模块项目。
模块注册
模块注册分为两种,一种是模块间依赖的注册,一种是将模块注册到应用程序项目中,这两种注册方式是不一样的。
在整个XAF项目启动过程中模块是最先被初始化的,这里要引出一个概念Application(在不同的平台会有不同的实现,它们都是XafApplication的子类),从名字你就可以看出它在XAF中的地位,它上面挂载了运行XAF所需的所有资源,XAF模块当然也在其中,同时它还提供了丰富的功能,在WinForm项目中Application是以单例形式存在的,在Blazor项目中每一个会话都会有一个Application实例,现在你可以简单将Application理解为整个XAF应用的中心,后面会对它进行单独的介绍。
模块注册到应用程序项目中
在XAF22.1后,将模块注册到应用程序项目中WinForm与Blazor采用了相同的配置方式,下面以WinForm为例
上面的代码是ApplicationBuilder类中的片段,在XAF22.1后,WinForm项目都会存在一个ApplicationBuilder类,它是用于构建Application实例。接触过XAF的小伙伴对上面的代码应该比较容易理解,这里简单的解读一下,builder是IXafApplicationBuilder<TBuilder>的一个实例,在不同的平台(WinForm与Blazor)会有不同的实现。
builder.Modules是专门用于将模块注册到应用程序项目中的,它上有一个Add方法可以方便对模块进行注册,当然你也可以基于Add方法再作进一步的封装,例如:AddConditionalAppearance(ConditionalAppearance模块的注册方法),注册模块的先后顺序并不影响模块的加载。
在之前的XAF版本中,将模块注册到应用程序项目中,都是将模块中Module类的实例添加到Application.Modules中,新的注册方式可读性更强,目的是相同的,但将模块添加到Application.Modules中是在builder.Build()时执行的。
builder实例中包含一个_buildSteps委托集合,调用builder上的方法也就是向_buildSteps集合中添加委托(Action<XafApplication>),委托有一个Application参数,借用此参数可以对Application进行配置,最后的builder.Build()是将_buildSteps集合中的委托遍历并执行,并返回Application的一个实例。下面是Build方法简化后的代码
在模块注册到应用程序项目中时可以对模块进行配置,如果是自己的模块,首先需要在模块的Module类中添加对应的属性,如Test,在注册时直接给模块属性赋值,如下面的代码。如何使用模块Module类中的属性,将在后面进行介绍
如果你的模块是对外发布的,想更严谨一些,你可以参考XAF内部模块的做法,单独创建一个Options类,通过Options类收集模块配置,避免使用者直接给Module类中的属性赋值
模块间依赖的注册
在前面的介绍中,大家应该已经知道模块的依赖与.NET类库的依赖是相似的。一个XAF模块想依赖另一个XAF模块,首先要引用这个被依赖模块的类库,并在Module类的构造函数中,将被依赖模块中Module类的类型添加到RequiredModuleTypes中,代码如下:
由于RequiredModuleTypes的初始值是来自GetRequiredModuleTypesCore方法,所以我们也可以在Module类中重写GetRequiredModuleTypesCore方法来实现相同的操作,代码如下:
在上面的代码中,模块注册的先后顺序是不影响后续操作的,甚至依赖模块被无意中重复添加了,也不会有任何影响,因为模块在被正式加载时,会进行相应的过滤,当然最好不要这样做,下面的代码是可以正常运行的
模块加载
模块加载是在builder.Build()时进行的,模块加载的核心逻辑是放在ModuleList类中的。
这里简单说一下它的加载逻辑,ModuleList会先加载通过builder在应用程序项目中注册的模块,然后再去加载已加载模块的依赖模块,依赖模块再去加载自身的依赖模块,这里是一个递归,当然在加载过程中ModuleList会判断如果已被加载则跳过,最终会保证ModuleList中每一个模块类型只有一个模块实例,这里的ModuleList就是Application.Modules,你可以通过Application.Modules获取被加载的所有模块实例。
ModuleList类上有一个FindModule方法,可以使用它通过模块类型查找模块实例,那我们就可以在能够访问到Application的情况下,调用Application.Modules.FindModule查找模块实例,就可以访问到模块中的属性。
总结
模块在XAF中是一个比较重要的概念,模块中还很多知识点,这里只介绍了模块的注册与加载,在后续还会结合其它概念再介绍,同时还要注意在XAF22.1后,XAF项目结构进行了简化,同时模块在应用程序项目中的注册方式也不一样了。
注意:本系列文章只是讲解XAF中常用到的概念,方便大家理解,具体的操作方法还是需要参考官方文档