欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用ASP.NET MVC引擎开发插件系统

程序员文章站 2023-11-17 09:21:46
一、前言 我心中的插件系统应该是像nop那样(更牛逼的如orchard,osgi.net),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或ioc技术来调...

一、前言

我心中的插件系统应该是像nop那样(更牛逼的如orchard,osgi.net),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或ioc技术来调用,而是一个完整的mvc小应用,我可以在后台控制插件的安装和禁用,目录结构如下:

使用ASP.NET MVC引擎开发插件系统

生成后放在站点根目录下的plugins文件夹中,每个插件有一个子文件夹

plugins/sms.aliyun/

plugins/sms.mandao/

我是一个有强迫症的的懒人,我不想将生成的dll文件拷贝到bin目录。

二、要解决的问题

1.asp.net引擎默认只会加载“bin”文件夹中的dll,而我们想要的插件文件则是分散在plugins目录下的各个子目录中。

2.视图中使用了模型时如何处理?默认情况下razorviewengine使用buildmanager将视图编译成动态程序集,然后使用activator.createinstance实例化新编译的对象,而使用插件dll时,当前的appdomain不知道如何解析这种引用了模型的视图,因为它不存在于“bin”或gac中。更糟糕的是,不会收到任何错误消息,告诉您为什么它不工作,或者问题在哪。相反,他会告诉你,从view目录中找不到文件。

3.某个插件正挂在站点下运行着,直接覆盖插件的dll,会告诉你当前dll正在使用,不能被覆盖。

4.视图文件不放站点的view目录中,该如何加载。

三.net 4.0让这一切变成可能

net4.0有一个新特性是在应用程序初始化之前执行代码的能力(preapplicationstartmethodattribute),这个特性使得应用程序在application_star前可以做一些工作,例如我们可以在应用启动之前告知我们的mvc插件系统的dll放在哪,做预加载处理等。关于.net的几个新特性,有歪果仁写得有博客来介绍,点我。,关于preapplicationstartmethodattribute,有博友已经写过了,点我。 abp的启动模块应该也是使用preapplicationstartmethodattribute这个特性原理来实现的,具体是不是这样还没看。

四、解决方案

1.修改主站点web.config目录,让运行时除了加载bin目录中的文件,还可以从其它目录加载

 <runtime>
 <assemblybinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <probing privatepath="plugins/temp/" />
 </assemblybinding>
 </runtime>

2.开发一个简易的插件管理类,这个类的作用就是在application_start之前就把plugins各子目录中的dll拷贝到第1步指定的文件夹中,为了让demo尽可能简单,没有对重复的dll进行检测(比如插件中引用了ef程序集,主站点也引用了,在站点bin目录中已经存在ef的dll了,就没必要再把插件中的dll拷贝到上面设置的动态程序集目录中)

using system;
using system.collections.generic;
using system.io;
using system.linq;
using system.reflection;
using system.text;
using system.threading.tasks;
using system.web;
using system.web.compilation;
using system.web.hosting;
[assembly: preapplicationstartmethod(typeof(plugins.core.preapplicationinit), "initialize")]
namespace plugins.core
{
 public class preapplicationinit
 {

  static preapplicationinit()
  {
   pluginfolder = new directoryinfo(hostingenvironment.mappath("~/plugins"));
   shadowcopyfolder = new directoryinfo(hostingenvironment.mappath("~/plugins/temp"));
  }

  /// <summary>
  /// 插件所在目录信息
  /// </summary>
  private static readonly directoryinfo pluginfolder;

  /// <summary>
  /// 程序应行时指定的dll目录
  /// </summary>
  private static readonly directoryinfo shadowcopyfolder;

  public static void initialize()
  {
   directory.createdirectory(shadowcopyfolder.fullname);
   //清空插件dll运行目录中的文件
   foreach (var f in shadowcopyfolder.getfiles("*.dll", searchoption.alldirectories))
   {
    f.delete();
   }
   foreach (var plug in pluginfolder.getfiles("*.dll", searchoption.alldirectories).where(i=>i.directory.parent.name== "plugins"))
   {
    file.copy(plug.fullname, path.combine(shadowcopyfolder.fullname, plug.name), true);
   }
   foreach (var a in
    shadowcopyfolder
    .getfiles("*.dll", searchoption.alldirectories)
    .select(x => assemblyname.getassemblyname(x.fullname))
    .select(x => assembly.load(x.fullname)))
   {
    buildmanager.addreferencedassembly(a);
   }

  }
 }
}

3.如何让view引擎找到我们的视图呢?答案是重写razorviewengine的方法,我采用了约定大于配置的方式(假设我们的插件项目命名空间为plugins.apps.sms,那么默认的控制器命名空间为plugins.apps.sms.controllers,插件生成后的文件夹必须为/plugins/plugins.apps.sms/),通过分析当前控制器就可以知道当前插件的view目录位置

using system;
using system.collections.generic;
using system.linq;
using system.threading.tasks;
using system.web;
using system.web.mvc;
using system.web.webpages.razor;

namespace plugins.web
{
 public class customerviewengine : razorviewengine
 {

  /// <summary>
  /// 定义视图页所在地址。
  /// </summary>
  private string[] _viewlocationformats = new[]
  {
   "~/views/parts/{0}.cshtml",
   "~/plugins/{pluginfolder}/views/{1}/{0}.cshtml",
   "~/plugins/{pluginfolder}/views/shared/{0}.cshtml",
   "~/views/{1}/{0}.cshtml",
   "~/views/shared/{0}.cshtml",
  };
  public override viewengineresult findview(controllercontext controllercontext, string viewname, string mastername, bool usecache)
  {
   string ns = controllercontext.controller.gettype().namespace;
   string controller = controllercontext.controller.gettype().name.replace("controller", "");
   //说明是插件中的控制器,view目录需要单独处理
   if (ns.tolower().contains("plugins"))
   {
    var pluginsfolder = ns.tolower().replace(".controllers", "");
    viewlocationformats = replaceplaceholder(pluginsfolder);
   }
   return base.findview(controllercontext, viewname, mastername, usecache);
  }
  /// <summary>
  /// 替换pluginfolder占位符
  /// </summary>
  /// <param name="foldername"></param>
  private string[] replaceplaceholder(string foldername)
  {
   string[] temparray = new string[_viewlocationformats.length];
   if (_viewlocationformats != null)
   {
    for (int i = 0; i < _viewlocationformats.length; i++)
    {
     temparray[i] = _viewlocationformats[i].replace("{pluginfolder}", foldername);
    }
   }
   return temparray;
  }
 }
}


然后在主站点的global.asax中将razor引擎指定为我们重写过的

使用ASP.NET MVC引擎开发插件系统

4.开始制作一个插件目录,跟我们平时建立的mvc项目并没有太大区别,只是发布时需要做一些设置。

.生成路径要按照第3条的约定来写,不然会找不到视图文件

使用ASP.NET MVC引擎开发插件系统

.view目录下的web.config和.cshtml文件要复制到生成目录(在文件中点右键)

使用ASP.NET MVC引擎开发插件系统

使用ASP.NET MVC引擎开发插件系统

3.设置引用项目中的生成属性,主程序下面已经有了的就把“复制到输出目录”设置为无,要不然拷贝到动态bin目录时会出错,可以对第2步中的那个类改造一下,加入文件比较功能,bin目录中没有的,才拷贝到动态bin目录中。

使用ASP.NET MVC引擎开发插件系统

4.生成后的目录结构如下:

使用ASP.NET MVC引擎开发插件系统

5.跑一下,一切正常,插件中的控制器工作正常,视图中引用了model也没问题

使用ASP.NET MVC引擎开发插件系统

到此,一个插件系统的核心部分就算完成了,你可继续进行扩展,增加插件的发现、安装、卸载功能,这些相对于核心功能来说,都是小儿科。后续我会基于abp框架出一个插件系统的文章,有兴趣的把小板凳准备好,瓜子花生买上:)

五、源代码

下载plugins链接: https://pan.baidu.com/s/1nvmbl81 密码: 85v1

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。