本文内容基于
cjnmy36723 http://www.cnblogs.com/cjnmy36723/p/3405269.htmlBrnShop(其实他是借chao鉴nopcommerce的)
下文中会提及它们是如何解决问题,在这之前给他们取个小名
cjnmy36723 方案一
BrnShop 方案二
本文不讲代码细节
什么MVC插件
目前我所理解的MVC插件是
- 可独立运行的
- 容易部署的
- 容易删除的
我们所想象的插件,是只要把插件文件复制到指定位置,并修改配置就能运行的.
那么就先约定一个Plugins目录来放插件
平时访问MVC网站经过的流程大致为:
Url被路由获取-->路由匹配-->找到相关控制器-->控制器找到相关视图
开发一个插件,并让站点使用会遇到很多坑
那么就从视图,控制器,路由方面 开始解决它们.
控制器问题
如何让网站不仅能拿到网站本身的控制器类,同时能拿到插件的控制器类
那么就要了解下控制器如何被创造出来的
推荐看artech的博客
这里只说相关部分的
通常控制器是被一个控制器工厂类(DefaultControllerFactory)创建出来的
这个类实现了IControllerFactory接口
该接口有三个方法
//使用指定的请求上下文来创建指定的控制器。 IController CreateController(RequestContext requestContext, string controllerName); //获取控制器的会话行为。 SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); //释放指定的控制器 void ReleaseController(IController controller);
获得控制器的关键就是
CreateController方法
只要修改这个方法,让他不仅仅能获得网站自己的控制器类,同时能获得插件的控制器类
当然我们修改的话,并不需要自己实现IControllerFactory创建自己的控制器工厂类,我们可以重写DefaultControllerFactory相关的方法
DefaultControllerFactory除了能重写IControllerFactory的三个方法外还能重写
-
protected internal virtual TypeGetControllerType(RequestContext requestContext,string controllerName)
为了达到我们的目的可以重写这个方法,因为根据源码,CreateController创建实例需要Type,即会用到GetControllerType方法
最简单的处理:可以重写这个方法,让它反射程序集,创建控制器
下面介绍下两个案例是如何处理控制器的
部署程序集
显然无论哪种方法都需要加载程序集,这里需要注意的是两种方案都把程序集复制到另一个文件夹下
方案一
复制到 ~/App_Data/Dependencies
方案二
它略微复杂点,它尝试把插件的dll复制到运行时文件夹
临时文件夹大概在C:\Users\用户名\AppData\Local\Temp\Temporary ASP.NET Files
这里就涉及到了权限问题
在BrnShop的BrnMall.Core.BMAPlugin.DeployDllFile方法中它判断应用程序的信任级别,如果是最高等级的信任级别就把插件dll复制到运行时文件夹.如果不是则复制到/plugins/bin.
之所以要复制程序集,因为使用Assembly.LoadFrom方法后,将把该程序集锁定,那么久不能移动,删除程序集了
这里我无意间在网上看到了一个解决方法.把dll以字节的形式读取到内存,再生成Assembly,这样似乎锁程序集
但我仍然有个疑惑,为什么BrnShop要尝试把程序集复制到运行时文件夹?归在一个文件夹不好吗?求解答
使用程序集
部署好程序集后,就要想办法,让网站能用到这个程序集
方案一
方案二
BrnShop并不是像上面那样运行时反射加载程序集并保存起来用
确切的说他是运行前加载程序集并添加引用
在它的BrnMall.Core.BMAPlugin.DeployDllFile 方法中,它读取插件程序集并使用BuildManager.AddReferencedAssembly添加程序集引用.
这里有个知识点:CLR是如何搜索dll的?
相关资料:http://www.cnblogs.com/Charles2008/archive/2010/07/02/Assembly_Search.html
这里我说下相关部分
CLR会按照下面的顺序搜索程序集,但提前这个程序集要被引用,只有被引用的程序集才会出现在程序集清单中,CLR搜索程序集就是靠清单上的信息
1、 在GAC(Global Assembly Cache)中搜索相应版本的DLL
2、 配置文件(web.config或app.config)中
<codebaseversion=”AssemblyVersion" href=”URL of assembly” />
3、 应用程序当前目录下
4、 配置文件(web.config或app.config)
<probingprivatePath=”Paths”/>
所以Brnshop不仅动态添加了引用,同时设置了<probing>节点
-
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <!--插件dll文件影子目录,在Medium及其以下的信任级别时使用--> <probing privatePath="plugins/bin/"/> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/> <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0"/> </dependentAssembly> </assemblyBinding> </runtime>
这样做后,网站就能想找自己控制器一样轻松的找到插件的控制器
存在问题,使用这种方法,如果在网站运行时添加插件,毕竟运行的情况下是不同添加引用的
视图问题
这里要了解想MVC的Razor视图是如何被编译出来的
推荐看artech的MVC视图系列文章
http://www.cnblogs.com/artech/archive/2012/09/04/razor-view-engine-01.html
这里也只说相关部分的
找视图
视图引擎RazorViewEngine的爷爷
VirtualPathProviderViewEngine(抽象类)
它内部有一些数组用于保存视图的路径
-
public string[] AreaMasterLocationFormats{ get; set; } public string[] AreaPartialViewLocationFormats{ get; set; } public string[] AreaViewLocationFormats{ get; set; } public string[] FileExtensions{ get; set; } public string[] MasterLocationFormats{ get; set; } public string[] PartialViewLocationFormats{ get; set; } public IViewLocationCache ViewLocationCache{ get; set; } public string[] ViewLocationFormats{ get; set; }
显然根据名字就知道,它们是哪些视图的路径
它们在RazorViewEngine中初始化,下面给出一部分的代码
-
public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator) { //省略.... base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; //省略... }
上面的路径仅仅是个模板,真正的路径将在运行时候根据路由请求被修改
这部分操作被写在 VirtualPathProviderViewEngine中的
-
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { ... }
易知,这个方法可以被重写,那么我们可以修改FindView方法和ViewLocationFormats等数组,让视图引擎能找到插件的视图
编译视图
如果你看过artech的文章,应该明白,视图(.cshtml)将在运行的时候编译成一个类保存在运行时文件夹
临时文件夹大概在
C:\Users\用户名\AppData\Local\Temp\Temporary ASP.NET Files
那么这里就出现了一个 坑
由于是把视图文件(.cshtml)编译成一个dll,那么当这个视图是模型视图,也就是用到了自己创建的Model类时候该去哪个dll找我的Model?
通常情况下,我写的Model都跟我的MVC网站编译成一个dll
比如
我的网站将编译成一个PluginMvcWeb.dll保存在bin下,运行时也会保存在临时文件夹下.
方案一
他也设置了<probing>节点,告诉CLR去哪里搜索dll
可能有人问,这个视图类还没引用程序集,我写到这也惊得菊花一紧.然而神奇的是这视图类会自动添加引用
方案二
它是写死视图的路径(毕竟是约定大于配置的项目),同样也设置了<probing>节点
路由
方案一
每个插件加载时候都会调用一个初始化函数,在这个函数中每个插件都会注册一条路由
方案二
额.......,它似乎并没改动什么,它是约定大于配置的,主网站默认的路由配置已经能够匹配到插件的位置
未完待撸...