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

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

基于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,请大家自行加载,源码下载 ,没有密码全部开放!

目录
相关文章
|
1月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
1月前
|
存储 消息中间件 前端开发
.NET常见的几种项目架构模式,你知道几种?
.NET常见的几种项目架构模式,你知道几种?
|
2月前
|
Java API 开发者
【Java模块化新飞跃】JDK 22模块化增强:构建更灵活、更可维护的应用架构!
【9月更文挑战第9天】JDK 22的模块化增强为开发者构建更灵活、更可维护的应用架构提供了强有力的支持。通过模块化设计、精细的依赖管理和丰富的工具支持,开发者可以更加高效地开发和管理应用,提高应用的性能和可维护性。
86 10
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
179 3
|
3月前
|
设计模式 存储 前端开发
揭秘.NET架构设计模式:如何构建坚不可摧的系统?掌握这些,让你的项目无懈可击!
【8月更文挑战第28天】在软件开发中,设计模式是解决常见问题的经典方案,助力构建可维护、可扩展的系统。本文探讨了.NET中三种关键架构设计模式:MVC、依赖注入与仓储模式,并提供了示例代码。MVC通过模型、视图和控制器分离关注点;依赖注入则通过外部管理组件依赖提升复用性和可测性;仓储模式则统一数据访问接口,分离数据逻辑与业务逻辑。掌握这些模式有助于开发者优化系统架构,提升软件质量。
54 5
|
3月前
|
XML 开发框架 .NET
.NET框架:软件开发领域的瑞士军刀,如何让初学者变身代码艺术家——从基础架构到独特优势,一篇不可错过的深度解读。
【8月更文挑战第28天】.NET框架是由微软推出的统一开发平台,支持多种编程语言,简化应用程序的开发与部署。其核心组件包括公共语言运行库(CLR)和类库(FCL)。CLR负责内存管理、线程管理和异常处理等任务,确保代码稳定运行;FCL则提供了丰富的类和接口,涵盖网络、数据访问、安全性等多个领域,提高开发效率。此外,.NET框架还支持跨语言互操作,允许开发者使用C#、VB.NET等语言编写代码并无缝集成。这一框架凭借其强大的功能和广泛的社区支持,已成为软件开发领域的重要工具,适合初学者深入学习以奠定职业生涯基础。
102 1
|
4月前
|
Web App开发 JavaScript 前端开发
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
|
3月前
|
移动开发 前端开发 weex
Android项目架构设计问题之模块化后调用式通信如何解决
Android项目架构设计问题之模块化后调用式通信如何解决
16 0
|
4月前
|
JSON Go C++
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
45 1
|
5月前
|
安全 Java Maven
Spring Boot项目的模块化设计与架构
Spring Boot项目的模块化设计与架构