, LINQ to SQL and ASP.NET AJAX打造一个digg类型的应用程序。
前言
前面一段时间,我一直试着学习新的ASP.NET MVC framework,我看到了很多这方面的高级话题的讨论,比如ioc容器/依赖注入,View Engine,Controller factory等。但是我找不到一篇简单的文章来展现ASP.NET MVC framework的能量。当然,知道那些高级话题确实有一些额外的好处,但是他们并不能对你你在ASP.NET MVC Framework开发上有所帮助。在DotNetSlackers team打造的这篇文章里,我将用asp.net mvc开发一个简单的类似Digg / DotNetKicks类型的程序。你可以在下面的地址里查看这个程序:
【程序演示】
注意:这篇文章和代码都是基于ASP.NET 3.5 Extensions的第一个预览版。每当新的预览出来的时候,我们将相应的更新。
译注:貌似mix08版很快就要出来的样子。
准备
scottgu关于ASP.NET MVC Framework的一套介绍:
ASP.NET MVC 框架 简介
ASP.NET MVC 教程 (第一部分)
ASP.NET MVC 教程 (第二部分: URL路径选择)
ASP.NET MVC 教程 (第三部分: 把ViewData从控制器传到视图)
ASP.NET MVC 教程 (第四部分: 处理表单编辑和提交场景) 译注:以上链接全部换为博客堂scottgu博客中文版链接,并比原文增加了第四部分的链接。
Scott Hanselman的一段很酷的视频教程Screencast。
概述
MVC (模式-视图-控制器)是一套开发ui为中心应用程序中很流行的模式。他建立在一个简单的概念上:把整个应用分割成三个逻辑模块
- Model,模式
- View,视图
- Controller.控制器
ASP.NET MVC Framework是mvc模式的一套实现,并且内置了开发web应用的能力。让我们快速浏览一下这三个模块。
图一:MVC Framework
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9hSFIwY0RvdkwyUnZkRzVsZEhOc1lXTnJaWEp6TG1OdmJTOXBiV0ZuWlhNdllYSjBhV05zWldsdFlXZGxjeTl0ZG1NdWFuQm4%3D.jpg?w=700&webp=1)
- Model: 是你程序的领域逻辑。通常来说,model的状态会存储在数据库里。在开发一个n层应用程序中,他是介于领域模型和商业逻辑之间的中间层。
- View: 典型的用户界面,他负责把model的数据展示给用户,并且接受用户输入。
- Controller: 处理用户的动作。他是整个ASP.NET MVC Framework的终极驱动力量。建立在用户动作之上,他决定了使用怎样的方法到model里取得数据,把这些数据组织成view data并且最终决定使用什么样的View去展示这些数据。
跟web form模型相比,ASP.NET MVC Framework是一套更棒的开发web应用程序的方法。他给我们提供了一下能力:
- 清晰的分层思路,一个模块专注于处理一类问题。并且在开发过程中给了我们绝佳的TDD (测试驱动开发) 体验。在单元测试中我们完全不用顾及其他模块,因为在framework中绝大多数的模块都是interface-based的所以这就允许我们为他们创建模仿对象。
- 整个framework都是非常容易扩展的。可以在不影响其他模块的前提下轻松替换或者自定义每一个模块。
- Pretty/SEO (搜索引擎优化) URLs. URLs的设置和创建权牢牢的掌握在我们手里。跟URL重写彻底说再见吧。
- 真正的无状态网页。我们再也不需要处理postbacks和ViewState了。
- 玩去控制HTML代码产生。这意味着再也没有多余的标签了。
- 可以利用现有的ASP.NET的一些特性,比如Providers, Caching, Configuration等等。
译注:我主要还是喜欢clean url和clean html。
请求流程
在asp.net web form程序中,URLs经常被映射到物理磁盘上的文件。当请求一个url时,于该文件相关的代码将被执行。但是,在ASP.NET MVC Framework中,URLs在Controllers就结束了,而不是传统的物理文件。Routing Handler把URL映射到Controller。当应用程序启动时,他需要注册URL筛选规则。当请求来临时,Routing Handler使用这些规则把请求映射到controller。让我们来快速浏览一下在ASP.NET MVC Framework中请求在不同层之间的流转过程:
图二:The Request Flow
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9hSFIwY0RvdkwyUnZkRzVsZEhOc1lXTnJaWEp6TG1OdmJTOXBiV0ZuWlhNdllYSjBhV05zWldsdFlXZGxjeTl5WlhGMVpYTjBMbXB3Wnc9PQ%3D%3D.jpg?w=700&webp=1)
- 用户请求了一个URL.
- ASP.NET MVC Framework在已经注册的筛选规则中寻找请求的URL所匹配的Controller.Framework把这个请求交给匹配的Controller
- Controller调用Model来创建ViewData。在ViewData的创建过程中可能会多次调用model。
- Model前面提到过他是一个中间层。他也许是一个数据存取模块,工作流,依赖于外部的web service等等。Model把Controller请求的数据返回给他。.
- Controller选择一个View并且把他刚从model里得到的数据发送给View。View展现这些数据,并且生成html给用户浏览。
默认约定
在下一步开发之前,我们必须先知道下面一些默认的约定。
首先,当为进来的请求匹配Controller时,framework使用类似UrlPathController 这样的模式来匹配。例如,如果请求的是 http://www.example.com/Home,那么就要使用HomeController 来处理这个请求。一旦当请求到达Controller,Controller根据子路径来执行一个指定的行为。或者,如果路径中没有行为那么就执行默认的行为。Controller 的默认行为是在application start事件中和筛选规则定义时一起定义的。行为在Controller类中定义为方法。例如,如果请求以下地址http://www.example.com/Home/Index,那么就会自动执行HomeController中的Index方法。如果url中还有子路径,那么就会把子路径中每个部分转换成方法的参数。
其次,当使用Visual Studio建立ASP.NET MVC项目时,会自动建立Controllers, Models和Views这三个文件夹。推荐在对应的文件夹里创建文件。但是,如果你在开发一个很大型的项目,那么你可以把models分离出来放在一个或多个项目中。但是Controllers 和Views必须放在MVC项目中。对于每一个Controller,在Views下都会有名字与他想对应的文件夹。比如,如果有个名叫HomeController的Controller,那么在Views 文件夹里就必然又一个名叫Home 的文件夹。如果多个Controller需要使用同一个view,那么这个view就必须放在views文件夹的共享目录里。这个共享文件夹里也可以包含共享的用户控件,css文件,javascript文件等等。
kigg的相关知识和功能
在动手之前,先让我们探讨一下Digg/DotNetKicks类型程序的一些相关知识。这两个程序都是完全的社区驱动,人们在网上找到他们感兴趣的内容,然后在程序里提交。这些内容会立刻出现在upcoming story队列中。其他用户可以对这些文章投票,一旦投票达到某个数值,他就会出现在首页上。
程序的主要功能如下:
- 所有已经发布的Stories列表.
- 根据Stories的分类进行列表.
- Upcoming Stories列表.
- 根据Stories的标签进行列表.
- 根据Stories的发布用户进行列表.
- 搜索Stories.
- 查看Story的详细内容.
- 允许用户提交新的Story (需要登录)
- 允许用户对Story 进行Kigg (投票) (需要登录)
- 允许用户对Story 进行评论(需要登录)
- 允许用户登录.
- 允许用户注册
- 允许用户重设丢失的密码.
Controllers 和 Actions 的定义
Kigg的功能是和Story和用户有关的。所以我们可以把所有的功能归为以下两类:
-
StoryController
: 处理所有Story的列表搜索提交投票等等。
-
UserController
:处理身份验证,注册,忘记密码等等。
译注:这里的排版稍微变动了一下。把上面一句总述从列表里独立出来了。
建议使用实际的功能名称给Controller Actions命名。下面这些代码给出了StoryController里所以的行为方法:
:
1
.
public
class
StoryController
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
2
.
{
3. //List published stories for all category or for a specific category
4. [ControllerAction]
5. public void Category(string name, int? page)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
6.
{
7. }
8.
9. //List all upcoming stories regardless the category
10. [ControllerAction]
11. public void Upcoming(int? page)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
12.
{
13. }
14.
15. //List Stories for a specific tag
16. [ControllerAction]
17. public void Tag(string name, int? page)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
18.
{
19. }
20.
21. //List Stories Posted by a Specific User
22. [ControllerAction]
23. public void PostedBy(string name, int? page)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
24.
{
25. }
26.
27. //Search the Stories
28. [ControllerAction]
29. public void Search(string q, int? page)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
30.
{
31. }
32.
33. //View the details of a specific story
34. [ControllerAction]
35. public void Detail(int id)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
36.
{
37. }
38.
39. //Submit a Story
40. [ControllerAction]
41. public void Submit(string storyUrl, string storyTitle, int storyCategoryId,
42. string storyDescription, string storyTags)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
43.
{
44. }
45.
46. //Kigg the Story
47. [ControllerAction]
48. public void Kigg(int storyId)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
49.
{
50. }
51.
52. //Post a Comment
53. [ControllerAction]
54. public void Comment(int storyId, string commentContent)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
55.
{
56. }
57. }
下面这些代码片段则展示了UserController里所以的行为方法:
1
.
public
class
UserController
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
2
.
{
3. // Login
4. [ControllerAction]
5. public void Login(string userName, string password, bool rememberMe)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
6.
{
7. }
8.
9. //Logout
10. [ControllerAction]
11. public void Logout()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
12.
{
13. }
14.
15. // Reset the current password and mail back the new password
16. [ControllerAction]
17. public void SendPassword(string email)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
18.
{
19. }
20.
21. //User Registration
22. [ControllerAction]
23. public void Signup(string userName, string password, string email)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
24.
{
25. }
26. }
注意所以的行为方法都定义为public并且使用了ControllerAction 属性。在下一个版本的ASP.NET MVC中这个属性就不再需要了,所有定义为公共的方法将自动成为一个行为方法。
定义筛选规则
一旦Controllers的参数签名确定就应该立刻开始声明把URLs映射到Controllers的行为方法的筛选规则。前面我提到过,这些映射规则在web.config文件中的application start事件中定义。定义筛选规则的时候你要注意,把最特殊的规则放在最上面。这个就跟try/catch块中定义错误处理规则一样,要遵循从特殊到一般的原则。如果你打开Global.asax 文件查看,你会发现我们明确的定义了两个方法来定义这些规则,并且在application start事件中调用方法。这么做是因为我们不想把不同版本iis中使用的规则弄乱。在iis7中有一个很cool的特性,所有的URLs都不需要扩展名,但是在老版本的iis中URLs都需要一个.mvc的扩展名。所以,为了同事支持这个两个版本的iis,我们就要把同一个URL定义两遍,为新的iis定义一套没扩展名的,为旧的iis定义一套有扩展名的。在这里,我们在web.confitg文件里设置当前程序是跑在什么版本的iis中,然后只把当前版本iis用的那套筛选规则读出来。这样做还有一个好处,那就是我们一会要讲的单元测试。下面这些代码展现了筛选规则是如何实现的:
1
.
protected
void
Application_Start(
object
sender, EventArgs e)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
2
.
{
3. RegisterRoutes(RouteTable.Routes);
4. }
public
static
void
RegisterRoutes(RouteCollection routes)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
5
.
{
6. int iisVersion = Convert.ToInt32(ConfigurationManager.AppSettings["IISVersion"]);
7.
8. if (iisVersion >= 7)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
9.
{
10. RegisterRoutesForNewIIS(routes);
11. }
12. else
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
13.
{
14. RegisterRoutesForOldIIS(routes);
15. }
16. }
17
.
private
static
void
RegisterRoutesForNewIIS(ICollection
<
Route
>
routes)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
18
.
{
19. var defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
20.
{
21. controller = "Story",
22. action = "Category",
23. name = (string)null,
24. page = (int?)null
25. };
26.
27. routes.Add(
28. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
29.
{
30. Url = "User/Login",
31. RouteHandler = typeof(MvcRouteHandler),
32. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
33.
{
34. controller = "User",
35. action = "Login"
36. }
37. }
38. );
39.
40. routes.Add(
41. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
42.
{
43. Url = "User/Logout",
44. RouteHandler = typeof(MvcRouteHandler),
45. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
46.
{
47. controller = "User",
48. action = "Logout"
49. }
50. }
51. );
52.
53. routes.Add(
54. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
55.
{
56. Url = "User/Signup",
57. RouteHandler = typeof(MvcRouteHandler),
58. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
59.
{
60. controller = "User",
61. action = "Signup"
62. }
63. }
64. );
65.
66. routes.Add(
67. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
68.
{
69. Url = "User/SendPassword",
70. RouteHandler = typeof(MvcRouteHandler),
71. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
72.
{
73. controller = "User",
74. action = "SendPassword"
75. }
76. }
77. );
78.
79. routes.Add(
80. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
81.
{
82. Url = "Story/Detail/[id]",
83. RouteHandler = typeof(MvcRouteHandler),
84. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
85.
{
86. controller = "Story",
87. action = "Detail"
88. }
89. }
90. );
91.
92. routes.Add(
93. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
94.
{
95. Url = "Story/Upcoming/[page]",
96. RouteHandler = typeof(MvcRouteHandler),
97. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
98.
{
99. controller = "Story",
100. action = "Upcoming"
101. }
102. }
103. );
104.
105. routes.Add(
106. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
107.
{
108. Url = "Story/Search/[q]/[page]",
109. RouteHandler = typeof(MvcRouteHandler),
110. Defaults = new
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
111.
{
112. controller = "Story",
113. action = "Search"
114. }
115. }
116. );
117.
118. routes.Add(
119. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
120.
{
121. Url = "Story/Category/[page]",
122. RouteHandler = typeof(MvcRouteHandler),
123. Defaults = defaults
124. }
125. );
126.
127. routes.Add(
128. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
129.
{
130. Url = "Story/[action]/[name]/[page]",
131. RouteHandler = typeof(MvcRouteHandler),
132. Defaults = defaults
133. }
134. );
135.
136. routes.Add(
137. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
138.
{
139. Url = "[controller]/[action]/[id]",
140. RouteHandler = typeof(MvcRouteHandler),
141. Defaults = defaults
142. }
143. );
144.
145. routes.Add(
146. new Route
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
147.
{
148. Url = "Default.aspx",
149. RouteHandler = typeof(MvcRouteHandler),
150. Defaults = defaults
151. }
152. );
153. }
如你所见,我们把类似User/Login, User/Signup, Story/Detail, Story/Category这类比较特殊的规则放在前面,而把Story/[action], [controller]/[action]这类一般的规则放在后面。当碰到变量时候,我们用[]来表示。MVC framework有两个固定的变量名:[controller]和[action],其他的就用controller里行为方法的变量名来命名。最后一个规则我们把default.aspx映射到所有分类列表来处理路径/。
测试筛选规则
当上面那些筛选规则定义好以后,我们就应该立刻开始测试。这样就可以帮助我们确定现有的筛选规则能否很好的映射所有的controller行为和URL中传递的方法是否正确。下面这个表列出了我们想测试的所有映射规则:
Table 1: Tests
Functionality |
Url Format |
Controller |
Action |
Login登录 |
User/Login |
UserController |
Login |
SendPassword发送密码 |
User/SendPassword |
UserController |
SendPassword |
Signup注册 |
User/Signup |
UserController |
Signup |
Logout注销 |
User/Logout |
UserController |
Logout |
List All Published Story列表所有发布的Story |
Story/Category Story/Category/[page] |
StoryController |
Category |
List Published Stories for a specific Category 列表某一类别里所有发布的Storys |
Story/Category/[categoryName] Story/Category/[categoryName]/[page] |
StoryController |
Category |
List Upcoming Stories 列表Upcoming Stories |
Story/Upcoming Story/Upcoming/[page] |
StoryController |
Upcoming |
List Stories for a specific Tag 列表某一标签里所有Stories |
Story/Tag/[tagName] Story/Tag/[tagName]/[page] |
StoryController |
Tag |
List Stories Posted By an User 列表某一用户发布的Stories |
Story/PostedBy/[userName] Story/PostedBy/[userName]/[page] |
StoryController |
PostedBy |
Search Stories 查询Stories |
Story/Search?q=query Story/Search/[q]/[page] |
StoryController |
Search |
View Details of a Story查看一个Storie的详细信息 |
Story/Detail/[storyID] |
StoryController |
Detail |
Submit a Story提交一个Story |
Story/Submit |
StoryController |
Submit |
Vote a Story给一个Story投票 |
Story/Kigg |
StoryController |
Kigg |
Post a Comment发布评论 |
Story/Comment |
StoryController |
Comment |
你可以在测试项目里找到我们测试所使用的Route.cs文件。我们同时创建了VSTSTest和NUnit两个版本的单元测试。我们用了Rhino Mocks来制作模仿对象。这些测试Phil Haack几个礼拜之前发布的Testing Routes In ASP.NET MVC一文里部分代码创建的。
译注:模仿对象是单元测试时常用的一种方法。
下面这些代码片段测试了筛选规则:
1
. [TestInitialize]
2
.
public
void
Init()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
3
.
{
4. routes = new RouteCollection();
5. Global.RegisterRoutes(routes);
6.
7. mocks = new MockRepository();
8. }
9
.
10
. [TestMethod]
11
.
public
void
VerifyDefault()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
12
.
{
13. IHttpContext httpContext;
14.
15. using (mocks.Record())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
16.
{
17. httpContext = GetHttpContext(mocks, "~/Default.aspx");
18. }
19.
20. using (mocks.Playback())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
21.
{
22. RouteData routeData = routes.GetRouteData(httpContext);
23.
24. Assert.IsNotNull(routeData);
25. Assert.AreEqual("Story", routeData.Values["Controller"]);
26. Assert.AreEqual("Category", routeData.Values["action"]);
27. }
28. }
29
.
30
. [TestMethod]
31
.
public
void
VerifyAllCategory()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
32
.
{
33. IHttpContext httpContext;
34.
35. using (mocks.Record())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
36.
{
37. httpContext = GetHttpContext(mocks, "~/Story/Category/20");
38. }
39.
40. using (mocks.Playback())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
41.
{
42. RouteData routeData = routes.GetRouteData(httpContext);
43.
44. Assert.IsNotNull(routeData);
45. Assert.AreEqual("Story", routeData.Values["Controller"]);
46. Assert.AreEqual("Category", routeData.Values["action"]);
47. Assert.IsNull(routeData.Values["name"]);
48. Assert.AreEqual("20", routeData.Values["page"]);
49. }
50. }
实现UserController
前面我们定义了UserController的签名,现在我们就来具体实现他。UserController使用了ASP.NET Membership provider来实现登录、注册和其他一些功能。这个controller和其他controller唯一的区别就是这个controller里所以方法都返回一个JSON数据而不是HTML输出。客户端通过ASP.NET AJAX Framework来调用这个controller里的方法。下面这些代码是Login方法的实现:
1
. [ControllerAction]
2
.
public
void
Login(
string
userName,
string
password,
bool
rememberMe)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
3
.
{
4. using (new CodeBenchmark())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
5.
{
6. JsonResult result = new JsonResult();
7.
8. if (string.IsNullOrEmpty(userName))
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
9.
{
10. result.errorMessage = "User name cannot be blank.";
11. }
12. else if (string.IsNullOrEmpty(password))
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
13.
{
14. result.errorMessage = "Password cannot be blank.";
15. }
16. else if (!UserManager.ValidateUser(userName, password))
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
17.
{
18. result.errorMessage = "Invalid login credentials.";
19. }
20. else
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
21.
{
22. //The following check is required for TDD
23. if (HttpContext != null)
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
24.
{
25. FormsAuthentication.SetAuthCookie(userName, rememberMe);
26. }
27.
28. result.isSuccessful = true;
29. }
30.
31. RenderView("Json", result);
32. }
33. }
如你所见,我们在方法开头就创建了一个JsonResult对象。JsonResult是一个用来反馈controller行为是否成功的简单类,他只有两个属性isSuccessful和errorMessage, errorMessage。如果操作不成功,就把失败原因存在errorMessage里。在结尾处,我们把结果当作一个名为Json的共享视图里的view data返回。因为是一个共享视图,所以他可以被UserController和StoryController使用。我们把他放在了Views里名为Shared 的文件夹里。这个controller 里的其他方法工作原理都跟这个十分相似。我这里需要提到的一件重要的事情是,我们在构造函数里传递了一个抽象的membership provider来代替静态的Membership 类。这样做是因为在单元测试中我们可以传递一个模仿的Membership Provider,我们将在下一小节展示。在另一个构造函数中,我们传递web.config中定义的默认membership provider。
测试UserController
为了测试这个Controller,我们仍然使用Phil Haack前几周在Writing Unit Tests For Controller Actions一文中的方法。前面一小节说过,我们把一个模仿的Membership Provider传递过去来测试这个controller。我们预想在controller 调用这个membership provider然后能够得到正确的数据并发送给view。下面这些代码展示了正确的登录和当用户名为空时错误的登录:
1
. [TestInitialize]
2
.
public
void
Init()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
3
.
{
4. mocks = new MockRepository();
5. userManager = mocks.PartialMock<MembershipProvider>();
6. controller = new UserControllerForTest(userManager);
7. }
8
.
9
. [TestMethod]
10
.
public
void
ShouldLogin()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
11
.
{
12. using(mocks.Record())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
13.
{
14. Expect.Call(userManager.ValidateUser(DefaultUserName, DefaultPassword)).IgnoreArguments().Return(true);
15. }
16.
17. using(mocks.Playback())
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtVM1ZpUW14dlkydFRkR0Z5ZEM1bmFXWT0%3D.jpg?w=700&webp=1)
18.
{
19.
20. controller.Login(DefaultUserName, DefaultPassword, true);
21. }
22.
23. Assert.AreEqual(controller.SelectedView, "Json");
24. Assert.IsInstanceOfType(controller.SelectedViewData, typeof(JsonResult));
25. Assert.IsTrue(((JsonResult)controller.SelectedViewData).isSuccessful);
26. Assert.IsNull(((JsonResult)controller.SelectedViewData).errorMessage);
27. }
28
.
29
. [TestMethod]
30
.
public
void
ShoudNotLoginForEmptyUserName()
![[翻译]使用asp.net mvc再造一个digg 第一部分 [翻译]使用asp.net mvc再造一个digg 第一部分](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9nby9MMGx0WVdkbGN5OVBkWFJzYVc1cGJtZEpibVJwWTJGMGIzSnpMMFY0Y0dGdVpHVmtRbXh2WTJ0VGRHRnlkQzVuYVdZPQ%3D%3D.jpg?w=700&webp=1)
31
.
{
32. controller.Login(string.Empty, DefaultPassword, false);
33.
34. Assert.AreEqual(controller.SelectedView, "Json");
35. Assert.IsInstanceOfType(controller.SelectedViewData, typeof(JsonResult));
36. Assert.IsFalse(((JsonResult)controller.SelectedViewData).isSuccessful);
37. Assert.AreEqual(((JsonResult)controller.SelectedViewData).errorMessage, "User name cannot be blank.");
38. }
综述
我最初想用一篇文章搞定所有问题,但是你也发现了,这篇文章实在是太长了。
译注:确实长的可以。
在这篇文章中,我们首先简单了解了一下ASP.NET MVC Framework,然后讲解了如何在controllers里定义功能,如何定义筛选规则并通过URLs测试他们,我们也看见了如何在Controller 中使用JSON 数据来代替完整的HTML视图。在本文的下一个部分里,我们讲着重讲解Controller,如何展示完整的HTML视图,使用master pages和user controls来创建视图,给视图发送强类型的view data和最后创建Model。就此停笔。