Asp.net Mvc模块化开发之分区扩展框架

对于一个企业级项目开发,模块化是非常重要的。

默认Mvc框架的AreaRegistration对模块化开发真的支持很好吗?真的有很多复杂系统在使用默认的分区开发的吗?我相信大部分asp.net的技术团队最开始都研究过分区,甚至在实际项目里面有尝试运用,但是碰到了种种问题"各种坑",最后回头是岸放弃了(我们的团队也碰到了类似问题,也有人评论中说起,直接搜索asp.net mvc分区也有不少类似信息)。

有人说asp.net Mvc框架就不适合做模块化开发,我们可以弄一个其他框架来做企业级的分模块开发,确实现在好像已经有类似的开源项目。

但是,这个确实没有必要,这里对Mvc框架做了一个简单的扩展,基本能做到分模块开发和单个简单Mvc项目开发没有太大区别,并分享给大家。

本文中的栗子是使用.net4.0、Mvc4.0及Unity2.0(企业库4.0)的。

本分区扩展集成了IoC和分区DI(依赖注入)及分区过滤器的支持。

感兴趣的同学请继续,用AreaRegistration有不爽的看官请拭目以待...


 Asp.net Mvc模块化开发系列目录

 1、 Asp.net MVc模块化开发之分区扩展框架(送源码)

 2、 Asp.net Mvc模块化开发之“开启模块开发、调试的简单愉快之旅”

 3、 Asp.net Mvc模块化开发之“逻辑(项目)复用”

   3.1、 不同角色或者权限的逻辑(项目)复用(分区过滤器的应用)

   3.2、 不同业务的逻辑(项目)复用(DI(依赖注入)的应用)

 4、 Asp.net Mvc模块化开发之“项目(分区)拆分”

 5、  Asp.net Mvc模块化开发之“部分版本部分模块更新(上线)”

 


一、先说一下我用Mvc不爽的地方,看大家是否有同感

1、分区类(继承AreaRegistration)是个"特殊"类型,只能创建一个对象,而且只能简单的"New",不能使用构造函数、属性和方法来初始化对象

   好在Mvc是开源的,我们从AreaRegistration的源码中发现是,Mvc是通过AreaRegistration.RegisterAllAreas()初始化所有分区

   从上图可以看到Mvc先通过把每个继承AreaRegistration的类型找出来,再通过Activator.CreateInstance创建对象。在我看来类型和对象应该是一对多的关系,分区类也一样。我希望对分区的字段和属性进行不同的初始化以便可以使用同一个类型创建不同的分区。

2、分区的完整路径提前写死了,哪怕是换一下前面的路径都休想

   比如有个评论的功能,我希望做成一个分区。新闻的评论地址规则为/News/Comment/,博客的评论地址规则为/Blog/Comment/,使用Mvc默认分区就只能定义为/Comment/了

3、路由初始化需要使用Global.asax

   其一、但是多个分区部署同一站点用谁的Global.asax呢?有人说每个Global都定义RegisterAllAreas,用谁的都行。但是对于程序员这个大多都是偏执狂的人群是很难接受的。如果哪个项目下线,正好整个站点都使用的是这个项目的Global,那就都挂了!有风险啊。

   其二、那就建一个项目为主项目,其他项目都以分区的形式定义?What?谁是主项目?偏执狂纠结中...

   其三、建一个项目为空项目为主项目,只有Global和RegisterAllAreas,还是觉得挺别扭的。

   其四、定义一个HttpModule在Init中执行RegisterAllAreas。这个还算靠谱,建议大家使用。本扩展框架也是定义一个HttpModule来初始化一些Mvc的配置达到扩展目的的。

4、Mvc很多种过滤器,但默认配置过滤器的方法只有两种,一种是Attribute,一种是GlobalFilters(全局过滤器)

   其一、Attribute定义过滤器太细了,要每个Controller或者Action去加,不能漏哦,特别是权限判断

   其二、GlobalFilters拦截所有请求,如果使用分区开发的项目,每个分区有自己的过滤器需求,该需求可能与其他分区冲突(影响其他分区的Controller正常执行)。

   其三、分区我希望复用,其中的Controller当然也要服用,在不同的部署中有使用不同过滤器或者过滤器参数的需求,Attribute定义的过滤器很难满足要求。

   总之我希望有一种过滤器只影响当前部署的分区,不需要提前和分区及Controller绑定(高耦合),部署(运行时)的时候再和指定分区关联(当然要借助IoC容器,这里使用的是微软的Unity框架)。

5、Mvc4.0支持DI,如果没有IoC容器的支持,很鸡肋

   其一、Mvc4.0默认的DI基本上没有什么作用,必须扩展。

   其二、每个分区有不同的DI(依赖注入)需求,我希望每个分区可以配置成不同的。

6、使用默认的分区开发出现重名的控制器错误或者视图渲染出错

   6.1 在主项目中增加分区,如果新加分区的控制器(Controller)类名与主项目中控制器同名,导致主项目的控制器出错,但是分区的控制器正常

     这是个挺要命的问题,新加分区不出错,主项目的控制器出错,这个问题很难定位,我们一般是哪里出错找哪里。但是我们在主项目的控制器里找不到错误,而且这个控制器原来是正常工作的。新加了分区功能,但他"干掉"原来正常的功能!!!(很恐怖不是吗?如果知道  原因当然好处理,关键是很多人不会去怀疑新加并正常运行的分区,新分区的开发人员也可能会"理直气壮"的否认是自己的问题。)

   6.2 新加分区的视图出错

        一种原因是默认分区新加视图的位置和普通项目不一样,搞不好弄错位置

       还有一种原因,主项目的视图与新分区的视图冲突,这个就有点恶心了

      

      总之默认的分区开发总是和普通的项目(Mvc)开发有很多的不同,导致分模块开发的困难和故障,甚至使人敬而远之,彻底不分区。

二、Mvc分区扩展方法

1、我们继续从源码查找分区初始化的过程

   RegisterAllAreas调用CreateContextAndRegister

   但是CreateContextAndRegister是一个internal保护的方法,不让我们用啊!好在这个方法没有几行代码,我们直接再写一个类似方法就可以了。

2、Mvc最重要的就是路由规则,这个也要搞清楚

   这个简单,随便建一个分区就可以看得很清楚

   原来Mvc是通过RegisterArea调用AreaRegistrationContext的MapRoute方法来设置路由规则的;而AreaRegistrationContext对象创建就更简单,直接New出来的(参考前面CreateContextAndRegister的源码截图)!

   现在基本上都搞清楚了,可以动手进行扩展了。

3、首先对分区扩展类(Area),我们来增加一些功能

   我这里定义了一个类型Fang.Mvc.Area继承AreaRegistration。这个是可以按个人爱好加一些功能的,最好是一些常用功能,每个分区项目的个性化功能可以通过再次继承这个类型来扩展

   3.1 扩展分区基本属性

   

   通过Name属性实现分区名的配置(注意:同一个站点下分区名不能重复)。

   通过Path属性分区路径前缀,就是分区的路由规则前都加一个这个路径。其一、大家知道,如果路由规则冲突化,Mvc无法找到正常的路由规则,这个很要命,增加一个部署是配置的路径就很有必要了。其二、我的需求比较奇特,我希望同一个站点下可以部署多个相同的分区,就像前面说的我们要给/News/和/Blog/都部署Comment分区,访问路径不一样,访问的数据库或者数据表也可以不一样,对已数据源不一样就靠DI了,后面再继续说。

   通过NameSpaces属性配置当前分区查找Controller的范围。如果没有配置就按Mvc默认处理,按当前分区类的命名空间。

   这样我们的每个分区是独立的(通过NameSpaces隔离),每个分区既可以独立运行调试和可以和其他分区部署到一起也不会相互冲突。和普通简单的Mvc项目几乎没有区别,大大减少开发和学习成本。当然有人会说,用这个分区扩展框架不是还要现学容器框架(Unity),这个确是事实。但是容器框架(Unity)的作用不仅仅是管理好几个分区,容器的Ioc、Aop等作用对项目开发那是如虎添翼,容器技术几乎是大型系统开发必备工具,小小的分区框架集成了容器本身也是该框架的一大亮点。

4、定义一个路由规则注册类(AreaRoute)来达到把Area的配置应用到路由注册

   AreaRoute通过调用AreaRegistrationContext的对应方法注册

   AreaRoute调用Area的CheckName、CheckUrl和CheckNameSpaces来实现Area的配置

   

   把分区的路由规则保存到Area的_routes列表中备用(这里不详述作用,不在本文探讨范围内)

5、现在开始搞定分区过滤器

   5.1 在Area类增加属性Filters配置分区过滤器

   5.2 定义AreaFilterProvider类型来调用Area的Filters

   AreaFilterProvider继承Mvc框架的IFilterProvider接口

   在分区初始化时把AreaFilterProvider添加到Mvc的FilterProviders.Providers中,任务完成。

6、该轮到期盼已久DI容器了

   6.1 首先定义Fang.Mvc.Container类来封装一下Unity容器

   这个使用其他容器工具也是可以的,甚至封装一个工厂,支持多种容器也未不可。

   至于有人说Unity有"bug",不适合做DI。我使用Unity容器有几年了,还没完全搞明白Unity,我认为Unity容器还是挺博大精深的,足够我用了。至于"bug"也算不上bug,最多是坑,多踩踩就平了。至于Unity(及企业库)有多挺博大精深,不在本文探讨范围,敢兴趣的可以自己研究,找高手探讨。

   找我交流也行(但是我还在学习中,认识可能还肤浅或者有错误,最好不要误导了大家)。

   6.2 增加属性DependencyContainer配置DI(依赖注入)容器的名字

   6.3 定义AreaDependencyResolver类来实现分区配置和DI配置的结合

   AreaDependencyResolver继承Mvc框架的IDependencyResolver接口

   接下来就简单了,按Http上线文信息获取分区配置,调用容器工厂,调用容器实现GetService方法即可。

7、万事俱备了,借HttpModule的东风吹起来

   定义类AreaMergeModule继承asp.net的IHttpModule,在Init方法中一通调用初始化以上扩展的功能的初始化。

   调用容器工厂获取Mvc容器,在容器中获取所有的Area对象并逐个初始化,再初始化AreaDependencyResolver。

三、重头戏开场,炒"栗子"

1、一个站点配置两个分区,分别调用不同的数据源

   源码路径:\Test\MvcApplication1

   1.1 先看分区及容器配置

   配置文件:\Test\MvcApplication1\ConfigFiles\unity.config

   

   以上使用分区类MvcApplication1.RouteConfig定义两个分区(A和B),每个分区的名字和路径等都配置不一样。

   另外还额外定义了两个依赖注入的容器。名字分别为两个分区配置的依赖注入的容器名。

   以上还可以看出分区类和分区的Controller没有依赖关系,事实上只要路由规则没有特殊路由及其他要求,可以使用定义好的默认分区类Area。而且分区定义是按命名空间,事实上原有复杂系统可以在不修改原项目的情况下按逻辑(命名空间)拆分为多个分区,以后看情况再逐个分区拆分为独立项目,优化项目结构。

   如果有同学说你上面的配置看不懂,不好意思要补课了,找度娘,搜索"Unity2.0容器配置"(小心有一个叫Unity3d的东西是做游戏的,和这个没关系)。

   1.2 再看HttpModule配置

   配置文件:\Test\MvcApplication1\Web.config

   

   system.web的httpModules在经典模式下起作用,system.webServer的modules在集成模式下起作用,如果不确定两个都配上就Ok了。

   1.3 分区路由配置(与Mvc默认路由配置及分区配置对比)

   配置文件:\Normaltest\MvcApplication4\App_Start\RouteConfig.cs

   

   配置文件:\Test\MvcApplication1\App_Start\RouteConfig.cs

   

   以上三种路由配置中,可以看出新改良的分区扩展居然比Mvc默认分区配置和静态路由配置更相似

   首先使用该扩展配置路由没有新的学习成本

   其次原非分区项目要改成分区项目建一个分区类(扩展后的),把路由规则直接复制过来,意味迁移成本非常低。

   1.4 再看一下Controller怎么用DI

   代码文件:\Test\MvcApplication1\Controllers\HomeController.cs

   HomeController执行依赖一个数据源(Config),但是HomeController并没有定义和调用获取数据源的方法,只是把数据源(Config属性)声明为Dependency的。

   1.5 最后看测试效果

   

   

   例子有点简单,完全说明问题,两个不同路径(分区路径)的Url都访问到HomeController而且数据源(Config属性)自动初始化成功,而且这两个地址调用了不同的数据源。

2、分区过滤器的栗子

   源码路径:\Test\Site

   2.1 先看分区及容器配置

   配置文件:\Test\Site\ConfigFiles\unity.config

   

   这次配置更复杂了一点,定了三个分区,使用两个分区类。配置了一个过滤器,而且有两个分区(不同分区类)引用了该过滤。(依赖注入配置同前一个例子,没必要说了)

   2.2 其他配置和上一个例子相似,直接看三个分区效果

   

   

   

   对此不想多说了,一切都在以上的热乎乎的栗子里,我的目的已经达到了。你的目的达到了吗?

四、下载地址重新开放

      源码下载 

 

   模块开发是个系统性问题,后续我还将发表使用Mvc分区扩展框架更好的解决实际开发问题文章,敬请期待...

 

   本框架是从搜房内部框架中拆分出来的一部分。随着.net开源社区的快速发展,搜房作为广泛使用.net的大型互联网公司之一,正在逐步以新的姿态拥抱开源。

   我们鼓励(甚者奖励)搜房的程序员参与开源项目或者把内部通用系统脱敏整理后在网上开源。

posted on 2016-03-19 09:18  xiangji  阅读(5302)  评论(33编辑  收藏  举报

导航