你的位置:首页 > ASP.net教程

[ASP.net教程]Asp.net模块化开发之Mvc分区扩展框架


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

默认Mvc框架的AreaRegistration对模块化开发已经支持比较好了,还有必要扩展吗?还能扩展吗?

本文中的栗子是使用.net4.0、Mvc4.0及Unity2.0(企业库4.0)的,提供完整源码。

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

本分区扩展框架(Fang.Mvc)在演示栗子源码中包含完整源码,拿到自己的项目直接引用即可使用了。

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


一、先说一下我用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(依赖注入)需求,我希望每个分区可以配置成不同的。

二、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默认处理,按当前分区类的命名空间。

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的大型互联网公司之一,正在逐步以新的姿态拥抱开源。

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