前一段时间做一个网站项目(运行于win2003+.net2.0环境),其间自己写了一个MVC架构,觉得比较适合团队快速开发ASP.NET web站点,现在拿出来和大家一起共享,请多指正。
一、MVC编程:
针对用户的浏览器来说,网站可以看作一个实体、一个接口,其接收浏览器的请求,并将相应的信息返回给浏览器;因此,网站程序完全可以用一个程序来完成,而 实际上也确实如此,IIS、apache等web服务器本身就是一个程序,而运行其中的asp、aspx、php、jsp、html等等的单个页面,只不 过是帮助web服务器来实现一定功能而已。
由此可以引申出:我们完全可以用一个aspx页面来处理针对网站的所有动态请求。
而这个页面,我们就把它起名为index.aspx吧。
在apache、tomcat等web服务器,都有相应的技术,将网站符合一定规则的所有http请求,都转向一个程序页面(如index.jsp或 index.php)来处理。而IIS在IIS7(前面提到,我们的网站服务器是windows2003,IIS版本为6.0)出来以前,只能借助于第三 方组件实现(windows2008的IIS7.0可以不借助第三方组件实现URLRewrite,网上可以找到相关代码)。这其中比较有名的2个是 ISAPI_Rewrite(Full版收费,Lite版免费)和IonicIsapiRewriter(免费),而对于我们这个MVC架构来 说,ISAPI_Rewrite Lite版(下载ISAPI_Rewrite Lite,这里有ISAPI_Rewrite Lite的最新版本,我们使用的是ISAPI_Rewrite3_0047_Lite.msi)就足够了,由它来控制请求到我们的index.aspx(具体代码参见DotNetMVC示例网站代码)。
(一) 网站的目录结构
为了使图片、css文件、js文件、html文件等免于ISAPI_Rewrite处理,需在网站根目录建立一个单独的mvc目录,其中存放MVC架构需要的aspx文件,从而网站的目录结构如下:
ROOT
|–App_Code
|–DotNetMVC
|–DAL
|–UserDA.cs
|–Model
|–User.cs
|–Util
|–ControllerUtil.cs
|–DBUtil.cs
|–Bin
|–log4net.dll
|–css
|–images
|–js
|–mvc
|–application
|–controllers
|-default.aspx
|-user.aspx
|–views
|–default
|-index_view.aspx
|–share
|–user
|-home_view.aspx
|-list_view.aspx
|–cache
|–log
|-index.aspx
|-Default.aspx
|-Global.asax
|-web.config
上面的结构中,css、images、js放什么文件分别存放站点用的样式文件、图片文件、JavaScript脚本文件;Bin存放公共组件(目前有一 个日志组件log4net.dll,其配置信息在web.config配置文件中),App_Code存放自定义的公共类,这是.net2.0规定 的,mvc目录的作用上面说了,不再赘述。
下面再看App_Code和mvc下的子目录构成:
App_Code->Util目录,存放一些实用工具类,目前有2个,DBUtil.cs是SQLServer数据库处理工具类,而 ControllerUtil.cs则是我们这个MVC架构比较核心的URL路由工具类,其中ParseUri方法解析URI返回控制器、控制分支、参数 等,LinkTo方法产生需要的网站链接URL(具体代码参见DotNetMVC示例网站代码)。大家还可以在其中添加一些需要的功能,如上传、Email、字符串、分页、图形处理等。
而App_Code->DAL和App_Code->Model目录中的内容一起组成MVC架构中M层逻辑,其中Model目录的仅仅是数据 库表的数据抽象类(这些类可以自己写,也可以通过工具结合数据库表自动生成),DAL目录中的类则通过访问数据库返回数据抽象类的对象实例或实例列表,供 控制层调用。
mvc根目录下的index.aspx文件,如上所述是MVC架构的入口文件。
mvc->cache和mvc->log目录分别存放缓存文件和日志文件,如果需要的话。
mvc->application目录中的controllers目录和views目录分别是控制器文件和视图文件,即MVC架构中的C和V层,其 中一个控制器对应一个aspx文件(如user.aspx),并在views目录中有一个视图子目录对应(如views->user),其中存放多 个此控制器可能用到的视图文件(如home_view.aspx和list_view.aspx等);views->share目录中,则是一些视 图文件可能用到的公共文件,如头尾包含页、母板页等。
(二)MVC运转流程:
前面曾经提到,要把所有针对本站点的动态请求,都统一到index.aspx这个文件来集中处理,因此需要借助ISAPI_Rewrite,在ISAPI_Rewrite Lite的配置文件httpd.conf(位于ISAPI_Rewrite Lite的安装目录)中增加下面一条记录即可:
RewriteRule /mvc/(.*)$ /mvc/index.aspx\?idu=$1 [NC,NE]
这条规则是将针对/mvc这个URI及其后的所有访问,都导向到index.aspx这个文件来处理,并将URI路径作为idu参数的值传给index.aspx。
下面看看index.aspx文件的内容:
<%@ import namespace="System.Threading" %>
<%
string controller;
string method;
string[] parameters;
string uri_string = Request.QueryString["idu"];
DotNetMVC.Util.ControllerUtil.ParseUri(uri_string, out controller, out method, out parameters);
Context.Items.Add("method", method);
Context.Items.Add("parameters", parameters);
string ctlpage = "system/application/controllers/" + controller + ".aspx";
try
{
Server.Transfer(ctlpage, true);
}
catch (HttpException e)
{
log4net.LogManager.GetLogger("LogFile").Error("\r\n客户机IP:" + Request.UserHostAddress + "\r\n错误地址:" + Request.Url, e);
}
%>
上面代码的关键是,通过对idu值的解析,取得controller、method、parameters这3个参数;其中controller对应于一 个控制器文件,method对应控制器中的一个控制分支,parameters则是参数的字符串数组(参数顺序自己把握),其对应URI如下:
/mvc/controller/method/parameter1/parameter2/parameterN
目前这个MVC架构的URI,都是一个控制器,后跟一个分支,再后跟多个参数,如果没有controller、method、parameters,则默 认调用default控制器的index分支,如果没有method、parameters,则默认调用本控制器的index分支;如果以上不满足要求, 大家可以自己更改ControllerUtil类的ParseUri方法或者写一个复杂的URL Routing(我个人认为不管什么,够用就好,controller、method已经能够表达复杂的网站层次了)。Server.Transfer方 法的第2个参数为true,是确保request、cookie、session等变量能够传递到控制器程序中。
下面通过default.aspx这个控制器(可以一个功能或一个模块作为一个控制器)说明一下控制器的流程:
<%@ import namespace="DotNetMVC.Util" %>
<%
string method = (string)Context.Items["method"];
string[] parameters = (string[])Context.Items["parameters"];
try
{
if (method == "index")
{
Context.Items.Add("loginurl", ControllerUtil.LinkTo("user", "login", ""));
Context.Items.Add("userlisturl", ControllerUtil.LinkTo("user", "list", ""));
Server.Execute("application/views/default/index_view.aspx");
}
}
catch (HttpException e)
{
log4net.LogManager.GetLogger("LogFile").Error("\r\n客户机IP:" + Request.UserHostAddress + "\r\n错误地址:" + Request.Url, e);
}
%>
上面代码中,首先通过Context获取index.aspx中 得到的method、parameters参数,然后根据method值的不同,走不同的处理分支(这里可以调用M层获取数据)产生数据并保存在 Context(目前仅找到用Context来传递数据到视图)中,再调用不同的视图;这里只有一个默认index分支,去调用首页视图 index_view.aspx。
最后看看views->default目录下index_view.aspx这个视图文件内容:
<%
string loginurl = (string)Context.Items["loginurl"];
string userlisturl = (string)Context.Items["userlisturl"];
%>
<!– #include file="../share/top.aspx" –>
<center>
<form name=frm1 method=post action=<%=loginurl %>>
昵称:<input type=text name=nick size=20>
密码:<input type=password name=pass size=20>
<input type=submit value=登陆> <a href=<%=userlisturl %>>去用户列表</a>
</form>
</center>
<!– #include file="../share/bottom.aspx" –>
这个视图文件中的代码则通过Context接收控制器传过来的数据并显示它。
至此,整个MVC的运转流程就完成了;其他更复杂的一些操作,大家可以参照DotNetMVC示例网站代码,相信根据这个示例,就可以直接建立并快速开发自己的MVC网站了,并有URL重写功能;在其中的user控制器及视图中,大家可以看到这种编程有一个好处,那就是可以充分利用visual studio的代码智能感知功能。
不过,有一点需要大家注意,因为需要经过ISAPI_Rewrite过滤器与Transfer、Execute处理,所以会有一些性能损失。根据流程的复 杂程度,其损失所占比重会有所不同:越复杂的流程,其损失所占比重越小,越简单的流程,其损失所占比重越大;所以大家在用这套流程开发网站时,应充分注意 这一点,并适当进行程序优化,或者干脆别用它了。
二、MV编程:
大家在看上面的DotNetMVC示例网站代码的时候,肯定有一个感觉:在控制层产生数据,要赋给Context,视图层再从Context中取出数据来显示,似乎多此一举;所以引出下面的MV编程(可能会丧失一些灵活性和可维护性,但开发一般的网站应该足够了)。
我们只需要把DotNetMVC示例网站代码中的代码简单做一下修改就可以了。
首先,修改index.aspx如下:
<%@ import namespace="System.Threading" %>
<%
string controller;
string method;
string[] parameters;
string uri_string = Request.QueryString["idu"];
DotNetMVC.Util.ControllerUtil.ParseUri(uri_string, out controller, out method, out parameters);
Context.Items.Add("method", method);
Context.Items.Add("parameters", parameters);
string ctlpage = "application/views/" + controller + "/" + method + ".aspx";
try
{
//Server.Transfer(ctlpage, true);
Server.Execute(ctlpage);
}
catch (HttpException e)
{
log4net.LogManager.GetLogger("LogFile").Error("\r\n客户机IP:" + Request.UserHostAddress + "\r\n错误地址:" + Request.Url, e);
}
%>
这里直接用controller和method组合出视图路径,并用Server.Execute去执行它(因为Server.Transfer总是抛出 System.Threading.ThreadAbortException异常,虽然不影响运行,这似乎是ASP.NET的一个小问题)。
然后删除控制器目录的内容,并将控制器页面中的代码转移到视图中即可,具体请参看DotNetMV示例网站代码。这样,既减少了编程复杂度,又增加的开发效率和运行性能,同时URL重写和代码智能感知等优点仍然得以保留。
附:
下载DotNetMVC示例网站代码和DotNetMV示例网站代码到本地后,请注意示例网站代码是一个完整的网站架构,大家可以直接在VS中打开此网站(VS2005和VS2008),根据实际修改web.config文件,在IIS中配置dotnetmv(c)为站点根目录,并配置站点属性“目录安全性”标签为“集成windows身份验证”,同时将站点应用程序配置为“启用ASP服务器脚本调试”和“启用ASP客户端脚本调试”;运行调试前,请先安装ISAPI_RewriteLite(下载ISAPI_Rewrite Lite),并修改其配置文件;调试时,请注意修改站点的“启动选项”使用本地IIS,因为VS中的DevelopWebServer无法加载ISAPI_Rewrite过滤器。
另外,测试数据表建立脚本给出如下:
[id] [int] IDENTITY(1,1) NOT NULL,
[nick] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL,
[pass] [varchar](20) COLLATE Chinese_PRC_CI_AS NULL,
[reg_date] [datetime] NULL CONSTRAINT [DF_test_inputdate] DEFAULT (getdate())
) ON [PRIMARY]