以前一直对.Net
的表现业务感觉不爽,
上次和Forever
讨论很久,
也没能很好的解决表现成业务分离的问题,
最近看到了新的Asp.Net MVC
框架,
恍然茅塞顿开,
原来如此,
以前的WebForm
基于MVP
方式,
这次新的MVC
框架将表现成很轻松的分离这些业务.
Ok,今天我就来试着用MVC的方式写一个小的Demo程序,这个程序非常简单,就是一个Article的发布和浏览程序,程序有两个表,分别是category和content,分别是分类表,文章表,程序使用MySql数据库,建表脚本如下:
create
table
`category`(
`id`
int
primary
key
not
null
auto_increment,
`name`
varchar
(
255
)
not
null
,
`intro`
varchar
(
1024
),
`
order
`
int
default
1000
,
`url`
varchar
(
255
),
`isurl`
bit
not
null
default
0
);
ALTER
TABLE
`category`
ADD
INDEX
( `
order
` ) ;
create
table
`content`(
`id`
int
primary
key
not
null
auto_increment,
`categoryid`
int
not
null
,
`title`
varchar
(
255
)
not
null
,
`info`
varchar
(
1024
),
`content`
text
,
`posttime`
datetime
not
null
,
`postuser`
varchar
(
50
),
`hits`
int
not
null
default
0
);
ALTER
TABLE
`content`
ADD
INDEX
( `categoryid` ) ;
ALTER
TABLE
`content`
ADD
INDEX
( `postuser` ) ;
ALTER
TABLE
`content`
ADD
INDEX
( `hits` ) ;
insert
into
`category`(`name`,`intro`,`
order
`,`url`,`isurl`)
values
(
'
Index
'
,
'
Index Page
'
,
0
,
'
/default.aspx
'
,
1
),
(
'
News
'
,
'
业界新闻
'
,
1
,
''
,
0
),
(
'
.Net
'
,
'
.Net开发
'
,
2
,
''
,
0
),
(
'
Asp.Net MVC
'
,
'
Asp.Net MVC
'
,
3
,
''
,
0
),
(
'
C/C++
'
,
'
C/C++开发
'
,
4
,
''
,
0
);
insert
into
`content`(`categoryid`,`title`,`info`,`content`,`posttime`,`postuser`,`hits`)
values
(
2
,
'
MvcArticle System 1.0发布
'
,
'
MvcArticle System 1.0发布
'
,
'
经过一段时间的开发,MvcArticle System 1.0发布终于发布.
'
,NOW(),
'
Leven
'
,
0
);
在程序中
,
我创建一个
HttpModule
来初始化整个
UrlRouting
部分
.UrlRouting
初始化代码如下
:
private
static
void
InitRoutingUrls(RouteCollection routes)
{
routes.Add(new Route(string.Format("article{0}/list/{{category}}/{{page}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "list", category = "0", page = "1" }),
Constraints = new RouteValueDictionary(new { category = "\\d+", page = "\\d+" }),
});
routes.Add(new Route(string.Format("article{0}/index/{{page}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "index", page = "1" }),
Constraints = new RouteValueDictionary(new { page = "\\d+" }),
});
routes.Add(new Route(string.Format("article{0}/view/{{id}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "view", id = "1" }),
Constraints = new RouteValueDictionary(new { id = "\\d+" }),
});
routes.Add(new Route(string.Format("article{0}/add", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "add" }),
});
routes.Add(new Route(string.Format("article{0}/edit/{{id}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "edit", id = "0" }),
Constraints = new RouteValueDictionary(new { id = "\\d+" }),
});
routes.Add(new Route(string.Format("article{0}/delete/{{id}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "delete", id = "0" }),
Constraints = new RouteValueDictionary(new { id = "\\d+" }),
});
routes.Add(new Route(string.Format("article{0}/{{action}}/{{page}}", WebConfig.MvcHandle), new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "index", page = "1" }),
Constraints = new RouteValueDictionary(new { page = "\\d+" }),
});
routes.Add(new Route("default.aspx", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "index", page = "1" }),
});
routes.Add(new Route("index", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "article", action = "index", page = "1" }),
});
}
其中,routing部分是如”/article/list/{category}/page”的部分,它定义了url的格式,更加清晰表明页面的意义.Defaults是默认的值, Constraints是对参数的正则验证,为了使用Constraints,传入的参数必须是String类型的.
然后到Web.Config中添加HttpModule
<
add
name
="UrlRouting"
type
="System.Web.Routing.UrlRoutingModule, System.Web.Routing"
/>
<
add
name
="UrlRoutingInit"
type
="MvcArticle.Web.Routing.RoutingModule, MvcArticle.Web"
/>
第一个是MVC框架的Routing模块,第二个HttpModule中则是初始化Rouring的(一般初始化在Globle.ascx中,本Demo只是使用了另外一个方法.)
接下来,我们来创建Controllers部分,在Controllers目录下建立ArticleController控制器,该控制器被自动映射在”{controller}=”article”的url上,即所有访问url为article/…/…的url都归该类处理.而该类中的方法被成为Action,自动映射在Url中的Action部分.比如我访问/article/list/1/1实际上执行的是ArticleController类的List方法,而且该方法有两个参数,在本例中是访问了ArticleController.List(int category,int page)方法.默认情况下,Controller类中的所有方法都是Action,这点和Mvc的先前版本不一样,如果某个方法你不想让用户访问到,则可以加上[NonAction]属性.
关于View部分,默认的View页面应该要继承自System.Web.Mvc.ViewPage类,该类有泛型版和非泛型版,很显然,在显示数据的时候优先使用泛型版,比如demo中的list.aspx中, public partial class index : System.Web.Mvc.ViewPage<IList<ArticleContent>>此处就可以看出Controller传给View的数据是IList<ArticleContent>类型的,代码更易于维护.在MVC中,由于没了PostBack的概念,因此服务器控件都失去了交互性,因此在View部分完全可以抛弃服务器控件了,当然,如果想使用表现控件还是可以的,在本Demo中,则是和官方例子一样回归到使用内嵌c#代码的方式显示,此方法虽然对程序可读性有影响,但是在显示程序时候更加灵活.需要说明的是,在继承自ViewPage的类中,ViewData代表了Controller部分传入的数据,比如上面的例子,ViewData就是IList<ArticleContent>的数据,因此可以非常方便的显示出来.同时由于aspx页面只为了显示数据,不包含业务逻辑,维护起来更显方便.
比如在上面的index类中,循环显示ArticleCotent数据的代码如下:
<%
foreach (ArticleContent content in ViewData)
{
foreach (ArticleCategory cate in MvcArticle.Services.ServicesFactory.GetCategoryServices().Get())
{
if (cate.ID == content.CategoryID)
{
content.Category = cate;
}
}
%>
<
div
class
="body_list_item"
>
<
div
class
="item_head"
>
<%
=
Html.ActionLink(content.Title,
"
view
"
,
new
System.Web.Routing.RouteValueDictionary(
new
{controller
=
"
article
"
, id
=
content.ID.ToString()}))
%>
<
span
>
作者:
<%
=
content.PostUser
%>
日期:
<%
=
content.PostTime
%>
</
span
>
</
div
>
<
div
class
="item_body"
>
<%
=
content.Info
%>
</
div
>
<
div
class
="item_bottom"
>
分类:
<%
=
Html.ActionLink(content.Category.Name,
"
list
"
,
new
System.Web.Routing.RouteValueDictionary(
new
{ controller
=
"
article
"
, category
=
content.CategoryID.ToString(), page
=
"
1
"
}))
%>
| Hits:
<%
=
content.Hits
%>
|
<%
=
Html.ActionLink(
"
编辑
"
,
"
edit
"
,
new
System.Web.Routing.RouteValueDictionary(
new
{ controller
=
"
article
"
, id
=
content.ID.ToString() }))
%>
|
<
a
href
="<%=Url.Action("
delete", new System.Web.Routing.RouteValueDictionary(new { controller
= "article"
, id
= content.ID.ToString()
})) %
>
" onclick="if (!window.confirm('是否要删除该文章')) return false;" title="删除该文章">删除
</
a
>
</
div
>
</
div
>
<%
}
%>
由于UrlRouting的Url是层层深入的,因此页面引用其他文件的路径成了一个问题,在MVC框架中,引入了HtmlHelper和UrlHelper类来解决这个问题,在本版的MVC程序中,比如在程序中在本页面中,获取文章所在分类的分页浏览页面的url,使用的是<%=Html.ActionLink(content.Category.Name, "list", new System.Web.Routing.RouteValueDictionary(new { controller = "article", category = content.CategoryID.ToString(), page = "1" }))%>,这儿使用controller,action等数据组合的方式让程序生成url,很好解决了url问题.如果觉得这个方式生成的url不满足要求,则可以使用UrlHelper来自定义,比如上面的删除文章链接,使用的便是<a href="<%=Url.Action("delete", new System.Web.Routing.RouteValueDictionary(new { controller = "article", id = content.ID.ToString() })) %>" onclick="if (!window.confirm('是否要删除该文章')) return false;" title="删除该文章">删除</a>
最后是关于数据的提交,有两个办法,一个是在Controller的方法中访问Request对象,这个Request对象和WebForm中的保持一致,可以获取Form或者QueryString的内容,当时,更省事的方法是直接对Action方法定义参数,比如Demo的Save方法: public void Save(string title, int categoryid, string postuser, string info, string content)明确定义需要给出title等参数,要post给这种方法,你的aspx中相应的要有这些值,如果没有或者名称不同,则不会获取到你需要的值,比如在要调用该方法的add.aspx中,就分别有name为title,categoryid等的html表单,在提交的时候系统会自动将数据提交给Save方法的参数.
程序执行后首页示例:
同步更新地址:http://blog.leven.com.cn/Article_27.aspx
最后给出该Demo的源代码下载(仅MVC实现部分):
http://files.cnblogs.com/leven/public.rar