基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现
概述
在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有更好的实用性。
架构
我们采用的是MVC5(本文中介绍的方法对于MVC4也是适用的),如下图,解决方案中有四个项目,其中 WeDiscuss 为前端,WeDiscuss.Plugin.Framework 为插件公共类库 WeDiscuss.Plugin.Album 为插件(相册) WeDiscuss.Plugin.News 为插件(新闻),本文只是讲解决插件的实现方式,就不多做其它如果业务逻辑、数据访问层等
注;每个插件都有自已的(M、V、C),内部实现和常用MVC没有区别,这样可以方便的开发,没有其它新知识的引入。

其中,插件层可以在主项目中引用,也可以不引用,或是放到其它目录下(如把插件DLL单独放到“Plugins”目录中),如果不引用就采用在编译完成时复制
下面讲解编译完成复制方法,如想复制到“Plugins”目录中请修改BIN为“Plugins”:
在如下图加入:
copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)Wediscuss\Bin\"

如何让ASP.NET加载BIN目录之外的路径的Assembly
我们把各个模块编译出来的assembly和各个模块的配置文件自动放到一个bin平级的plugin目录,然后web应用启动的时候自动扫描这个plugin目录并加载各个模块plugin,这个怎么做到的?大家也许知道,ASP.NET只允许读取Bin目录下的assbmely,不可以读取其他路径,包括Bin\abc等,即使在web.config这样配置probing也不行:(不信你可以试一下)
<configuration> Element
<runtime> Element
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="bin;plugins;"/>
</assemblyBinding>
</runtime>
</configuration>
如何注入菜单
插件能用了,但也想动态注入菜单,这样才实现了自动化,要不还是人工进行菜单注入永远是半自动化,这和我们开发的思想是不想符的,下面就来说一下菜单的注入
1、首称在WeDiscuss.Plugin.Framework 为插件公共类库中建实体类PluginMenu 和PluginMenus
/*
* -------------------------------------------------------------------------------
* 功能描述:
*
* 创建人: JunHan(俊涵)
* 创建日期: 2013/12/15 21:59:16
* 创建说明:
*
* 修改人:
* 修改日期:
* 修改说明:
*
* -------------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc; namespace WeDiscuss.Plugin.Framework
{
public class PluginMenus
{
public List<PluginMenu> MenuList { get; set; } public string CssClass { get; set; } public int MenuType { get; set; } public string Html
{
get
{
StringBuilder stringBuilder = new StringBuilder();
foreach (var menu in MenuList)
{
TagBuilder tagBuilder = new TagBuilder("a");
tagBuilder.MergeAttribute("href", menu.MenuUrl);
tagBuilder.InnerHtml = menu.MenuText;
tagBuilder.MergeAttribute("class", CssClass);
stringBuilder.Append(tagBuilder.ToString(TagRenderMode.Normal) + "\r\n");
}
return stringBuilder.ToString();
}
} public List<PluginMenu> AvailableList
{
get
{
if (MenuList == null)
{
return new List<PluginMenu>();
}
if (MenuType == )
{
return MenuList;
}
if (!MenuList.Any(o => o.MenuType == MenuType))
{
return new List<PluginMenu>();
}
return MenuList.Where(o => o.MenuType == MenuType).ToList();
}
}
} public class PluginMenu
{
public string MenuText { get; set; }
public string MenuUrl { get; set; } public int MenuType { get; set; } public int MenuOrder { get; set; } public bool Visible { get; set; }
}
}
这样我们就实现了菜单的结构,接下来就是采单的生成或注入方法:
新建 AppPlugin 和 PluginApplication来实现菜单的初使化方法,并将生成好的菜单存放在静态变量中。
/*
* -------------------------------------------------------------------------------
* 功能描述:
*
* 创建人: JunHan(俊涵)
* 创建日期: 2013/12/15 23:29:58
* 创建说明:
*
* 修改人:
* 修改日期:
* 修改说明:
*
* -------------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace WeDiscuss.Plugin.Framework
{
public class PluginApplication : BaseMvcPluginApplication
{
#region Plugin Menu Support
public static PluginMenus PluginMenus = new PluginMenus();
public void RegisterMenuItem(PluginMenu menu)
{
lock (PluginMenus)
{
if (PluginMenus.MenuList == null) PluginMenus.MenuList = new List<PluginMenu>();
PluginMenus.MenuList.Add(new PluginMenu() { MenuText = menu.MenuText, MenuUrl = menu.MenuUrl, MenuType = menu.MenuType, MenuOrder = menu.MenuOrder });
PluginMenus.MenuList = PluginMenus.MenuList.OrderBy(o => o.MenuOrder).ToList();
}
}
#endregion public static new PluginApplication Instance
{
get { return BaseMvcPluginApplication.Instance as PluginApplication; }
set { BaseMvcPluginApplication.Instance = value; }
} protected override bool ShouldIncludeResourceCore(BaseMvcPluginApplication.ResourceTypes type, IMvcPlugin plugin)
{
return ShouldIncludeResource(plugin, null);
} protected virtual bool ShouldIncludeResource(IMvcPlugin plugin, object resource)
{
bool should = true;
if (plugin != null)
{
if ((should = plugin.Enabled) && plugin is AppPlugin)
should = ((AppPlugin)plugin).ShouldIncludeResource(resource);
} return should;
} protected override void AddAdditionalRazorViewLocationsCore(List<string> lst)
{
lst.Add("~/Plugins/PluginDemo/Views.{1}.{0}.cshtml");
} public static PluginApplication SetupApplication(object bundles, object routes)
{
PluginApplication me = new PluginApplication(bundles, routes);
return me;
} protected PluginApplication(object bundles, object routes)
: base(bundles, routes)
{
}
} public class AppPlugin : BaseMvcPlugin
{
public PluginApplication _App { get { return (PluginApplication)App; } } public AppPlugin(bool ensureStandardViewLocation = true)
: base(ensureStandardViewLocation)
{
} public void DefineMenuItem(PluginMenu item)
{
_App.RegisterMenuItem(item);
} public virtual bool ShouldIncludeResource(object content)
{
return true;
}
}
}
2、菜单初使化
在每个插件项目中新建一类,并继承AppPlugin,重写方法:SetupExtensions 调用DefineMenuItem 实现菜单初使化,在菜单的结果中我们看到有MenuType类型,这里我们自定义,一般会用枚举来实现,可以定义为前台或后台等,一个插件可以拥有多个菜单,可以注入多个地方
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WeDiscuss.Plugin.Framework; namespace WeDiscuss.Plugin.Album
{
[Export(typeof(IMvcPlugin))]
[MvcPluginMetadata("AlbumPlugin", null, "Demo App Site Album", "")]
class AlbumPlugin : AppPlugin
{
public override void SetupExtensions(IMvcPluginApplication app)
{
base.SetupExtensions(app);
DefineMenuItem(new PluginMenu { MenuText = "相册", MenuUrl = "/Album", MenuType = , MenuOrder = });
DefineMenuItem(new PluginMenu { MenuText = "相册管理", MenuUrl = "/ManageAlbum", MenuType = , MenuOrder = });
}
}
}
3、菜单调用
在需要出现插件菜单的地方,我们用以下方法实现菜单的注入并呈现
@using WeDiscuss.Plugin.Framework
@{
var menu = PluginApplication.PluginMenus;
menu.MenuType = 1;
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - 我的 ASP.NET 应用程序</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr") </head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("应用程序名称", "Index", "Home", null, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("主页", "Index", "Home")</li>
<li>@Html.ActionLink("关于", "About", "Home")</li>
<li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
@{
foreach (var item in menu.AvailableList)
{
<li><a href="@item.MenuUrl">@item.MenuText</a></li>
}
}
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - 我的 ASP.NET 应用程序</p>
</footer>
</div> @Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
小结
本文主要讲解了实现方法,并没有讲内部结构是如何实现的,以后的分享中我们会慢慢讲解内部实现逻辑和产品化中的应用和配置。大这有问题或是好的建议想法可以发邮件给我 junhan@wediscuss.cn 如果您修改了代码以实现更好的功能,也烦请转发我一份谢谢!
源码下载
我们站在前辈的肩膀上成长,感谢所有帮助WD成长的人.
源码中没有加入packages,请大家自行加载,源码下载 ,没有密码全部开放!
基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现的更多相关文章
- 转发-基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现
基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现 概述 在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有 ...
- ASP.NET MVC:模块化/插件式架构实现(转载)
I’ve recently spent quite a lot of time researching and prototyping different ways to create a plugi ...
- ASP.NET MVC:模块化/插件式文章汇总
方案 Shazwazza | Developing a plugin framework in ASP.NET MVC with medium trust 基于ASP.NET MVC3 Razor的模 ...
- 基于ASP.NET MVC和Bootstrap搭建响应式个人博客站(一)
1.0 为什么要做这个博客站? www.zynblog.com 在工作学习中,经常要搜索查找各种各样的资料,每次找到相关资料后都会顺手添加到浏览器书签中,时间一长,书签也就满了.而且下次再点击这个 ...
- 基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)介绍(二)
基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)介绍(二) 之前文章中给大家说明了下我这个小小的想法,发现还是有不少人的支持和关注.你们的鼓励是对我最大的支持. 我总结了了 ...
- (转)Asp.Net Mvc视图引擎Razor介绍
Asp.Net Mvc视图引擎Razor介绍 1.Razor介绍 程序园原创,转载请注明:http://www.kwstu.com/ArticleView/dabaomvc_2014082408205 ...
- 基于ASP.NET MVC的快速开发平台,给你的开发一个加速度!
基于ASP.NET MVC的快速开发平台,给你的开发一个加速度! bingo炸了 2017/4/6 11:07:21 阅读(37) 评论(0) 现在的人做事情都讲究效率,最好能达到事半功倍那种效果,软 ...
- GPS部标平台的架构设计(十)-基于Asp.NET MVC构建GPS部标平台
在当前很多的GPS平台当中,有很多是基于asp.NET+siverlight开发的遗留项目,代码混乱而又难以维护,各种耦合和关联,要命的是界面也没见到比Javascript做的控件有多好看,随着需求的 ...
- 如何提高码农产量,基于ASP.NET MVC的敏捷开发框架开发随笔一
公司业务量比较大,接了很多项目,为了缩短开发周期老板让我牵头搭建了一个敏捷开发框架. 我们主要的业务是做OA.CRM.ERP一类的管理系统,一个通用的后台搭出来,再配合一些快速开发的组件开发效率能提高 ...
随机推荐
- linux i2c tools
最近要操作eeprom,所以了解一下i2c-tool的使用方法,记录于此. 参考链接: http://www.myir-tech.com/bbs/thread-7567-1-1.html http:/ ...
- qt QString 与 int,char的转换
每次QString转换int或者char的时候都要查资料,记录一下,方便下次查看. 参考: http://blog.csdn.net/ei__nino/article/details/7297791 ...
- raspberry 烧写镜像到SD卡
刚开始接触树莓派,需要烧写系统镜像到SD卡,网上找了些资料,记录于此. raspberry镜像下载: https://www.raspberrypi.org/downloads/ 烧写工具: wid3 ...
- 将一个UIView对象的内容保存为UIImage
+ (UIImage*)imageFromView:(UIView*)view{ UIGraphicsBeginImageContextWithOptions(view.bounds.size, YE ...
- AES对称加密算法原理
原著:James McCaffrey 翻译:小刀人 原文出处:MSDN Magazine November 2003 (Encrypt It) 本文的代码下载:msdnmag200311AES.exe ...
- Linux信号处理
给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取.这些信息包括子进程的进程ID.终止状态以及资源利用信息(CPU时间,内存使用量等等).如果一个进程终止,而该进程有子进程处于僵 ...
- 禁用LMHOSTS和NetBIOS后提升上网速度 ?
LMHOSTS 文件是 windows 中进行 netbios 静态解析时使用的,其作用类型于 HOSTS 文件. 今天发现这个东西有点问题,在用ADSL上网时,明明 IP . DNS 都设置得好好的 ...
- sql server 还原数据库时提示数据库正在使用,无法进行操作的解决方法
这个问题的原因在于有用户连接了当前要做还原的数据库,这里的用户甚至包括当前要做还原的用户.解决办法就是关闭与要还原数据库的所有连接. 脚本之家小编推荐的一个方法:到服务里面重启下sqlserver服务 ...
- Java Volatile关键字
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写. 这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量 ...
- 仿Smarty替换模板标签时遇到的问题
最近正在做一个微型的仿TP框架,当然以鄙人之技术只能略仿表层,于是遇到的问题层出不穷.今天做到View层替换模板部分,本以为一下子搞掂的事,果不其然又是败下阵来. 好了,来重点. 模板文件 test1 ...