一、前言
我心中的插件系统应该是像Nop(甚至更好,比如Orchard,OSGI。NET)。每个插件模块不仅仅是一堆实现某个业务接口,然后通过反射或者IOC技术调用的dll,而是一个完整的mvc小程序。我可以在后台控制插件的安装和禁用。目录结构如下:
生成后,它被放在站点根目录下的Plugins文件夹中,每个插件都有一个子文件夹
插件/Sms。阿里云/
插件/Sms。ManDao/
我是一个有强迫症的懒人,不想把生成的dll文件复制到bin目录下。
二、需要解决的问题
默认情况下,1.asp.net引擎将只加载“bin”文件夹中的dll,而我们想要的插件文件分散在Plugins目录下的各个子目录中。
2.在视图中使用模型时,应该怎么做?默认情况下,RazorViewEngine使用BuildManager将视图编译成动态程序集,然后使用Activator。创建实例来实例化新编译的对象。当使用插件dll时,当前的AppDomain不知道如何解析这个引用模型的视图,因为它不存在于“bin”或GAC中。更糟糕的是,您不会收到任何错误消息来告诉您为什么它不起作用或者问题出在哪里。相反,他会告诉你在View目录中找不到文件。
3.站点下正在运行一个插件,直接覆盖这个插件dll会告诉你这个dll当前正在使用,不能覆盖。
4.如何加载视图文件而不将其放入网站的视图目录。
3.Net 4.0使这一切成为可能
Net4.0有一个新特性,那就是在应用程序初始化之前执行代码的能力。这个特性使应用程序能够在Application_Star之前做一些工作,比如我们可以在应用程序启动之前告诉我们的mvc插件系统dll放在哪里,并做预加载处理。关于的几个新特性?net,有一些坚果和博客要介绍。点击我。关于preapplications startmethod attribute,已经有一些博主写过了。点击我。Abp启动模块也应该利用preapplicationstartmethod attribute的特征原理来实现,但不清楚是否是这样。
第四,解决方案
1.修改主站点的web.config目录,这样运行时就可以从bin目录之外的其他目录加载文件。
runtime assembly binding xmlns=' urn : schemas-Microsoft-com : ASM . v1 '探测private path=' Plugins/temp/'//assembly binding/Runtime 2。开发一个简单的插件管理类。这个类的功能是在Application_Start之前,将Plugins的各个子目录中的dll复制到步骤1中指定的文件夹中。为了使演示尽可能简单,不会检测到重复的dll(例如,ef程序集在插件中被引用,主站点也引用ef dlls,因此无需将插件中的dll复制到上面设置的动态程序集目录中)
使用系统;使用系统。集合。通用;使用系统IO;使用系统Linq .使用系统。反思;使用系统。文字;使用系统。线程化。任务;使用系统网络.使用系统。网络。汇编;使用系统。网络托管;[程序集:预应用程序启动方法(类型的(插件核心。预应用初始化),'初始化')]命名空间插件核心{公共类PreApplicationInit {静态PreApplicationInit(){ plug infolder=new DirectoryInfo(宿主环境.MapPath(' ~/plugins ');阴影复制文件夹=新目录信息(主机环境MapPath(' ~/plugins/temp ');} ///摘要///插件所在目录信息////摘要私有静态只读目录信息插件信息文件夹;///摘要///程序应行时指定的dll目录////摘要私有静态只读目录信息ShadowCopyFolder公共静态无效initialize(){ 0目录。创建目录(阴影复制文件夹。全名);//清空插件dll运行目录中的文件文件夹中的foreach (var f ).GetFiles('* .dll ',搜索选项所有目录){ f . Delete();} foreach (var)插件插件插件. GetFiles('* .dll ',搜索选项。所有目录)。其中(I=I .目录。父母。名称=='插件'){文件.复制(插头。全名,路径。组合(阴影复制文件夹。全名,插头。姓名),为真);} foreach (var a在阴影复制文件夹中. GetFiles('* ' .dll ',搜索选项。所有目录)。选择(x=AssemblyName .GetAssemblyName(x.FullName)).选择(x=组件加载(x .全名)){构建管理器.addreferenceassembly(a);} } }}3.如何让视角引擎找到我们的视图呢?答案是重写剃刀视图引擎的方法,我采用了约定大于配置的方式(假设我们的插件项目命名空间为插件。应用。短信,那么默认的控制器命名空间为插件。应用,短信,控制器,插件生成后的文件夹必须为/插件/插件app。Sms/),通过分析当前控制器就可以知道当前插件的视角目录位置
使用系统;使用系统。集合。通用;使用系统Linq .使用系统。线程化。任务;使用系统网络.使用系统网络。手动音量调节使用系统。网页。网页。剃刀;命名空间插件网站。{ 0公共类customerviewinengine : razoviewnine {///summary///定义视图页所在地址////摘要私有字符串[]_视图位置格式=新[]{ ' ~/视图/零件/{ 0 } .cshtml ',' ~/Plugins/{ pluginFolder }/view/{ 1 }/{ 0 } .“,”~/Plugins/{ pluginFolder }/view/Shared/{ 0 } .cshtml ',' ~/view/{ 1 }/{ 0 } .cshtml ',' ~/view/Shared/{ 0 } .cshtml ',};public override viewpunineresult find view(控制器上下文控制器上下文,字符串viewName,字符串masterName,bool useCache){ string ns=控制器上下文.控制器。GetType().命名空间;字符串控制器=controllerContext .控制器。获取类型()。//说明是插件中的控制器,视图目录需要单独处理if (ns .ToLower().包含(“插件”){ var plugin文件夹=ns .ToLower().替换('。控制器',' ');ViewLocationFormats=替换占位符(插件文件夹);}返回基地FindView(controllerContext、viewName、masterName、useCache);} ///摘要///替换插件文件夹占位符////summary///param name=' folderName '/param private string[]替换占位符(string folderName){ string[]tempArray=新字符串[_ viewplocationformats .长度];if (_viewLocationFormats!=null){ for(int I=0;i _viewLocationFormats .长度;I){ tempArray[I]=_ viewLocationFormats[I].替换(“{pluginFolder}”,Foldername);} }返回tempArray} }}然后在主站点的Global.asax中将剃刀引擎指定为我们重写过的
4.开始制作一个插件目录,跟我们平时建立的手动音量调节项目并没有太大区别,只是发布时需要做一些设置。生成路径要按照第3条的约定来写,不然会找不到视图文件
。视角目录下的网络。配置和cshtml .文件要复制到生成目录(在文件中点右键)
3.在引用项目中设置生成属性,如果主程序下已经存在“复制到输出目录”则设置为无,否则复制到动态bin目录时会出错。您可以在步骤2中修改类并添加文件比较功能,只有bin目录中不存在的文件才会被复制到动态bin目录中。
4.生成的目录结构如下:
5.运行,一切正常,插件中的控制器工作正常,引用视图中的Model没问题
此时,即使一个插件系统的核心部分完成了,也可以继续扩展和添加插件发现、安装和卸载功能,与核心功能相比,这些都是小儿科。后续我会给出一篇基于Abp框架的插件系统的文章。我有兴趣准备小板凳,买瓜子花生:)
动词(verb的缩写)源代码
下载插件链接: https://pan.baidu.com/s/1nvmbL81密码: 85v1
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。