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:
这样,我们的页面就准备好了。
然后右击这个插件项目,选择属性,在“生成”标签页面,我们需要把输出路径从"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插件目录下:
最后让我们来配置下主站:
首先在主站的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。
运行起来项目,让我们看看结果吧:
这节就讲完了,我们用已有的方法实现了一个简易的插件系统,但是,这个插件系统还不完善,做到商用还是远远不够的,缺点有以下几个:
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插件,用于检测强类型程序集是否能够被编译。
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升级之八:实现简易插件化开发的更多相关文章
- Android插件化开发
客户端开发给人的印象往往是小巧,快速奔跑.但随着产品的发展,目前产生了大量的门户型客户端.功能模块持续集成,开发人员迅速增长.不同的开发小组开发不同的功能模块,甚至还有其他客户端集成进入.能做到功能模 ...
- 【我的Android进阶之旅】Android插件化开发学习资料
1.目前开源的插件开发框架大致有哪些? 1. 任玉刚 的 dynamic-load-apk Github 地址:https://github.com/singwhatiwanna/dynamic-lo ...
- Android 插件化开发(四):插件化实现方案
在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...
- Android组件化和插件化开发
http://www.cnblogs.com/android-blogs/p/5703355.html 什么是组件化和插件化? 组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Modul ...
- Android 使用动态载入框架DL进行插件化开发
如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456 (来自时之沙的csdn博客) 概述: 随着应用的不断迭代.应用的体积不断增大,项目越来越臃肿,冗余添 ...
- Android插件化开发---执行未安装apk中的Service
欢迎各位增加我的Android开发群[257053751] 假设你还不知道什么叫插件化开发.那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从总体角度分析了一下 ...
- NET 平台下的插件化开发内核
.NET 平台下的插件化开发内核(Rabbit Kernel) 每个程序猿都有一个框架梦,曾经在2013年8月15日写过一篇“Koala Framework是什么?我为什么要写这个框架?”的文章, ...
- JavaScript插件化开发
大熊君JavaScript插件化开发 一,开篇分析 Hi,大家好!大熊君又和大家见面了,还记得昨天的那篇文章吗------这个系列的开篇(第一季).主要讲述了以“jQuery的方式如何开发插件”, 那 ...
- 插件化开发—动态加载技术加载已安装和未安装的apk
首先引入一个概念,动态加载技术是什么?为什么要引入动态加载?它有什么好处呢?首先要明白这几个问题,我们先从 应用程序入手,大家都知道在Android App中,一个应用程序dex文件的方法数最大不能超 ...
随机推荐
- HTML5离线存储原理
找到一篇介绍离线缓存的,感觉比之前看到的解释的更透彻,新的知识点记录如下: 大家都知道Web App是通过浏览器来访问的,所以离线状态下是无法使用app的.其中web app中的一些资源并不经常改变, ...
- jQuery 更改checkbox的状态,无效
今天写页面遇到复选框动态全选或全不选问题,正常写法如下: $("#tb").find("input[type='checkbox']").attr(" ...
- 记录一些在用wcf的过程中走过的泥巴路 【第一篇】
自从转移战场之后,比以前忙多了,博客也没能及时跟上,原本准备继续mvc系列,但是在那边技术比较陈旧还没能用得上,话说有3年没接触这玩意了,东西也 都忘了差不多了,既然再次接触,我也就继续温习温习,记录 ...
- SQL Server:字符串函数
以下所有例子均Studnet表为例: 1. len():计算字符串长度 len()用来计算字符串的长度,每个中文汉字或英文字母都为一个长度 select sname, len(sname) from ...
- 让HTML页面缩放适应移动客户端尺寸
多的不说了,直接看代码吧 <html lang="en"> <head> <meta http-equiv = "X-UA-Compatib ...
- 仿哔哩哔哩应用客户端Android版源码项目
这是一款高仿哔哩哔哩安卓客户端,跟官方网的差不多吧,界面也几乎是一样的,应用里面也加了一些弹出广告,大家可以参考一下吧,安装测试包在源码文件那里,大家可以多多参考一下. 哔哩哔哩弹幕网是国内知名的弹幕 ...
- spring为什么不能注入static变量
Spring 依赖注入 是依赖 set方法 set方法是 是普通的对象方法 static变量是类的属性 @Autowired private static JdbcTemplate jdbcTempl ...
- C中的数组与指针问题
反复在数组名与指针上犯错误,特记录下. ,,,,}; int *p, *q; p = (); q = (); *(p+1)? *(q-1) ? 答案是 3, 5.这里主要涉及的问题就是指针参与运算 ...
- BI软件搞不定业务管理报表的需求
BI是商业智能的缩写,是可以帮助企业做出明智的业务经营决策的工具,其数据来源于各个业务系统,如ERP.CRM.SCM.进销存.HER.OA等. BI系统不同于传统的管理信息系统,他号称是一个整体应用的 ...
- [转]How to add new table in NopCommerce
本文转自:http://www.tech-coder.com/2015/07/how-to-add-new-table-in-nopcommerce.html Hey guys I am back a ...