基于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>&copy; @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的模块化/插件式架构实现的更多相关文章

  1. 基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现

    概述 在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有更好的实用性. 架构 我们采用的是MVC5(本文中介绍的方法对于MVC4也是 ...

  2. ASP.NET MVC:模块化/插件式架构实现(转载)

    I’ve recently spent quite a lot of time researching and prototyping different ways to create a plugi ...

  3. ASP.NET MVC:模块化/插件式文章汇总

    方案 Shazwazza | Developing a plugin framework in ASP.NET MVC with medium trust 基于ASP.NET MVC3 Razor的模 ...

  4. 基于ASP.NET MVC和Bootstrap搭建响应式个人博客站(一)

    1.0 为什么要做这个博客站? www.zynblog.com   在工作学习中,经常要搜索查找各种各样的资料,每次找到相关资料后都会顺手添加到浏览器书签中,时间一长,书签也就满了.而且下次再点击这个 ...

  5. 基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)介绍(二)

    基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)介绍(二) 之前文章中给大家说明了下我这个小小的想法,发现还是有不少人的支持和关注.你们的鼓励是对我最大的支持. 我总结了了 ...

  6. (转)Asp.Net Mvc视图引擎Razor介绍

    Asp.Net Mvc视图引擎Razor介绍 1.Razor介绍 程序园原创,转载请注明:http://www.kwstu.com/ArticleView/dabaomvc_2014082408205 ...

  7. 基于ASP.NET MVC的快速开发平台,给你的开发一个加速度!

    基于ASP.NET MVC的快速开发平台,给你的开发一个加速度! bingo炸了 2017/4/6 11:07:21 阅读(37) 评论(0) 现在的人做事情都讲究效率,最好能达到事半功倍那种效果,软 ...

  8. GPS部标平台的架构设计(十)-基于Asp.NET MVC构建GPS部标平台

    在当前很多的GPS平台当中,有很多是基于asp.NET+siverlight开发的遗留项目,代码混乱而又难以维护,各种耦合和关联,要命的是界面也没见到比Javascript做的控件有多好看,随着需求的 ...

  9. 如何提高码农产量,基于ASP.NET MVC的敏捷开发框架开发随笔一

    公司业务量比较大,接了很多项目,为了缩短开发周期老板让我牵头搭建了一个敏捷开发框架. 我们主要的业务是做OA.CRM.ERP一类的管理系统,一个通用的后台搭出来,再配合一些快速开发的组件开发效率能提高 ...

随机推荐

  1. 7-6 Bandwidth UVA140

    没有清空向量导致debug了好久 这题难以下手  不知道怎么dfs 原来是用排序函数. letter[n]=i; id[i]=n++; 用来储存与设置标记十分巧妙 for(;;) { while(s[ ...

  2. def 函数的不定长函数

    *args 不定长函数传值可直接传,也可以拆包传,参数默认为元组: 参数为字典的时候:

  3. Eclipse添加git插件上传项目到github

    前提: 在Github已经注册成功自己的账号 新建一个仓库 创建成功后记住url: 首先像安装Pydev一样 点击help的Install New Software 点击Add后添加链接http:// ...

  4. C++雾中风景番外篇2:Gtest 与 Gmock,聊聊C++的单元测试

    正式工作之后,公司对于单元测试要求比较严格.(笔者之前比较懒,一般很少写完整的单测~~).作为一个合格的开发工程师,需要为所编写代码编写适量的单元测试是十分必要的,在实际进行的开发工作之中,TDD(T ...

  5. Flutter常用组件(Widget)解析-Text

    单一格式的文本. 文本组件是以字符串形式显示的单一格式,这个文本字符串可以是多行显示也可以是单独一行显示,主要取决于你的布局限制. 这样式内容是可选择的,如果你省略了,则会使用文本的默认样式来显示.如 ...

  6. 一道有意思的多线程面试题 C# 代码实现

    如果你对多线程的控制不怎么了解,那么理解了这篇文章的内容也许对你有帮助.鼓励先自己动手实现一遍,做不出来在看代码. 题目一:两个线程交替打印0~100的奇偶数 这道题就是说有两个线程,一个名为偶数线程 ...

  7. 一次线上FullGC问题记录

    ​ 标题采自:英雄联盟-瑞文:断剑重铸之日,骑士归来之时! 断剑 ​ 前两天早上在挤地铁的时候看到小组群里,主管发了好多消息,打开来一看,说是XX项目自从22号发版后,每天晚上就疯狂Full GC,让 ...

  8. sql注入总结(一)--2018自我整理

    SQL注入总结 前言: 本文和之后的总结都是进行总结,详细实现过程细节可能不会写出来~ 所有sql语句均是mysql数据库的,其他数据库可能有些函数不同,但是方法大致相同 0x00 SQL注入原理: ...

  9. Java中十个常见的违规编码

    摘要:作者Veera Sundar在清理代码工作时发现一些常见的违规编码,因此,Veera Sundar把针对常见的一些违规编码总结成一份列表,以便帮助Java爱好者提高代码的质量和可维护性. 最近, ...

  10. vue+ElementUI使用笔记

    1,使用表单验证: //定义验证规则 window.varifyUtil = { //验证数字 validateNumber: function(rule, value, callback){ if ...