基于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一类的管理系统,一个通用的后台搭出来,再配合一些快速开发的组件开发效率能提高 ...
随机推荐
- Java学习-009-文件名称及路径获取实例及源代码
此文源码主要为应用 Java 获取文件名称及文件目录的源码及其测试源码.若有不足之处,敬请大神指正,不胜感激!源代码测试通过日期为:2015-2-3 00:02:27,请知悉. Java获取文件名称的 ...
- 打造安全的App!iOS安全系列之 HTTPS
如何打造一个安全的App?这是每一个移动开发者必须面对的问题.在移动App开发领域,开发工程师对于安全方面的考虑普遍比较欠缺,而由于iOS平台的封闭性,遭遇到的安全问题相比于Android来说要少得多 ...
- Python-S13-day2-之购物车
Python-S13-day2 需求: 1.写一个购物小程序,用户开始先输入自己的工资,然后可以不断的买东西并加入购物车,如果钱不够了提示余额不足,用户中途可以选择查看自己购物车里面的商品,以及余额, ...
- 打印出1,11,21,31,41。。。。。。的shell脚本
打印出1,11,21,31,41......的shell脚本 方法一:#!/bin/bash ;i<;i=i+));do echo $i #cat -n /etc/services | sed ...
- MySQL (ZIP Archive) 下载及安装及卸载
下载地址官网: http://www.mysql.com/downloads/ MySQL Enterprise Edition 需要注册账户并且与Oracle公司购买 可以直接下载 MySQL Co ...
- 面向对象编程(十)——继承之Super关键字及内存分析
Super关键字
- system执行shell命令
system - execute a shell command #include <stdlib.h> int system (const char *command); 描述 syst ...
- Java 使用jaxp添加节点
<?xml version="1.0" encoding="UTF-8"?> <person> <p1> <name& ...
- fpm来制作rpm包
转自 http://blog.halfss.com/blog/2013/02/26/fpmbao-guan-li/ 另查看 http://my.oschina.net/lxcong/blog/1438 ...
- SpringJUnit4ClassRunner拉起来的单元测试怎么装配Container实例
由于历史代码的原因,产品中部分spring装配的实例需要通过Container的实现类(自定义的)去获取.那么当在单元测试中怎么实例化这个Container实现呢? 实例化Container实现需要A ...