基于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 == 0)
{
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 = 1, MenuOrder = 1 });
DefineMenuItem(new PluginMenu { MenuText = "相册管理", MenuUrl = "/ManageAlbum", MenuType = 2, MenuOrder = 1 });
}
}
}
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的模块化/插件式架构实现
概述 在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有更好的实用性. 架构 我们采用的是MVC5(本文中介绍的方法对于MVC4也是 ...
-
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一类的管理系统,一个通用的后台搭出来,再配合一些快速开发的组件开发效率能提高 ...
随机推荐
-
Hadoop:输入,输出,key,value格式
map: (K1, V1) → list(K2, V2) reduce: (K2, list(V2)) → list(K3, V3) (K1, V1): jobConf.setInputKeyClas ...
-
完整的社交app源码android+laravel
等想到写点什么的时候再写吧,其他看代码. https://github.com/huijimuhe/monolog-android https://github.com/huijimuhe/monol ...
-
react 基础
一.组件 函数式定义的无状态组件 es5原生方式React.createClass定义的组件 es6形式的extends React.Component定义的组件 React.Component是以E ...
-
vue 从入门到精通(一)
很早之前就想开一系列有关vue的博客,奈何太忙了,哈哈(爱信不信)...刚刚收到消息vue2.5发布了,哎!还是应该加快一下步伐,要不就与社会脱节了.这次采用小步慢跑的形式一点一点总结vue,第一篇先 ...
-
C# Task 是什么?返回值如何实现? Wait如何实现
关于Task的API太多了,网上的实例也很多,现在我们来说说Task究竟是个什么东西[task一般用于多线程,它一定与线程有关],还有它的返回值有事怎么搞的. 首先我们以一个最简单的API开始,Tas ...
-
ubuntu shell插件
1. NetSpeed 在状态栏显示当前网速 2. Screenshot Tool 同样在 Ubuntu 18.04 之前我们使用 Shutter,但在Ubuntu 18.04 Shutter的托盘图 ...
-
[APP] Android 开发笔记 001-环境搭建与命令行创建项目
1. 安装JDK,SDK JDK http://www.oracle.com/technetwork/java/javase/downloads/index.html Android SDK http ...
-
Qt_OpenGL_教程
1. 中文版: Qt OpenGL教程 http://blog.csdn.net/myths_0/article/details/24431597 http://qiliang.net/old/neh ...
-
PL/SQL编程基础(一):PL/SQL语法简介(匿名PL/SQL块)
PL/SQL PL/SQL是Oracle在关系数据库结构化查询语言SQL基础上扩展得到的一种过程化查询语言. SQL与编程语言之间的不同在于,SQL没有变量,SQL没有流程控制(分支,循环).而PL/ ...
-
判断IOS安装后是否是第一次启动
if(![[NSUserDefaults standardUserDefaults] boolForKey:@"firstLaunch"]){ [[NSUserDefaults s ...