概述

  在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有更好的实用性。

架构

  我们采用的是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>&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的模块化/插件式架构实现

    基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现   概述 在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有 ...

  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. JSON.stringify初识

    1.JSON.stringify()简介: JSON.stringify()这个函数是用来序列化对象的,即是把对象类型转换成json类型. 它有三个参数,即JSON.stringify(value [ ...

  2. sell-- 英文网站产品显示404?

    1. 简介: 通过在主页(header.jsp)查询B22212,在localhost本地, cn和us查询的结果search.jsp中显示都是没有找到! 但是在外网(www),cn能够查询到,并展示 ...

  3. 《JAVA NIO》第二章缓冲区

    1.缓冲区的构成 2.缓冲区的类图 3.ByteBuffer ByteBuffer是直接和Channel打交道的,准确的是直接字节缓冲. 问题:直接字节缓冲区和内存映射的关系 4.ByteOrder ...

  4. Java: Class Variable/Static Variable

    1. Class Variable/Static Variable: Class variable is also known as static variable with "static ...

  5. box_shadow

    .tip{width:485px; height:260px; position:absolute;top:10%; left:30%;background:#fcfdfd; box-shadow:1 ...

  6. 1.Oracle数据库概述

    Oracle数据库概述 1.1Oracle结构图 1.1如何访问数据库 a.本机直接通过sock(套接字)方式访问  IPC UDP协议 ,不需要网络 b.通过tcp建立连接到oracle服务器 1. ...

  7. Unicode 编码概念

    Unicode 编码概念 Unicode 编码可能是我们日常开发中接触最多的字符编码方式之一,其它常见的中文编码方式还包括 GB2132-80 / GB13000 / GBK / GB18030 .在 ...

  8. OC 对象和匿名对象

    OC 对象和匿名对象 对象和匿名对象的定义: 当new出一个对象时,如果用一个指针接收这个对象,那么这个指针通常被称为对象. 如果new出的对象,不用指针接收,那么这个对象就称为匿名对象. #impo ...

  9. Java8 新特性default

    在JDK1.8的Iterator接口中 package java.util; import java.util.function.Consumer; public interface Iterator ...

  10. ThinkPHP 自动验证与自动填充无效可能的原因(转)

    自动验证与自动填充是在使用ThinkPHP时经常用到的功能,但偶尔会遇到自动验证与自动填充无效的情况,本文就ThinkPHP 自动验证与自动填充无效可能的原因做一些分析. create() Think ...