ZKWeb实现了一个非常灵活和强大的插件系统,
支持在任何位置存放插件, 在代码修改后自动编译, 以及发布时可以只发布程序集等等.
ZKWeb对插件的管理不依赖于IDE, 内部会调用Roslyn的API来完成对插件源代码的编译.

插件的查找和加载

在前面的配置文件已经提到过, 插件列表保存在App_Data\config.json下.

config.json

加载时会按Plugins中定义的顺序进行加载, 然后按PluginDirectories中定义的顺序进行查找.
以上面的配置为例, 假定网站项目在D:\Projects\Hello.World\src\Hello.World.AspNetCore,
会查找以下的路径加载Common.Base这个插件:

D:\Projects\ZKWeb.Plugins\Common.Base
D:\Projects\Hello.World\src\Hello.World.Plugins\Common.Base

加载完Common.Base后会按查找以下的路径加载Common.Captcha:

D:\Projects\ZKWeb.Plugins\Common.Captcha
D:\Projects\Hello.World\src\Hello.World.Plugins\Common.Captcha

一直到Plugins中的插件全部加载完毕.

插件信息

各个插件都会有文件和版本等信息, 这些信息保存在插件文件夹下的plugin.json中, 格式如下:

{
    "Name": "插件名称",
    "Version":  "插件版本",
    "Description": "插件描述",
    "Dependencies": [ "依赖的其他插件" ],
    "References": [ "依赖的程序集" ]
}

以下是Common.Admin插件的plugin.json:

{
    "Name": "Admin Panel",
    "Version": "1.8.0",
    "Description": "Admin panel and users management",
    "Dependencies": [ "Common.Base", "Commin.Captcha" ]
}

添加新的插件时仿照这个格式编写plugin.json即可.

插件的目录结构

插件的目录结构在网站结构中已经提过, 如下:

  • 插件名称
    • bin 由插件编译出来的程序集
    • src 插件的源代码
      • Controllers 储存控制器的文件夹
      • 更多保存代码的文件夹...
      • Plugin.cs 载入插件时的处理, 可以省略
    • static 静态文件
    • templates 模板文件
    • templates.mobile 移动端专用的模板文件
    • plugin.json 插件信息

使用发布工具发布网站后会自动删除src目录, 所以最终发布出来的插件不会包含源代码.

依赖的程序集

插件需要依赖外部的程序集时, 需要在plugin.jsonReferences指定.

Common.QRCoder为例

  • Common.QRCoder 插件文件夹
    • references
      • net
        • QRCoder.dll net461版本的程序集
      • netstandard
        • QRCoder.dll netstandard版本的程序集
    • plugin.json

plugin.json的内容如下

{
    "Name": "QRCoder",
    "Version": "1.8.0",
    "Description": "Support generate QRCode",
    "References": [ "QRCoder" ],
    "Dependencies": [ "Common.Base" ]
}

ZKWeb在读取到References后会自动从references文件夹下查找对应的程序集并载入.

透过式文件系统

ZKWeb使用了类似Django的透过式文件系统, 一个插件可以简单的重载另外一个插件的资源文件.
读取资源文件的顺序如下, 会返回最先存在的路径.

"App_Data/路径"
foreach (按加载顺序的相反顺序枚举插件) {
    "插件目录/路径"
}

例如插件加载顺序是 Plugins: [ "PluginA", "PluginB" ], 且目录结构如下:

资源文件重载的示例

读取资源templates/some_folder/some.html会读取PluginB下的文件,
读取资源static/other_folder/other.txt会读取PluginA下的文件.

读取资源文件可以使用以下的代码

var fileStorage = Application.Ioc.Resolve<IFileStorage>();
var file = fileStorage.GetResourceFile("templates", "some_folder", "some.html");
var contents = file.ReadAllText();

组件的注册顺序

在ZKWeb中, 插件的注册顺序会影响组件的注册顺序.

例如插件加载顺序是 Plugins: [ "PluginA", "PluginB" ],
插件PluginA[ExportMany]class ExampleHandlerA : IExampleHandler { },
插件PluginB[ExportMany]class ExampleHandlerB : IExampleHandler { }.

这时使用Application.Ioc.ResolveMany<IExampleHandler>()会获取到包含两个实例的列表,
第一个实例类型是ExampleHandlerA, 第二个实例类型是ExampleHandlerB.

在网站启动时执行操作

部分插件可能需要在网站启动时执行一些操作, 可以使用IPlugin接口:

[ExportMany]
public Plugin : IPlugin {
    public Plugin() {
        // 这里会在网站启动时运行
        // 并且会按插件的注册顺序运行
    }
}

调试插件

调试在当前项目内的插件很简单, 直接F5运行并下断点即可.

调试在当前项目外的插件有两种办法:

  • 第一种
    • 在插件的VS中选择调试-挂载到进程并选择IIS进程或Kestrel进程挂载
    • Asp.Net, Owin选择iisexpress.exe
    • Asp.Net Core选择项目主程序.exe, 注意不要选iis或者w3wp
  • 第二种
    • 把需要调试的文件拖动到Visual Studio中,然后在里面下断点

挂载到进程

禁止检测插件源代码更新

在默认情况下, ZKWeb会自动检测插件源代码的更新, 更新时会自动重启服务器以重新编译和加载插件.
如果你不想ZKWeb自动重启服务器可以添加以下的选项到App_Data\config.json:

{
    "Extra": { "ZKWeb.DisableAutomaticPluginReloading": true }
}