• 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="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:   

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

    QQ截图20140421225046

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

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

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

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

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

    QQ截图20140421225631

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

    首先在主站的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。

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

    QQ截图20140421230153

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

    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.WebAreas,否则你调试的页面一直都是Bin目录下的,而非你设置的..TinyFrame.WebAreas 目录下的。

    2014.04.22更新

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

    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中的某些图片或者是文件需要拷贝到主站中,所以这里添加了拷贝到主站目录的方法。

     

    最后,提供源代码下载:

    百度网盘链接

    微云链接

  • 相关阅读:
    CSS选择器
    CSS的语法规范
    CSS简介
    spring 工厂模式解耦的升级版(多例转单例)
    Html label标签
    Java 多线程入门详解
    Html 表单
    Html 列表
    web项目的建立(idea版本)
    工厂模式详解
  • 原文地址:https://www.cnblogs.com/scy251147/p/3679474.html
Copyright © 2020-2023  润新知