TinyFrame升级之八:实现简易插件化开发

本章主要讲解如何为框架新增插件化开发功能。

在.net 4.0中,我们可以在Application开始之前,通过PreApplicationStartMethod方法加载所需要的任何东西。那么今天我们主要做的工作就集中在这个时间段:

1.将插件DLL及文件拷贝入主网站目录并编译

2.加载Plugin

首先来说说第一步,由于这步里面,我们主要拷贝DLL及文件,所以我们利用了一个List<Assembly>容器来记录所有的插件的DLL,具体代码如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Web;
   6:  using System.Reflection;
   7:  using System.Web.Hosting;
   8:  using System.IO;
   9:  using System.Web.Compilation;
  10:  using TinyFrame.Framework;
  11:   
  12:  [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
  13:  namespace TinyFrame.Framework
  14:  {
  15:      public class PluginManager
  16:      {
  17:          private const string pluginDirectory = "~/Areas";
  18:          private const string pluginShadowCopyDirectory = "~/Areas/Temp";
  19:          private static readonly List<Assembly> pluginAssemblies=new List<Assembly>();
  20:   
  21:          //初始化方法
  22:          public static void Initialize()
  23:          {
  24:              var pluginPath = HostingEnvironment.MapPath(pluginDirectory);
  25:              //var pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");
  26:              foreach (var file in Directory.EnumerateFiles(pluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
  27:              {
  28:                  pluginAssemblies.Add(Assembly.LoadFile(file));
  29:              }
  30:   
  31:              pluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
  32:   
  33:              AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
  34:          }
  35:   
  36:          private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
  37:          {
  38:              var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
  39:              // 如果未加载程序集
  40:              foreach (var assembly in currentAssemblies)
  41:              {
  42:                  if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
  43:                  {
  44:                      return assembly;
  45:                  }
  46:              }
  47:   
  48:              return null;
  49:          }
  50:   
  51:          //得到程序集名稱
  52:          public static List<string> PluginNames()
  53:          {
  54:              return pluginAssemblies.Select(c => c.GetName().Name).ToList();
  55:          }
  56:      }
  57:  }

第12行,主要标志本方法将于Application_Start开始之前执行。

第17行,主要定义了主网站中放置插件的目录,这里为Areas目录。

第22行,为我们的初始化方法。

第28行,遍历我们的插件目录,加载名称中包含Plugin关键字的DLL。

第31行,遍历完毕后,将这些DLL加入到BuildManager中,以便于编译操作。

这里讲完之后,剩下的就是来写我们的插件了,这里要进行的是第二步:

首先,新建一个TinyFrame.Plugin.NivoSlider的MVC 4 站点,在这个站点根目录下面添加一个名称为AreaRegister.cs的类文件,内容如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using System.Web.Mvc;
   6:   
   7:  namespace TinyFrame.Plugin.Sample
   8:  {
   9:      public class AreaRegister : AreaRegistration
  10:      {
  11:   
  12:          public override string AreaName
  13:          {
  14:              get
  15:              {
  16:                  return "TinyFrame.Plugin.NivoSlider";
  17:              }
  18:          }
  19:   
  20:          public override void RegisterArea(AreaRegistrationContext context)
  21:          {
  22:              context.MapRoute(
  23:                      "NivoSlider_default",
  24:                      "NivoSlider/{controller}/{action}/{id}",
  25:                      new { action = "Index", id = UrlParameter.Optional },
  26:                      new string[] { "TinyFrame.Plugin.NivoSlider.Controllers" }
  27:                  );
  28:          }
  29:   
  30:      }
  31:  }

其中,类本身需要继承自AreaRegistration,以便于使用MVC本身提供的插件化支持。然后通过重载AreaName和RegisterArea方法,实现AreaName的定义和路由的映射。AreaName主要是为了识别返回的Plugin内容区域,而RegisterArea主要是为路由访问提供了支持。需要注意的是,第26行是Controller的命名空间,需要写上,否则会因为插件的目录和主站的目录一致,导致报错。

然后我们来添加测试的Controller和View:

在Controllers文件夹下面添加NivoSliderController.cs类,并返回一个空的页面:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using System.Web.Mvc;
   6:   
   7:  namespace TinyFrame.Plugin.NivoSlider.Controllers
   8:  {
   9:      public class NivoSliderController : Controller
  10:      {
  11:          public ActionResult Index()
  12:          {
  13:              return View();
  14:          }
  15:   
  16:      }
  17:  }

然后在Views文件夹下新建NivoSlider文件夹,并在其中创建一个名称为Index.cshtml的页面文件:

   1:  @{
   2:      ViewBag.Title = "Index";
   3:  }
   4:  <div style="width:600px;height:400px;margin:0 auto;">
   5:    <div class="slider-wrapper theme-default" style="float:left;">
   6:          <div id="slider" class="nivoSlider">
   7:              <img src="@Url.Content("~/Content/slider/images/toystory.jpg")"  alt="" />
   8:              <img src="@Url.Content("~/Content/slider/images/up.jpg")"        alt="" title="This is an example of a caption" />
   9:              <img src="@Url.Content("~/Content/slider/images/walle.jpg")"     alt="" data-transition="slideInLeft" />
  10:              <img src="@Url.Content("~/Content/slider/images/nemo.jpg")"      alt="" title="#htmlcaption" />
  11:          </div>
  12:          <div id="htmlcaption" class="nivo-html-caption">
  13:              <strong>This</strong> is an example of a <em>HTML</em> caption with <a href="#">a link</a>. 
  14:          </div>
  15:      </div>
  16:  </div>
  17:   

这样,我们的页面就准备好了。

TinyFrame升级之八:实现简易插件化开发

然后右击这个插件项目,选择属性,在“生成”标签页面,我们需要把输出路径从"bin\"修改成 “..\TinyFrame.Web\Areas\”,这样做是为了将最新的DLL文件拷贝到主站目录中。

之后在”生成事件“标签页面,在”后期生成事件命令行“下的文本框中,输入以下命令,以便于将样式文件等也拷贝到主站的Areas插件目录中:

   1:  xcopy "$(ProjectDir)\Views" "$(TargetDir)\TinyFrame.Plugin.NivoSlider\Views\" /s /i /y
   2:  xcopy "$(ProjectDir)\Areas" "$(TargetDir)\TinyFrame.Plugin.NivoSlider\Areas\" /s /i /y
   3:  xcopy "$(ProjectDir)\Content" "$(TargetDir)\TinyFrame.Plugin.NivoSlider\Content\" /s /i /y

其中,TinyFrame.Plugin.NivoSlider就是你之前定义的AreaName。

这样,当项目编译完毕之后,会自动把DLL文件,视图文件,样式文件等拷贝到主站的Areas插件目录下:

TinyFrame升级之八:实现简易插件化开发

最后让我们来配置下主站:

首先在主站的Content目录下添加slider文件夹,并将NivoSlider用到的所有CSS文件,JS文件引入,然后利用ActionLink,来链接至这个幻灯片插件:

   1:  <!DOCTYPE html>
   2:   
   3:  <html>
   4:  <head>
   5:      @RenderSection("featured", false)
   6:      <meta name="viewport" content="width=device-width" />
   7:      <title>书籍借阅系统</title>
   8:      <!--EasyUI CSS files-->
   9:      <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/jqueryeasyui/themes/default/easyui.css")" />
  10:      <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/jqueryeasyui/themes/icon.css")" />
  11:   
  12:      <!--BootStrap CSS&JS files-->
  13:      <link href="@Url.Content("~/Content/mycss/default.css")" rel="stylesheet" type="text/css" />
  14:      <link href="@Url.Content("~/Content/bootstrap/css/bootstrap.min.css")" rel="stylesheet" type="text/css" />
  15:      <link href="@Url.Content("~/Content/bootstrap/css/bootstrap-theme.min.css")" rel="stylesheet" type="text/css" />
  16:      <script src="@Url.Content("~/Content/jquery/jquery.min.js")" type="text/javascript"></script>
  17:      <script src="@Url.Content("~/Content/bootstrap/js/bootstrap.min.js")" type="text/javascript"></script>
  18:   
  19:      <!--EasyUI JS files-->
  20:      <script src="@Url.Content("~/Content/jqueryeasyui/jquery.easyui.min.js")" type="text/javascript")"></script>
  21:      <script src="@Url.Content("~/Content/jqueryeasyui/plugins/datagrid-detailview.js")" type="text/javascript")"></script>
  22:      <script src="@Url.Content("~/Content/jqueryeasyui/DataGrid.js")" type="text/javascript"></script>
  23:   
  24:      <!--NivoSlider-->
  25:      <link href="@Url.Content("~/Content/slider/themes/default/default.css")" rel="stylesheet" type="text/css" />
  26:      <link href="@Url.Content("~/Content/slider/nivo-slider.css")" rel="stylesheet" type="text/css" />
  27:      <script src="@Url.Content("~/Content/slider/scripts/jquery.nivo.slider.js")" type="text/javascript"></script>
  28:      <script src="@Url.Content("~/Content/slider/scripts/base.js")" type="text/javascript"></script>
  29:  </head>
  30:  <body>
  31:      <div class="containerMaster">
  32:          <ul class="nav nav-pills" id="menu">
  33:            <li>@Html.ActionLink("书籍入库", "Book", "Home", new { Area = string.Empty }, new { })</li>
  34:            <li>@Html.ActionLink("借入借出", "BookLend", "Home", new { Area = string.Empty }, new { })</li>
  35:            <li>@Html.ActionLink("书籍分类", "BookType", "Home", new { Area = string.Empty }, new { })</li>
  36:            <li>@Html.ActionLink("书籍存放", "BookPlace", "Home", new { Area = string.Empty }, new { })</li>
  37:            <li>@Html.ActionLink("学生管理", "Student", "Home", new { Area = string.Empty }, new { })</li>
  38:            @*<li>@Html.ActionLink("人员管理","Manager","Home")</li>*@
  39:            

<li>@Html.ActionLink("幻灯片插件", "Index", "NivoSlider", new { Area = "TinyFrame.Plugin.NivoSlider" }, new { })</li>

  40:          </ul>
  41:      </div>
  42:      @RenderBody()
  43:  </body>
  44:  </html>

需要注意的是,在上图代码中,着色的部分是访问插件的链接,Area = "TinyFrame.Plugin.NivoSlider"是我们之前定义的AreaName。

运行起来项目,让我们看看结果吧:

TinyFrame升级之八:实现简易插件化开发

这节就讲完了,我们用已有的方法实现了一个简易的插件系统,但是,这个插件系统还不完善,做到商用还是远远不够的,缺点有以下几个:

1.无法进行热插拔,插件的更新需要重启网站。

2.无法检测插件的更新。

而下一章节,我们将会打造一个克服以上缺点的强大易用的插件系统。敬请期待。

以下问题及解决方法是我在开发过程中遇到的,在这里我讲答案贴出,以便于记录及追踪:

1).提示无法找到System.Web.Optimization,是否缺少引用?
    解决方法:
      1.PM> Install-Package Microsoft.AspNet.Web.Optimization 或者 
      2.删掉web.config中的引用

2).“找到多个与名为“Home”的控制器匹配的类型。如果为此请求(“{controller}/{action}/{id}”)提供服务的路由在搜索匹配此请求的控制器时没有指定命名空间,则会发生此情况。如果是这样,请通过调用含有“namespaces”参数的“MapRoute”方法的重载来注册此路由。”
解决方法:
出现该问题的愿原因是在默认的Golbal.asax.cs文件中已经注册了默认路由
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
//new string[] { "Working_with_Areas.Controllers"}
);

}

而在AREAS中有注册了一个同名的Controller

public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
//new string[] { "Working_with_Areas.Areas.Admin.Controllers" }
);
}
解决方法就是在每个注册Router的时候加上命名空间即可。

3).运行的时候,经常无法出现最新编译好的页面。

调试plugin的时候,请先将 输出路径改成  bin\  等调试完毕后,修改成..\TinyFrame.Web\Areas\,否则你调试的页面一直都是Bin目录下的,而非你设置的..\TinyFrame.Web\Areas\ 目录下的。

2014.04.22更新

1.添加了一个强类型的Plugin插件,用于检测强类型程序集是否能够被编译。

TinyFrame升级之八:实现简易插件化开发

2.插件路由新增了controller参数,用于规避某些特定场合下访问出现404的错误。

 public class AreaRegister : AreaRegistration
{ public override string AreaName
{
get
{
return "TinyFrame.Plugin.NivoSlider";
}
} public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"NivoSlider_default",
"NivoSlider/{controller}/{action}/{id}",
new { controller = "NivoSlider", action = "Index", id = UrlParameter.Optional },
new string[] { "TinyFrame.Plugin.NivoSlider.Controllers" }
);
} }

3.修改了生成事件中的后期生成命令行,由于插件中的Content或者是Scripts中的某些图片或者是文件需要拷贝到主站中,所以这里添加了拷贝到主站目录的方法。

TinyFrame升级之八:实现简易插件化开发

最后,提供源代码下载:

百度网盘链接

微云链接

上一篇:Color Me Less


下一篇:大熊君JavaScript插件化开发------(实战篇之DXJ UI ------ ItemSelector)