最近在研究OSGI.NET插件式开发框架。官方网站提供了一个基于OSGI.NET的插件仓库。下载官方的SDK包安装后VS项目模板会多出一组iOpenWorks项目模板。在学习过程中,发现通过iOpenWorks模板创建的应用程序主程序都会附加上iOpenWorks插件仓库相关的插件。如何使用OSGI.NET框架库来创建纯净的插件框架,来实现自己的IDE呢?

  基于WinForm、WPF的主程序很简单。这里主要介绍下如何来实现基于MVC的插件式框架。

  主要思路:通过MVC中的Area方式来实现对插件页面的访问,一个子项目为一个Area。

  需要解决的问题:

    1.基于OSGI.NET插件的框架,实现的物理隔离,运行时不会将子项目的dll文件复制到主程序的bin目录下。

    2.MVC中Area下的controller创建是根据命名空间结构来查找controller的,如何将Area下的controller对应到子项目的程序集中。

    3.Controller如何正确的加载cshtml页面。

    4.子项目中的View文件中如何访问资源文件。

  接下来我们来逐个解决以上的问题。(关于OSGI.NET,可以在官网上看下视频教程,这里就不介绍了)

1.实现MVC主程序对插件MVC项目下的ControllerAction的访问

  新建空的解决方案:Osgi.Net.Mvc

  在解决方案下新建MVC4项目,命名为Osgi.Net.Mvc.Web,选择空模板,选择Razor视图引擎

  在新建的MVC4项目下添加项目目录Plugins

  在解决方案中添加新的MVC4项目,命名为About,选择空模板,选择Razor视图引擎,将About项目的路径,保存到Osgi.Net.Mvc.Web项目的Plugins目录下

  删除Global.asax文件和App_Start文件夹,并添加Manifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="About" SymbolicName="About" Version="1.0.0.0" InitializedState="Active">
<Runtime>
<Assembly Path="bin\About.dll" Share="false" MultipleVersions="false" />
</Runtime>
</Bundle>

  建成后的空项目如下:

  在About下添加控制器Hello创建Index方法。

public class HelloController : Controller
{
public ActionResult Index()
{
return Content("About Plugin.");
}
}

  

  接下来我们来编写代码,在Application启动前启动插件运行时BundleRuntime。

  在Global.asax的MvcApplication方法中加入静态方法,并实现BundleRuntime的启动。

public static void BundleStart()
{
var runtime = new BundleRuntime();
runtime.Start();
}

  

  在命名空间上注册PreApplicationStartMethod,让方法在Application前启动。

[assembly: PreApplicationStartMethod(typeof(MvcApplication), "BundleStart")]
namespace Osgi.Net.Mvc.Web
{
//......
}

  

  此时,插件加载已完成。启动应用程序,访问/hello/index页面返回404.

  如何能让主程序能访问插件中的controller呢?

  我们知道,MVC的动态编译是通过System.Web.Compilation.BulidManager来管理程序集的。

  我们尝试将插件的程序集加入到BulidManager管理的程序集中。

  在BundleStart()方法中,BundleRuntime启动后加入代码  

foreach (var bundle in runtime.Framework.Bundles)
{
var bundleData = runtime.GetFirstOrDefaultService<IBundleInstallerService>()
.GetBundleDataByName(bundle.SymbolicName);
if (bundleData == null) continue; var serviceContainer = runtime.Framework.ServiceContainer;
var service = serviceContainer.GetFirstOrDefaultService<IRuntimeService>();
var assemlbies = service.LoadBundleAssembly(bundle.SymbolicName);
assemlbies.ForEach(BuildManager.AddReferencedAssembly);
}

  

  重新启动应用程序,访问/hello/index页面,这时已经能够看到从About中的HelloController返回的数据了。

2.实现通过Area的方式来访问插件

  要实现通过Area的方式来访问插件,就需要修改通过Area来查找Controller的方式

  首先给About插件添加Area注册文件AboutAreaRegistration.cs  

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

  

  然后创建一个BundleAreaControllerFactroy类来重载DefaultControllerFactory  

public class BundleAreaControllerFactroy : DefaultControllerFactory
{
}

    

  重载GetControllerType方法

    1.拦截上下文中的Area信息

    2.根据Area信息加载对应的Bundle信息

    3.在Bundle中查找对应的contrller

    4.如果未找到对应controller,将上下文转交给基类来处理

protected override Type GetControllerType(RequestContext requestContext, string controllerName)
{
string symbolicName = null;
object area;
if (requestContext.RouteData.DataTokens.TryGetValue("area", out area))
{
symbolicName = area as string;
}
else
{
var routeWithArea = requestContext.RouteData.Route as IRouteWithArea;
if (routeWithArea != null)
{
symbolicName = routeWithArea.Area;
}
var castRoute = requestContext.RouteData.Route as Route;
if (castRoute != null && castRoute.DataTokens != null)
{
symbolicName = castRoute.DataTokens["area"] as string;
}
} if (symbolicName != null)
{
var controllerTypeName = controllerName + "Controller";
var runtimeService = BundleRuntime.Instance.GetFirstOrDefaultService<IRuntimeService>();
var assemblies = runtimeService.LoadBundleAssembly(symbolicName); foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes())
{
if (type.Name.ToLower().Contains(controllerTypeName.ToLower())
&& typeof(IController).IsAssignableFrom(type))
{
return type;
}
}
}
} return base.GetControllerType(requestContext, controllerName);
}

    

   在Global.asax的Application_Start中注册ControllerFactory

ControllerBuilder.Current.SetControllerFactory(new BundleAreaControllerFactroy());

     

  到这里已经完成了通过Area访问插件的功能。启动应用程序,通过/about/hello/index访问页面,这时就能够看到刚才的页面了。

3.实现能够正确加载插件路径下cshtml文件的视图引擎

  在RazorViewEngine对象中提供了一组视图路径模板:AreaViewLocationFormats、AreaMasterLocationFormats、AreaPartialViewLocationFormats、ViewLocationFormats、MasterLocationFormats、PartialViewLocationFormats。我们可以通过修改路径模板来实现对插件路径下的cshtml文件的正确加载。

  首先创建BundleRazorViewEngine对象来重载RazorViewEngine

public class BundleRazorViewEngine : RazorViewEngine
{
}

  

  在构造函数中我们首先将基类中的路径模板存储下来

public class BundleRazorViewEngine : RazorViewEngine
{
private readonly string[] _baseAreaViewLocationFormats;
private readonly string[] _baseAreaMasterLocationFormats;
private readonly string[] _baseAreaPartialViewLocationFormats;
private readonly string[] _baseViewLocationFormats;
private readonly string[] _baseMasterLocationFormats;
private readonly string[] _basePartialViewLocationFormats; public BundleRazorViewEngine()
{
_baseAreaViewLocationFormats = AreaViewLocationFormats;
_baseAreaMasterLocationFormats = AreaMasterLocationFormats;
_baseAreaPartialViewLocationFormats = AreaPartialViewLocationFormats;
_baseViewLocationFormats = ViewLocationFormats;
_baseMasterLocationFormats = MasterLocationFormats;
_basePartialViewLocationFormats = PartialViewLocationFormats;
}
}

    

  然后重载FindView方法,在FindView中

    1.从上下文中查找Area信息

    2.根据Area信息加载插件

    3.根据插件路径信息修改路径模板

    4.执行基类的FindWiew来返回ViewEngineResult

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName,
string masterName, bool useCache)
{
string symbolicName = null;
object area;
if (controllerContext.RouteData.DataTokens.TryGetValue("area", out area))
{
symbolicName = area as string;
}
else
{
var routeWithArea = controllerContext.RouteData.Route as IRouteWithArea;
if (routeWithArea != null)
{
symbolicName = routeWithArea.Area;
}
var castRoute = controllerContext.RouteData.Route as Route;
if (castRoute != null && castRoute.DataTokens != null)
{
symbolicName = castRoute.DataTokens["area"] as string;
}
} if (string.IsNullOrEmpty(symbolicName)) return new ViewEngineResult(new string[0]); var bundle = BundleRuntime.Instance.Framework.GetBundleBySymbolicName(symbolicName);
if (bundle == null) return new ViewEngineResult(new string[0]); SetLocationFormats(@"~\" + bundle.Location.Replace(HostingEnvironment.ApplicationPhysicalPath, String.Empty));
return base.FindView(controllerContext, viewName, masterName, useCache);
} private void SetLocationFormats(string bundleLocation)
{
AreaViewLocationFormats = _baseAreaViewLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
AreaMasterLocationFormats = _baseAreaMasterLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
AreaPartialViewLocationFormats = _baseAreaPartialViewLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
ViewLocationFormats = _baseViewLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
MasterLocationFormats = _baseMasterLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
PartialViewLocationFormats = _basePartialViewLocationFormats
.Select(item => item.Replace("~", bundleLocation)).ToArray();
}

    

  重载FindPartialView方法,方式与重载FindView相同,这里就不贴代码了。

  在Global.asax的Application_Start中注册BundleRazorViewEngine

ViewEngines.Engines.Add(new BundleRazorViewEngine());

    

  然后在About的HelloController中添加一个方法,返回ViewResult,在cshtml页面中添加一些文字来测试一下吧。

  关于资源文件的访问,下次在说。

基于OSGI.NET的MVC插件式开发的更多相关文章

  1. MVC 插件式开发

    MVC 插件式开发 在开发一个OA系统是,我们可能遇到 A模块. B模块 .C模块,这也模块组成一个完整的系统,买给客服.现在又有一个客服要我们做一个OA系统,唉我们发现,跟上一个OA系统差不多,但没 ...

  2. 零基础ASP.NET Core MVC插件式开发

    零基础ASP.NET Core MVC插件式开发 一个项目随着业务模块的不断增加,系统会越来越庞大.如果参与开发的人员越多,管理起来也难度也很大.面对这样的情况,首先想到的是模块化插件式开发,根据业务 ...

  3. MVC插件式开发平台

    ---恢复内容开始--- 经过DyOS.BraveOS1.0再到BraveOS2.0,系统现在已经开发了下载. 我们的目标是,网页版操作系统,可以在线安装更新软件,并提供二次开发平台,提供基础的逻辑和 ...

  4. 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

    标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...

  5. 从零开始实现ASP.NET Core MVC的插件式开发(九) - 升级.NET 5及启用预编译视图

    标题:从零开始实现ASP.NET Core MVC的插件式开发(九) - 如何启用预编译视图 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1399 ...

  6. .NET MVC 简单的插件式开发

    插件式开发的优势 1.提高软件的复用度 2.提高软件开发的并行性 3.缩短软件的研发周期.节约研发成本,带给程序开发人员更多的灵活性,产品在软件发布以后还可以添加新的插件和完善已有的功能. 4.方便软 ...

  7. 基于AppDomain的"插件式"开发

    很多时候,我们都想使用(开发)USB式(热插拔)的应用,例如,开发一个WinForm应用,并且这个WinForm应用能允许开发人员定制扩展插件,又例如,我们可能维护着一个WinService管理系统, ...

  8. 从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图

    标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...

  9. 从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板

    标题:从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11155 ...

随机推荐

  1. Grooming Meeting及测试人员所扮演的角色

    Grooming Meeting的中文翻译是“梳理会议”,它并不是Scrum框架中标准的会议(标准会议为Planning Meeting, Daily Scrum Meeting, Review Me ...

  2. linux 更改文件夹所有者

    更改“tp5”文件的所有者为”www” chown -R tp5/ www 修改目录及其子目录的用户组为“www” chgrp -R www tp5 同时更改文件或目录的所有者和用户组 chown - ...

  3. 复杂HTML解析

    面对页面解析难题时候,需要注意问题: 1.寻找“打印次页”的链接,或者看看网站有没有HTML样式更友好的移动版(把自己的请求头设置成处于移动设备的状态,然后接收网站移动版). 2.寻找隐藏在JavaS ...

  4. 使用sqlmap中tamper脚本绕过waf

    使用sqlmap中tamper脚本绕过waf 刘海哥 · 2015/02/02 11:26 0x00 背景 sqlmap中的tamper脚本来对目标进行更高效的攻击. 由于乌云知识库少了sqlmap- ...

  5. [转]C++赋值运算符重载函数(operator=)

    写在前面: 关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面.面对这一局面,在下在整合各种资源及融入个人理解的基础上,整理出一篇较 ...

  6. 关于python中的module

    python中的module(模块),关于这个概念以及使用时主要有以下几点需要注意: (1)import xx时,会首先将这个xx module中的代码执行一遍(且仅执行一遍): 例如: (2)模块包 ...

  7. ubuntu16.04+caffe+python接口配置

    在Windows上用了一个学期的caffe了.深感各种不便,于是乎这几天在ubuntu上配置了caffe和它的python接口,现在记录配置过程,亲测可用: 环境:ubuntu16.04 , caff ...

  8. linux 查看cpu的使用百分比

    先安装 sudo apt-get install sysstat 然后: mpstat -u 2 5

  9. ES系列六、ES字段类型及ES内置analyzer分析

    一.背景知识 在Es中,字段的类型很关键: 在索引的时候,如果字段第一次出现,会自动识别某个类型,这种规则之前已经讲过了. 那么如果一个字段已经存在了,并且设置为某个类型.再来一条数据,字段的数据不与 ...

  10. java中不同类型的数值占用字节数

    在Java中一共有8种基本数据类型,其中有4种整型,2种浮点类型,1种用于表示Unicode编码的字符单元的字符类型和1种用于表示真值的boolean类型.(一个字节等于8个bit) 1.整型 类型 ...