代码改变世界

.net企业级架构实战之7——Spring.net整合Asp.net mvc

2008-11-30 17:57  莫耶  阅读(7550)  评论(15编辑  收藏  举报

既然提到mvc,就不得不说说微软在asp.net上的一个重要创造——postback机制。
但凡用asp.net做web开发的人,都和这个机制打过交道吧。asp.net页面aspx是其于这种自回发机制运转的。
我们平时用到的web控件,诸如TextBox、Button等,无一不是基于postback进行封装,在最终生成的html页中,它们还是会被转化成为<input type='text'>或者<input type='submit'>。
然后我们可以查看一下页面的源文件代码,会发现.net framework为我们写了不少东西:比如__doPostBack()这个js方法,用于提交窗体;WebForm_OnSubmit()方法会进行窗体的验证;一个长长的__VIEWSTATE隐藏字段保存窗体上控件的状态信息等(令人发指)……等等。(详述请参见种玉堂的《再认识asp.net的postback机制:探索__doPostBack的来龙去脉 》)。
多么让人又爱又恨的发明! postback确实方便了开发者,尤其是小型应用,手到擒来;但性能呢?不敢恭维!尤其那一长串__VIEWSTATE,每每看到,都觉得揪心。

所以,在项目中的前端表现界面,决定启用mvc实现。
原来使用monoRail,感觉很好,但一直没有解决与Spring.net整合问题;而后来干脆听说,monoRail已经停止维护这个项目,只得作罢。幸好,此时还有一个选择——asp.net Mvc。
经过一段时间的尝试,终于把它与Spring.net整合在了一起,这意味着,mvc能共享到Spring.net中的所有对象,无缝集成。

由于Asp.net Mvc是ASP.NET 3.5 Extensions Preview 的一个部分,能不能在IIS6+Asp.net 2.0 framework下良好运行,也一度让人担忧,可喜的是,网上很多人都这么干,并且成功了。也就是说,如果决定在.net 2.0下使用asp.net mvc(本实例使用的版本为asp.net mvc Preview 4),除引用mvc的dll外(Microsoft.Web.Mvc.dll、System.Web.Mvc.dll等),还需额外的引用一个.net 3.5的dll(System.core.dll),此处提供这个包的下载:

Asp.net mvc preview 4 和 System.core.dll(v3.5):/Files/moye/Asp.Net_MVC_4.rar

简单说,.net mvc将视图(View)与控制器(Controller)进行了分离,表现与逻辑的关注点不再纠缠于一团。而Model的传递,微软提供了一套DataDictionary(ViewDataDictionary、TempDataDictionary等)。同时,像monoRail一样,.net mvc在页面中也可以其于模板书写表达式,比如:你可以在Controller中传递一个IList<T>过来,这边接收到对象拆箱后,循环迭代输出一个行数等同的<Table>。
关于asp.net mvc的详尽原理,此处不累述,有兴趣可参阅 乱世重典的《Asp.net Mvc Framework 系列》。

欧克,回到重点,说说整合的事:

以上图示的结构:
将Controller单拿出来,做一个层,用以响应Web/Views中视图的请求。整合的目标就是:让这些Controller能够被注入,运行于Spring.net容器中。

通过学习Fredrik Normén的经验:《ASP.Net MVC Framework - Create your own IControllerFactory and use Spring.Net for DI 》,我们能知道,要创建自己的ControllerFactory,必须实现System.Web.Mvc的IControllerFactory接口。原型如(.net mvc Preview 4,版本不同接口也可能不一样):

using System;
using System.Web.Routing;

namespace System.Web.Mvc
{
   
public interface IControllerFactory
    {
        IController CreateController(RequestContext context,
string controllerName);
       
void DisposeController(IController controller);
    }
}


在实现的CreateController方法中,将负责对不同的Views派发对应的Controller。这里做一个假设,/Views目录中有一个叫User的目录,那框架就认为,这是一个User视图集合(具体表现在url上,如/User/home、/User/login等),应该有一个叫UserController的控制器对此目录中的所有页面进行响应。

欧克,实践一把:

◆在Web的/Views创建一个web.config文件,并这样配置(它的作用:接管对.aspx页的请求流程):

web.config


◆在/Views中创建一个叫User的目录,并在其下添加一个Home.aspx页。当然这样是不能用的,需要修改一下页面和.cs:

Home.aspx
Home.aspx.cs


◆此时,还需要一个Controller来负责响应User视图中的请求,如前结构图所示,在woodigg.controllers层中,创建一个UserController类,担当控制器角色:

UserControllers.cs


◆在控制器中,有一个Home方法,它刚好与Home.aspx同名,所以它就是负责响应Home页请求的方法。Home方法有一个整型的参数,说明这个请求也是传递参数的,形如http://localhost/User/Home/1(在.net 2.0 url没这么完美,它形如http://localhost/User.mvc/Home/1),那么1就是这个id参数,然后就去查数据库吧,1号用户的数据将它展示出来,当然这个示例没有取任何东西,只是传递了一个ViewData数据。
而我们也注意到,这里还进行了注入,欧克,开始整合Spring.net和.net mvc控制器工厂吧

◆做一个spring.net配置文件,controllers.xml,还放在web的/config目录中:

controllers.xml


◆然后,为实现IControllerFactory,在bll层中创建一个SpringControllerFactory类(一旦此bll程序集被引用,所有Views请求都将视这个实现类为Handler,控制器由它派发,这是.net mvc一个很妙的设计):

 

 

using System;
using System.Collections.Generic;
using System.Text;
using Spring.Core.IO;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Xml;
using System.Web.Mvc;
using System.Web.Routing;
using System.IO;
using woodigg.DAO.Tool;
using Spring.Context;
using Spring.Context.Support;

namespace woodigg.bll.Handler
{
    
/// <summary>
    
/// 控制器工厂
    
/// </summary>

    public class SpringControllerFactory : IControllerFactory
    
{
        
/// <summary>
        
/// 实现接口 
        
/// </summary>
        
/// <param name="context"></param>
        
/// <param name="controllerType"></param>
        
/// <returns></returns>

        public IController CreateController(RequestContext context, string name)
        
{           
            
if (File.Exists(ParameterFactory.CfgControllersFilePath))
            
{
                
//配置上下文
                IApplicationContext configContext = new XmlApplicationContext(
                    ParameterFactory.CfgFilePath,
                    ParameterFactory.CfgBusinessFilePath,
                    ParameterFactory.CfgControllersFilePath,
                    ParameterFactory.CfgBusinessMediaFilePath
                    );                
                
//首字母大写的控制器名字
                string controllName = GetFirstUpcaseName(name) + "Controller";
                
//返回对应的对象:IOC                
                if(configContext.ContainsObject(controllName))
                    
return (IController)configContext.GetObject(controllName);
                
else
                    
return null;
            }

            
else
            
{
                IControllerFactory fa 
= ControllerBuilder.Current.GetControllerFactory();
                
return fa.CreateController(context, name);
            }

            
        }


        
public void DisposeController(IController controller)
        
{
            
        }


        
//首字母大写
        private string GetFirstUpcaseName(string name)
        
{
            
string temp = name.Replace(".mvc","").ToLower();
            
string first = temp[0].ToString();
            
return temp.Replace(first, first.ToUpper());
        }

    }

}

◆这里需要说明的是ParameterFactory:一个静态参数工厂。它将当前站点的SERVER端物理目录存在static string类型的变量中,此处用到的三个变量CfgFilePath、CfgBusinessFilePath、CfgControllersFilePath,分别为三个spring.net xml配置文件所在的物理路径。
显然,当Views/User/Home.aspx发出请求时,它会在/config/controllers.xml中找到UserController控制器,并不算完——UserController请求注入UserMainDaoSpring对象,它又会在/config/business.xml中找到这个对象的定义。完了吗?那得看UserMainDaoSpring是否也有注入别的对象了。
这种情况会乱吗?一点也不,即使你不用spring.net 整合它们,也一样会用到这些对象,只是使用的流程会有一些不同罢了。那么,在这个工厂中,只要捋清楚,你会用到哪些配置就行了(对这些配置xml文件,还是按用途分类比较好,如页面一个,访问层一个,web services一个……然后,去管理它们!)。

◆最后,把它们联结起来,在web/下新建一个Global.asax全局程序文件:

 

void Application_Start(object sender, EventArgs e) 
{

       
// 控制器与spring整合
        System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory
            (
typeof(woodigg.bll.Handler.SpringControllerFactory));       
}