Mvc 模块化开发

在Mvc中,标准的模块化开发方式是使用Areas,每一个Area都可以注册自己的路由,使用自己的控件器与视图。但是在具体使用上它有如下两个限制

1.必须把视图文件放到主项目的Areas文件夹下才能生效,否则运行时会发生找不到视图的错误。

2.在实际开发中,这种开发方式只能建立一个项目,所有的开发工作都在这个项目里完成,非常不利于团队大规模开发。

显然,上面的两点限制严重制约了插件化开发实际运用。为了实现真正的插件化开发,大家积极的思考研究,又找到了如下几种方式

1.MVC Portable Areas

这种开发方式,是使用单独的项目进行Areas开发,然后将所有页面,样式,脚本等资源以“嵌入的资源”的方式编译到dll中。这样被主项目引用后,就不会发生找不到资源的情况了。另外,还有一个名为Razor Generator的插件来帮助做这个事情。

这种开发方式也有个严重的问题,即严重减慢了开发效率。每当你更改项目里的任意一点元素,包括样式,脚本,视图,都需要重新编译项目才能生效。而在标准的开发方式中,这些元素都是即时生效的。原因就是在运行时,系统寻找的是dll中的资源,而不是项目里的文件。

一般来讲,用这种方式进行模块项目发布,可能会更合适。

MVC Portable Areas

Portable Area disadvantages

2.模拟Areas

这个名字是我自己起的,是通过独立的项目来模拟主项目Areas部份。在具体使用上,是将普通的Mvc项目建立到主项目的Areas文件夹下,然后手工删除除Model,Controller,Views外的所有文件,再手工建立Areas注册文件。这种开发方式比较巧妙的将视图放到了Areas能找到的目录,又是通过独立项目的方式进行开发,基本满足了模块化开发的需要。

但是这种模拟开发方式仍有一个小的瑕疵。如果一个解决方案很大,包括了多个主项目,此时就无法实现主项目共用子模块,因为无法将一个子模块同时放到多个项目的Areas中去。

ASP.NET MVC 4 pluggable application modules

我目前在工作中使用的是第2种开发方式。对于无法共享子模块的问题,目前只是将代码复制多份来解决。这显然不是一个好的办法,但也是没有办法的办法。

PS:如果VS支持虚拟目录就好了。

最近翻阅园子,发现菜鸟一个同学通过自定义VirtualPathProvider类实现了模块化开发,感觉很不错,遂仔细研读,颇有收获,现分享如下。

3.自定义VirtualPathProvider

这类方式的基本思路是,改变Mvc中默认寻找文件的方式,让其到我指定的目录查找,将找到的文件返回。但是具体实现上,我与菜鸟一个有所不同。当然,我是学习他的,是他的简化版。

菜鸟一个同学是重量级实现方案,其不仅重写了寻找过程,还自定义了文件过滤机制。另外,其模块注册过程是在主项目中完成的。

我的方案是轻量级实现方案,在延用Areas方式的基础上,重写了文件的寻找方式。模块注册过程是在子项目中完成的。

下面主要介绍我的方案。菜鸟一个同学的方案可以去他的博客中研究。

在我的案例中,MvcApplication1是主项目,MvcApplication2是模块项目,项目文件夹与项目名同名,两个项目文件夹放置在同级目录。

一.什么是VirtualPathProvider

MSND上的说明是:提供了一组方法,以实现用于Web 应用程序的虚拟文件系统。

简单的讲,当一个请求申请某个文件时,如果不存在这个文件,默认会返回404错误,但是这个类可以动态将别的资源作为这个资源返回回去。比如将另一个目录下的同名或不同名文件返回,甚至动态生成一个文件然后返回。

二.注册模块路径

在我们的需求中,文件不是不存在,只是不在Areas目录下而以。所以我们要做的就是将请求的文件切换到实际目录下然后返回。那么第一步就是要告诉系统文件的真正路径。

在这里我定义了IAreaVirtualPathRegistration接口,只有一个方法GetPath,就是返回模块与路径的对应关系

public interface IAreaVirtualPathRegistration
{
    List<KeyValuePair<string, string>> GetPath();
}

这里我没有用字典的原因是我允许同一个模块名有多个不同的目录。如果使用了字典数据结构,后面的配置会覆盖前面的配置。

这里配置的路径,是相对于主项目的项目文件夹的路径。

MvcApplication2的注册文件如下

public class MvcApplication2AreaVirtualPathRegistration: IAreaVirtualPathRegistration
{
    public List<KeyValuePair<string, string>> GetPath()
    {
        var pathList = new List<KeyValuePair<string, string>>();
        pathList.Add(new KeyValuePair<string, string>("MvcApplication2", "MvcApplication2"));

        return pathList;
    }
}

三.自定义VirtualPathProvider

名字就叫AreaVirtualPathProvider好了

public class AreaVirtualPathProvider : VirtualPathProvider

定义一个basePath字段,记录主项目的物理路径

private readonly string basePath = Path.GetFullPath(HostingEnvironment.MapPath("~") + @"..");

定义了areaVirtualPathList字段,并在静态构造函数中获取项目中所有注册的模块路径关系

private static List<KeyValuePair<string, string>> areaVirtualPathList = new List<KeyValuePair<string, string>>();

static AreaVirtualPathProvider()
{
    var assemblies = AppDomain.CurrentDomain.GetAssemblies();
    foreach (var assembly in assemblies)
    {
        foreach (var type in assembly.GetExportedTypes())
        {
            if (Array.Exists(type.GetInterfaces(), t => t.Name.Equals("IAreaVirtualPathRegistration")))
            {
                var areaVirtualPathRegistration = assembly.CreateInstance(type.FullName) as IAreaVirtualPathRegistration;
                foreach (var areaVirtualPath in areaVirtualPathRegistration.GetPath())
                {
                    var key = @"/Areas/" + areaVirtualPath.Key;
                    var value = areaVirtualPath.Value;

                    areaVirtualPathList.Add(new KeyValuePair<string, string>(key, value));
                }
            }
        }
    }
}

定义了GetRealPath方法,将请求的虚拟路径转换为本地物理路径,这个方法是核心方法

private string GetRealPath(string virtualPath)
{
    if (virtualPath.StartsWith("~"))
    {
        virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
    }

    foreach (var areaVirtualPath in areaVirtualPathList)
    {
        if (virtualPath.StartsWith(areaVirtualPath.Key, StringComparison.OrdinalIgnoreCase))
        {
            var realPath = Path.Combine(basePath, virtualPath.Replace(areaVirtualPath.Key, areaVirtualPath.Value));

            if (File.Exists(realPath))
            {
                return realPath;
            }
        }
    }

    return null;
}

可以看到,实现其实很简单,即将虚拟路径中关于Areas的路径部份替换为所配置的实际路径。由于虚拟路径中对于模块项目的请求都会自动带上/Areas/段,所以在上一步中需要为areaVirtualPath的Key的前面增加一个Areas。

下面,就是重写VirtualPathProvider的相关方法了

首先重写FileExists方法

public override bool FileExists(string virtualPath)
{
    var realPath = GetRealPath(virtualPath);
    if (realPath != null)
    {
        return true;
    }

    return base.FileExists(virtualPath);
}

可以看到,这种重写方式,保留了默认的调用,即对于模块项目的请求,使用自定义方式,对于主项目的请求,由于获取的结果是null,最后还是使用默认方式。

重写GetCacheDependency方法

public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
    var realPath = GetRealPath(virtualPath);
    if (realPath != null)
    {
        var filePathList = new List<string>();
        foreach (var virtualPath1 in virtualPathDependencies)
        {
            filePathList.Add(GetRealPath(virtualPath1.ToString()));
        }

        return new CacheDependency(filePathList.ToArray(), utcStart);
    }

    return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}

重写GetFileHash方法

public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
{
    var realPath = GetRealPath(virtualPath);
    if (realPath != null)
    {
        var filePathList = new List<string>();
        foreach (var virtualPath1 in virtualPathDependencies)
        {
            filePathList.Add(GetRealPath(virtualPath1.ToString()));
        }

        return string.Join(string.Empty, filePathList.ToArray()).GetHashCode().ToString();
    }

    return base.GetFileHash(virtualPath, virtualPathDependencies);
}

重写GetFile方法,这个也是核心方法

public override VirtualFile GetFile(string virtualPath)
{
    var realPath = GetRealPath(virtualPath);
    if (realPath != null)
    {
        var viewStream = new FileStream(realPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        var webConfigFileStream = new FileStream(GetWebConfigFullPath(virtualPath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        return new AreaVirtualFile(virtualPath, CorrectView(virtualPath, viewStream, webConfigFileStream));
    }

    return base.GetFile(virtualPath);
}

在谈这个方法之前先说一下Mvc中的View。我们每天写的cshtml其实只是一个半成品,框架还会为我们自动加上父类声明,引用的命名空间等。这些文件中缺少的部份一般定义在Web.config中。

在GetFile返回的文件中,也需要包含这些内容。

在上面的代码中,viewStream变量指向请求的View文件,webConfigFileStream变量指向对应的Web.config文件。Web.config文件通过GetWebConfigFullPath方法获取

private string GetWebConfigFullPath(string viewVirtualPath)
{
    var realPath = Path.GetDirectoryName(GetRealPath(viewVirtualPath));
    while (realPath.Contains("\\"))
    {
        var webConfigPath = realPath + @"\Web.config";
        if (File.Exists(webConfigPath))
        {
            return webConfigPath;
        }

        realPath = realPath.Substring(0, realPath.LastIndexOf(‘\\‘));
    }

    return Path.GetFullPath(HostingEnvironment.MapPath("~/Views/Web.Config"));
}

可以看到,从cshtml所在文件夹开始逐级向上查找Web.config,如果找到则返回,如果一直没有找到,则使用主项目的View的Web.config。

拿到视图文件和Web.config文件后,通过CorrectView方法将必要内容插入到cshtml文件中。这个方法太长,就不贴了。

最后,创建一个AreaVirtualFile对象并返回。

public class AreaVirtualFile : VirtualFile
{
    private readonly Stream stream;

    public AreaVirtualFile(string virtualPath, Stream stream)
        : base(virtualPath)
    {
        this.stream = stream;
    }

    public override bool IsDirectory
    {
        get
        {
            return false;
        }
    }

    public override Stream Open()
    {
        return stream;
    }
}

以上,就是整个方案的全部内容。

对于这个解决方案,我有一点表示不解。我翻看了Mvc的源码,发现其并没有实现自己的VirtualPathProvider,那么对于我的自己实现的VirtualPathProvider,为什么GetFile方法不能使用默认实现,而必须是返回加工之后的文件呢?我功力不够,源码看的我很混乱,貌似其优先使用了自己的一套文件查找系统,如果找不到才使用VirtualPathProvider。

或者,还有更优的解决方案?

PS:项目实例下载

PPS:对于.Net源码调试的设置,可以参考这一篇

参考:

MVC 插件式开发

Using custom VirtualPathProvider to load embedded resource Partial Views

如何用MEF实现Asp.Net MVC框架

基于ASP.NET MVC3 Razor的模块化/插件式架构实现

自定义VirtualPathProvider映射ASP.NET MVC View

时间: 2024-08-26 11:59:59

Mvc 模块化开发的相关文章

ASP.NET MVC模块化开发——动态挂载外部项目

原文:ASP.NET MVC模块化开发--动态挂载外部项目 最近在开发一个MVC框架,开发过程中考虑到以后开发依托于框架的项目,为了框架的维护更新升级,代码肯定要和具体的业务工程分割开来,所以需要解决业务工程挂载在框架工程的问题,MVC与传统的ASP.NET不同,WebForm项目只需要挂在虚拟目录拷贝dll就可以访问,但是MVC不可能去引用工程项目的dll重新编译,从而产生了开发一个动态挂在MVC项目功能的想法,MVC项目挂载主要有几个问题,接下来进行详细的分析与完成解决方案 一般动态加载dl

MVC模块化开发方案

核心: 主要利用MVC的区域功能,实现项目模块独立开发和调试. 目标: 各个模块以独立MVC应用程序存在,即模块可独立开发和调试. 动态注册各个模块路由. 一:新建解决方案目录结构 如图: 二:EasyMvc.Core即为核心库. 核心库三大主力:AreaConfig .RouteConfig .FilterConfig AreaConfig :为区域启动停止以及其他状态时注入的方法,类似与Global.asax里面Application_Start.Application_End方法. Rou

Java单体应用 - 常用框架 - 07.Spring MVC - Maven 模块化开发(iot-

原文地址:http://www.work100.net/training/monolithic-frameworks-spring-mvc-maven-module.html更多教程:光束云 - 免费课程 Maven 模块化开发 序号 文内章节 视频 1 概述 - 2 创建根项目(工程) - 3 创建统一的依赖管理模块 - 4 创建通用的工具类模块 - 5 创建领域模型模块 - 6 创建管理后台模块 - 7 创建前端控制台模块 - 8 创建接口模块 - 9 清理.编译.打包 - 10 功能完善

关于Egret模块化开发---vip系统

关于Egret模块化开发---vip系统 目录 关于Egret模块化开发---vip系统... 1 前言... 1 一,搭建界面... 1 二,建立数据模型... 3 1)数据模型的搭建: 3 2)数据的建立... 4 3)数据的增删改查... 7 三.做交互, 7 结束... 8 前言 做游戏就是做数据,数据模型的设计,是体验mvc一种基础的设计,用数据驱动界面变化.. 做为一个程序要学会模块化,配置化,脚本化的需求,提防策划的需求变更的可能 例如:做一个VIP系统步骤: 搭建界面 建立数据模

全面解析ASP.NET MVC模块化架构方案

什么叫架构?揭开架构神秘的面纱,无非就是:分层+模块化.任意复杂的架构,你也会发现架构师也就做了这两件事. 本文将会全面的介绍我们团队在模块化设计方面取得的经验.之所以加了“全面”二字,是因为本文的内容将会涉及到:数据库.路由.C#.JavaScript.CSS.HTML等一个完整模块所需要的内容. 在阅读本文之前后,你也可以转到我们的开源项目:https://github.com/leotsai/mvcsolution.这个开源项目完整的总结了我们团队在ASP.NET MVC领域的分层架构思想

js模块化开发--AMD--CMD

什么是模块化开发? 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了,jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得前端开发得到重视,也使得前端项目越来越复杂,然而,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,那么什么是模块呢? 一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块.模块开发

时间:第1周9月16日;主题:初识ASP.NET MVC项目开发(一)

Part I:回顾及提问 ==================== 1. ASP.NET MVC是微软公司.NET平台上的一个______________,它为开发者提供了一种构建结构良好的Web应用程序的方式. 2. 自2007年首次公布预览以来,作为_____________的替代品,ASP.NET MVC的普及度已明显提高,现在很多大型Web应用程序都是使用这一技术构建的. 3. 为了简化软件开发的复杂度,以一种概念简单却又权责分明的架构来贯穿整个软件开发流程,将业务逻辑层与_______

JS模块化开发:使用SeaJs高效构建页面

一.扯淡部分 很久很久以前,也就是刚开始接触前端的那会儿,脑袋里压根没有什么架构.重构.性能这些概念,天真地以为前端===好看的页面,甚至把js都划分到除了用来写一些美美的特效别无它用的阴暗角落里,就更别说会知道js还有面向对象,设计模式,MVC,MVVM,模块化,构建工具等等这些高大上的概念了.现在想想还真是Too young too naive.前两天某大神在群里分享他招聘前端的心得的时候就说,就是那些以为能写两个页面就可以自称前端的人拉低了行业水平.这样看来前两年我还真的扯了不少后腿呢……

Maven模块化开发

最近在做一个项目,这个项目有个特点,含有众多业务模块,各业务模块间关联内容很少,且公司领导又想达到各模块可快速单独拆分使用的效果(说白了就是公司没钱,想把这个项目做完后,做下拆分,拆分成各个小产品单独销售),于是乎我想到是否可使用Maven来进行模块化开发,将各个业务模块做成一个个小的Jar包,到用的时候需要那个加载那个. 一,构想: 把系统分成以下几个模块(项目名称proj): 1,proj模块,作为父模块用于聚合所有子模块,打包方式pom 2,proj-web模块,用于发布最终的war包,打