既然提到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
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<remove verb="*" path="*.aspx"/>
<add path="*.aspx" verb="*"
type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
<remove name="PageHandlerFactory-ISAPI-2.0"/>
<remove name="PageHandlerFactory-ISAPI-1.1"/>
<remove name="PageHandlerFactory-Integrated"/>
<add name="BlockViewHandler" path="*.aspx" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
</configuration>
◆在/Views中创建一个叫User的目录,并在其下添加一个Home.aspx页。当然这样是不能用的,需要修改一下页面和.cs:
Home.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Home.aspx.cs" Inherits="mvcApp.Views.User.Home" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>User Home</title>
</head>
<body>
</body>
</html>
Home.aspx.cs
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
namespace mvcApp.Views.User
{
public partial class Home : ViewPage
{
}
}
◆此时,还需要一个Controller来负责响应User视图中的请求,如前结构图所示,在woodigg.controllers层中,创建一个UserController类,担当控制器角色:
UserControllers.cs
using System;
using System.Text;
using System.Collections.Generic;
using woodigg.model;
using woodigg.Interface.DAO;
using System.Data;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using System.IO;
using woodigg.controllers;
using woodigg.DAO.Tool;
using woodigg.bll.Tool;
namespace woodigg.controllers.Controllers
{
public class UserControllers : Controller
{
#region 注入对象
private IUserMainDAO _UserMainDaoSpring;
public IUserMainDAO UserMainDaoSpring
{
get { return _UserMainDaoSpring; }
set { _UserMainDaoSpring = value; }
}
#endregion
/*--------------------- USER ---------------------*/
/// <summary>
/// Home页
/// </summary>
[HandleError]
[OutputCache(Duration = 60, VaryByParam = "id")]
#region ActionResult Home(int id)
public ActionResult Home(int id)
{
ViewData["Title"] = "用户中心 -- Haphere.com";
return View();
}
#endregion
}
}
◆在控制器中,有一个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
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd"
>
<!--controllers-->
<object id="UserController"
type="woodigg.controllers.Controllers.UserControllers, woodigg.controllers" autowire="byName">
</object>
</objects>
◆然后,为实现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));
}