ASPNET MVC4 都是Action的控制器(Actions-packed Controllers)
--我沒有自信是否能把本次的任務(wù)完成,但我會(huì)負(fù)責(zé)任的完成
本人能力有限,盡量將書中的知識濃縮去講,仔細(xì)學(xué)過后,然后你再學(xué)習(xí)其他語言的MVC框架也就大同小異了
本次覆蓋知識點(diǎn):
在最近兩章里,我們看了一下創(chuàng)建一個(gè)Guestbook應(yīng)用程序基礎(chǔ)知識,也看了幾種不同的把數(shù)據(jù)傳遞給視圖的可以實(shí)現(xiàn)的方式。在本章里,我們將會(huì)完成Guestbook例子,我們只添加一點(diǎn)點(diǎn)細(xì)節(jié)部分。我們將研究什么應(yīng)該是controller中的一部分,什么不應(yīng)該是,然后看一下怎樣手動(dòng)構(gòu)造一個(gè)視圖models,驗(yàn)證用戶的輸入,寫幾個(gè)不會(huì)使用到視圖的控制器操作(action)。這個(gè)將會(huì)讓我們在controller中建造一個(gè)很常用action塊(block),也就是不返回view類型action代碼,我們以前學(xué)的都是返回一個(gè)view(),這里就不是的了。
我們也將向你簡單地介紹一下單元測試controller中的action,這樣可以確保這些action可以正確的工作。我們將會(huì)使用默認(rèn)的單元測試項(xiàng)目,然后為GuestbookController創(chuàng)建單元測試,測試前面幾章的工作情況。
在我們學(xué)習(xí)這些新知識之前,我們先快速地概括一下controllers和actions
4.1 探討Controllers和actions
備注:controller action指controller中的action(也就是你們經(jīng)常寫的方法而已)
controller actions指controller中全部actions
action名字加action表示 某某名字的action,例如有個(gè)叫Index的action,我就會(huì)這樣表示
Index action.知道了嗎?這方便我寫博客,也更方便理解
view model指頁面(view)中用到model,我上篇博客說的視圖模型就是view model
action method指 返回值是ActionResult那個(gè)方法,比如Index那個(gè)action方法
user interface,用戶接口,你可以意會(huì)理解成頁面,也就是要跟用戶交互的東西,比如我們給用提供一個(gè)頁面,讓用戶錄入數(shù)據(jù),這就是一個(gè)interface,一個(gè)接口,你暫且在這里就是view,就是頁面
business logic,商業(yè)邏輯,類似于數(shù)據(jù)訪問層中業(yè)務(wù)實(shí)現(xiàn)的邏輯
domain,你可以理解數(shù)據(jù)訪問層
domain model,數(shù)據(jù)訪問層要使用到的model
以后我想直接寫英文了,好方便理解,在上一波博客我是吃過苦了,文章讀起來好拗口
在第一波里面我們看出來了Controller的重要性,它里面有很多方法(action),這些action都可以通過url實(shí)現(xiàn)觸發(fā)調(diào)用.而這些action就是把model中的數(shù)據(jù)傳到頁面上的中間者,也可以理解為搬運(yùn)工吧,所以理解action是怎樣工作的是很重要的.在下一節(jié),我們主要更詳細(xì)地了解下controller actions是怎樣工作的.
下面我們看下action的樣子(GuestbookController中的Index action)
它繼承了Controller,也含有默認(rèn)的action,我們也知道所有的控制器必須繼承Controller,其實(shí)框架允許我們只要至少實(shí)現(xiàn)IController否則,它是不能處理Web請求的
下面我們看看框架是怎么知道哪個(gè)class是個(gè)被當(dāng)做controller來處理,因?yàn)閏ontroller說白了也是一個(gè)類而已.下面讓我們看看
IController
4.1.1 IController和Controller 父類(base class)
IController定義了一個(gè)controller最根本的東東---Execute方法接受一個(gè)RequestContext類型的對象
下面是一個(gè)簡單的controller,我們寫一些html放到響應(yīng)流里面(response stream)
我們手動(dòng)實(shí)現(xiàn)一下:
這是一個(gè)實(shí)現(xiàn)了IController的控制器,我們實(shí)現(xiàn)了Execute方法,我們就能直接訪問HttpContext,Request還有Response對象了.這種方式很容易定義但是沒什么用.這樣做,我們就無法直接呈現(xiàn)view.而且我們這樣做---在controller中直接就寫HTML,把邏輯和呈現(xiàn)的內(nèi)容混在一起了,很難受.
好吧,我們先不看框架那些有用的特征,比如說安全性(第8章會(huì)講),model binding(第10章),action results(第16章).
這樣做,我們也喪失了定義action方法的能力----------因?yàn)樗械恼埱蠖急籈xecute處理了
實(shí)際上,你需要去實(shí)現(xiàn)IController也不太可能那樣去做,因?yàn)樗旧頉]什么用(避開框架的主要部分,有一些理由能讓你覺的還是有用的).通常我們會(huì)繼承父類----------ControllerBase和Controller
ControllerBase
ControllerBase類除了包含了我們已經(jīng)看過的一些基本特征以外,它本身就直接實(shí)現(xiàn)了IController.舉個(gè)例子:ControllerBase也包含ViewData屬性(把數(shù)據(jù)出給view的一個(gè)手段).但是ControllerBase仍然還是不怎么有用-----它仍然還是不能通過使用action來呈現(xiàn)view.所以Controller來了
Controller
Controller繼承ControllerBase,所以除了包含一些有意義的東西以外,它還包含了ControllerBase定義的東東(比如ViewData).它包含了ControllerActionInvoker(這是一個(gè)知道怎樣根據(jù)url找到對應(yīng)的方法(method),并執(zhí)行了那個(gè)方法,它定義了一些方法,比如View(可以通過controller action來呈現(xiàn)一個(gè)view))
這是一個(gè)當(dāng)你開始創(chuàng)建好你的controller的時(shí)候會(huì)自動(dòng)繼承的類,所以說直接繼承ControllerBase或者IController都不會(huì)有什么幫助.但是我們知道了在MVC 管道(pipeline)模型中扮演很重要的角色,知道它們的存在還是很有用的.
好了,現(xiàn)在我們已經(jīng)知道了怎樣把一個(gè)類變成一個(gè)Controller.下面我們看看action
4.1.2 怎樣才可以成為一個(gè)action method
在第二章(我的第一波博客,我從第二章開始講的,第一章是介紹,以后我會(huì)按書里面的章節(jié)順序講,還希望諒解)我們 在controller中看到了一些public的action方法(事實(shí)上,決定一個(gè)方法能否成為一個(gè)action,是有條規(guī)律規(guī)則的,講起來還是蠻復(fù)雜的,我們將會(huì)在第16章開始講解).
平常呢,action method都是返回一個(gè)ActionResult的一個(gè)實(shí)例.舉個(gè)例子,一個(gè)action返回值可以是void類型的,也可以直接輸出HTML(很像我們剛剛的SimpleController)
我們也可以這樣寫,它們結(jié)果是一樣的,返回一個(gè)HTML片段(snippet)
這個(gè)沒有問題,因?yàn)镃ontrollerActionInvoker它保證了action的返回值總是ActionResult.如果action返回的是一個(gè)ActionResult(例如ViewResult),ControllerActionInvoker就會(huì)被調(diào)用.但是如果action返回的一個(gè)不同的類型(就像這里,一個(gè)string),它的返回值是ContentObject對象(也是一個(gè)把要寫入的東西放入響應(yīng)流里面的東東),直接使用ContentResult也是一樣的
這就是一個(gè)很簡單的action了,不需要view就可以直接在瀏覽器中呈現(xiàn)HTML標(biāo)簽了。但是在真實(shí)世界的應(yīng)用程序中通常不會(huì)這樣用的,還是最好通過view把要呈現(xiàn)的頁面和controller分開,這樣做當(dāng)我們改變user interface的時(shí)候,就不用動(dòng)controller中的代碼了
除了呈現(xiàn)標(biāo)簽或者返回一個(gè)view,還有其他幾種類型,舉個(gè)例子,我們可以使用RedirectToRouteResult,讓用戶在瀏覽的時(shí)候跳到其他頁面(我們在第二章(我的第一波博客)中的RedirectToAction方法你已經(jīng)使用過了,還記得嗎?),我們也可以返回JSON(在第七章的AJAX中我們將會(huì)學(xué)到)
我們可以使用NonActionAttribute讓一個(gè)controller中的public的action不是一個(gè)action。
NonActionAttribute是一個(gè)action方法選擇器(action method selector),它可以重寫 匹配一個(gè)方法名到一個(gè)action名的默認(rèn)行為(behavior).NonActionAttribute是最簡單的一個(gè)選擇器,它可以使那些可以通過URL訪問的方法不可以通過這種手段來訪問,其實(shí)在第二章,我們也已經(jīng)看到了一些選擇器,比如HttpPostAttribute(可以確保這個(gè)action可以響應(yīng)HTTP post形式的請求,其他方式都是不可以訪問的)
提醒
NonActionAttribute很少使用的,在一個(gè)controller中一個(gè)public的方法你不想它成為action,你最好想一下controller中是不是它最好的地方,如果這個(gè)方法有用,你可能會(huì)寫成private訪問修飾符級別的。如果這個(gè)方法必須public,因?yàn)樾枰獪y試的原因,我覺得應(yīng)該提取成一個(gè)單獨(dú)的類
現(xiàn)在讓我簡單地看一下什么構(gòu)成了action,你將會(huì)看到一些不同的方式,把內(nèi)容放到瀏覽器中顯示。除了view,你也可以直接發(fā)送content(內(nèi)容),或者直接執(zhí)行其他的action(重定向),這些技術(shù)在你的應(yīng)用程序中都會(huì)很有用。
現(xiàn)在讓我們?nèi)タ匆幌耡ction中的邏輯
4.2 在action method中有什么
MVC中最大的兩點(diǎn)就是將各自的職責(zé)分離,user interface 和 邏輯(比如頁面提交數(shù)據(jù)怎么處理)分開,因此整個(gè)程序就更容易維護(hù)(maintain)了。如果你沒有讓你的controller更精巧(lightweight實(shí)際是輕量級的意思),把重點(diǎn)都放在controller里,你就不會(huì)體會(huì)到它的好處了。
Controller應(yīng)該扮演中間者(coordinator)--它不應(yīng)該包含business logic。反之亦然(vice versa),它可以把view上的表單,用戶的輸入的數(shù)據(jù)(user input),來自view的,封裝成一個(gè)對象,然后我們把這個(gè)對象在domain(實(shí)際business logic代碼寫
在的地方)層中使用,這樣我們數(shù)據(jù)的存儲(chǔ)都可以完成了。
讓我們看一下通過controller如何把一個(gè)任務(wù)完成--手動(dòng)的映射view models,接受用戶的輸入的數(shù)據(jù)。首先,展現(xiàn)的是如何匹配view models,現(xiàn)在我們打開我們的guestbook例子,添加一個(gè)新的頁面, 用不同的方式顯示數(shù)據(jù),存數(shù)據(jù)就先放著。接下來我們在這個(gè)頁面上,把數(shù)據(jù)添加到數(shù)據(jù)庫之前先添加一些驗(yàn)證,因?yàn)槲覀兊臄?shù)據(jù)庫不需要存儲(chǔ)一些沒有用的(invalid)數(shù)據(jù)。在本節(jié)結(jié)束之后,你應(yīng)該在知道怎樣構(gòu)建一個(gè)view model了,怎樣完成基本的input驗(yàn)證,應(yīng)該有個(gè)大致的了解了
4.2.1 手動(dòng)地去匹配view models(view models 指view使用到的model)
在第三章(上一波博客),我們已經(jīng)有了強(qiáng)類型視圖的印象,還有一個(gè)view model--為了能夠把數(shù)據(jù)更好地顯示在屏幕上,創(chuàng)建的而一個(gè)單獨(dú)的model對象。
到目前為止,我們做的例子中,我們使用到了同樣的類(GuestbookEntry)作為我們的domain model和view model--它展現(xiàn)了我們數(shù)據(jù)庫中的信息,也是在user interface上要表示的字段,信息
在非常小的項(xiàng)目中使用,比如我們的guestbook,這還可以,但是隨著程序越來越復(fù)雜,用戶界面也復(fù)雜了,model中的數(shù)據(jù)已經(jīng)不能直接匹配了,就有必要要分成兩部分(題外話:我現(xiàn)在自己公司做的項(xiàng)目就是兩部分,一個(gè)EF生成的實(shí)體,一個(gè)擴(kuò)充的實(shí)體,都已Dto結(jié)尾,只是為了更方便的顯示數(shù)據(jù)),正是由于這個(gè),我們應(yīng)該有能力可以把domain model轉(zhuǎn)換成view model,讓數(shù)據(jù)更容易在user interface顯示
我們就寫個(gè)例子吧,向我們的Guestbook項(xiàng)目上添加一個(gè)頁面:做一個(gè)總結(jié)頁面,知道每一個(gè)人發(fā)了多少個(gè)comment,首先我們創(chuàng)建一個(gè)view model(view model專門讓頁面好展現(xiàn)數(shù)據(jù),就看你怎么設(shè)計(jì)了)
在Models文件下建立CommentSummary.cs
我們現(xiàn)在需要?jiǎng)?chuàng)建一個(gè)controller action----返回一個(gè)CommentSummary集合,我們在GuestbookController中寫action
1: public ActionResult CommentSummary()
2: {
3: var entries = from entry in _db.Entries
4: group entry by entry.name into groupByName
5: orderby groupByName.Count() descending
6: select new CommentSummary
7: {
8: NumberOfComments = groupByName.Count(),
9: UserName = groupByName.Key
10: };
11: return View(entries.ToList());
12:
13: }
我們使用linq查詢,不懂的可以看一下我的 小孩系列LINQ教程
這個(gè)映射(mapping)邏輯相當(dāng)簡單,把這些代碼寫在控制器你還是可以講得通的,但是如果這個(gè)mapping變得更加復(fù)雜(舉個(gè)例子,如果它取了很多來自不同數(shù)據(jù)源的信息,只是為了構(gòu)造view model),我們應(yīng)該把這些邏輯從controller action中移出,分離(就像以前的三層框架),讓我們的controller更加簡單,輕量級的
在view中,我們循環(huán)輸出這個(gè)列表,用table展現(xiàn)
右鍵該action名稱,保持默認(rèn),添加一個(gè)視圖
1: @model IEnumerable<GuestInfo.Models.CommentSummary>
2:
3: <table>
4: <tr>
5: <th>Number of comments</th>
6: <th>User name</th>
7: </tr>
8: @foreach(var summaryRow in Model) {
9: <tr>
10: <td>@summaryRow.NumberOfComments</td>
11: <td>@summaryRow.UserName</td>
12: </tr>
13: }
14: </table>
自動(dòng)匹配View models
除了手動(dòng)地把domain object和view models進(jìn)行映射(mapping)以外,你也可以使用工具,比如開源的AutoMapper,像這個(gè)目的,更少的代碼就可以完成了,我們將在第11章中MVC項(xiàng)目中教你怎么使用AutoMapper
在這一節(jié),我們已經(jīng)很簡短地看了一點(diǎn)view model,但是在下一章我們將要更仔細(xì)地看,我們當(dāng)然也會(huì)研究view models和input models的不同點(diǎn)
除了mapping操作,controller中還有個(gè)經(jīng)常用到的任務(wù),驗(yàn)證用戶輸入
4.2.2 驗(yàn)證用戶輸入(input validation)
回到第二章,我們在GuestbookController.cs中的Create action接受了用戶的input(輸入)。
這個(gè)action,接受了Create.cshtml post過來的input的數(shù)據(jù)(一個(gè)GuestbookEntry對象(已經(jīng)被MVC模型綁定(model-binding)機(jī)制處理了)的表單),設(shè)置一下日期,然后我們Insert一條數(shù)據(jù)到數(shù)據(jù)庫,雖然已經(jīng)完成了,但還沒有真的完成--我們還沒有任何的驗(yàn)證。此時(shí),用戶是可以不輸入他們的name或者comment就可以提交(submit)數(shù)據(jù)的。現(xiàn)在讓我們添加一些驗(yàn)證。
首先讓我們在GuestbookEntry類中用required特性 注解一下 Name和Message,使用using System.ComponentModel.DataAnnotations;導(dǎo)入命名空間
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Web;
6:
7: namespace GuestInfo.Models
8: {
9: public class GuestbookEntry
10: {
11: public int Id { get; set; }
12: [Required]
13: public string name { get; set; }
14: [Required]
15: public string Message { get; set; }
16: public DateTime DateAdded { get; set; }
17:
18: }
19: }
使用注解(Annotate),也是一種驗(yàn)證對象屬性的方法,Required表示這個(gè)filed不能為空,還有一些其他注解,比如StringLengthAttribute:驗(yàn)證字符串長度的。(我們將會(huì)在第6章更深入學(xué)習(xí)一下)
一旦注解了,當(dāng)Create action被調(diào)用的時(shí)候,MVC將會(huì)自動(dòng)地驗(yàn)證這些屬性,看是否驗(yàn)證通過,我們可以使用ModelState.IsValid屬性,然后就可以做決定了,怎么處理。修改一下Create
1:
2: [HttpPost]//限制只能是HTTP Post 能夠訪問這個(gè)方法
3: public ActionResult Create(GuestbookEntry entry)
4: {
5: if (ModelState.IsValid)
6: {
7: entry.DateAdded = DateTime.Now;
8: _db.Entries.Add(entry);
9: _db.SaveChanges();
10: return RedirectToAction("Index");
11: }
12: return View(entry);
13: }
注意
調(diào)用ModelState.IsValid實(shí)際上不會(huì)進(jìn)行表單驗(yàn)證,它只是檢查驗(yàn)證是失敗還是成功。驗(yàn)證發(fā)生在Controller action被調(diào)用之前
我們可以調(diào)用Html.ValidationSummary方法,當(dāng)驗(yàn)證失敗的時(shí)候,顯示錯(cuò)誤信息
運(yùn)行頁面,還沒輸入任何內(nèi)容點(diǎn)擊提交:
使用這些助手,MVC將會(huì)自動(dòng)檢測驗(yàn)證錯(cuò)誤的信息,還應(yīng)用了一個(gè)css樣式,因?yàn)槲覀兊某绦蚴腔谀J(rèn)的MVC項(xiàng)目模版上寫的,這個(gè)無效的信息,將會(huì)顯示淺紅色背景色
這個(gè)錯(cuò)誤的信息內(nèi)容你可以這樣改,在Required加參數(shù)
同樣地如果你不想 硬編碼消息,想要依賴于通過資源文件,支持本地化,你可以指定ResourceName和Resource type
例如:
1: [Required(ErrorMessageResourceType=typeOf(MyResources),ErrorMessageResourceName="RequiredMessageError")]
2: public string Message { get; set; }
4.3 單元測試介紹
在這一節(jié),我們將要簡短地看一下測試controllers。有很多測試的方法,這里我們主要講:unit testing
單元測試很小,腳本測試,使用的語言和你項(xiàng)目中的語言一樣。為了驗(yàn)證某個(gè)方法對不對,能不能達(dá)到想要的效果就去單獨(dú)隔離式地測試某個(gè)組件的方法或函數(shù)。隨著程序的變大,單元測試也會(huì)變多,看到一個(gè)應(yīng)用程序有成百甚至上千個(gè)單元測試都是很正常的,它們可以在任何時(shí)間執(zhí)行,去驗(yàn)證bug,讓bug不會(huì)再發(fā)生
為了讓單元測試運(yùn)行地更快,我們不能調(diào)用進(jìn)程外面的東西,這點(diǎn)很重要。當(dāng)測試一個(gè)controller中的代碼,任何獨(dú)立的部分都是模擬的,所以主要的產(chǎn)品代碼還是controller它本身。針對這個(gè)有一種可能,控制器被設(shè)計(jì)成獨(dú)立的部分,很容易就被調(diào)用(比如說database或者web服務(wù))
為了更高效地測試我們的GuestbookController,為了允許測試,我們要做一些修改,但是在我們做這些事情之前,讓我們看一下ASP.NET MVC中默認(rèn)的單元測試模版
4.3.1 使用默認(rèn)的單元測試模版
默認(rèn)地,當(dāng)你創(chuàng)建一個(gè)新的ASP.NET MVC項(xiàng)目的時(shí)候,VS會(huì)提供一個(gè)創(chuàng)建單元測試項(xiàng)目的選項(xiàng)(在第二章我們已經(jīng)見過了)
如果你選擇一個(gè)創(chuàng)建單元測試項(xiàng)目,vs將會(huì)生成一個(gè)using Visual Studio Unit Testing FrameWork。這個(gè)單元測試項(xiàng)目包括了一些示例的測試,比如在HomeControllerTest類可以看見
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Web.Mvc;
6: using Microsoft.VisualStudio.TestTools.UnitTesting;
7: using GuestBook;
8: using GuestBook.Controllers;
9:
10: namespace Guestbook.Tests.Controllers
11: {
12: [TestClass]
13: public class HomeControllerTest
14: {
15: [TestMethod]
16: public void Index()
17: {
18: // 排列
19: HomeController controller = new HomeController();
20:
21: // 操作
22: ViewResult result = controller.Index() as ViewResult;
23:
24: // 斷言
25: Assert.AreEqual("修改此模板以快速啟動(dòng)你的 ASP.NET MVC 應(yīng)用程序。", result.ViewBag.Message);
26: }
27:
28: [TestMethod]
29: public void About()
30: {
31: // 排列
32: HomeController controller = new HomeController();
33:
34: // 操作
35: ViewResult result = controller.About() as ViewResult;
36:
37: // 斷言
38: Assert.IsNotNull(result);
39: }
40:
41: [TestMethod]
42: public void Contact()
43: {
44: // 排列
45: HomeController controller = new HomeController();
46:
47: // 操作
48: ViewResult result = controller.Contact() as ViewResult;
49:
50: // 斷言
51: Assert.IsNotNull(result);
52: }
53: }
54: }
看代碼,跟以前其他 客戶端形式的單元測試還是很像的,很好就學(xué)會(huì)了,這里我就不多說了
4.3.2 測試GuestbookController
有一些事情,GuestbookController的實(shí)現(xiàn)直接初始化,使用了GuestContext對象,這個(gè)對象訪問數(shù)據(jù)庫。這就是說沒有數(shù)據(jù)庫的前提下,要想正確的獲得數(shù)據(jù),這個(gè)測試不可能進(jìn)行,這是一個(gè)集成測試(Integration Test),而不是單元測試了
盡管集成測試很重要,它能確保一個(gè)應(yīng)用程序的組件是否正確的在配合,也就是說如果我們對在controller中測試這個(gè)邏輯感興趣,我們不得不先測試數(shù)據(jù)庫連接。在小數(shù)量的測試,這個(gè)可能,但是如果一個(gè)項(xiàng)目有成百上千個(gè)測試,執(zhí)行時(shí)間明顯下降,因?yàn)槊恳粋€(gè)都要連接數(shù)據(jù)庫。這個(gè)問題的解決方案是減弱控制器中的GuestbookContext對象
不直接使用GuestbookContext,我們介紹一個(gè)repository,它提供了一個(gè)入口,在處理數(shù)據(jù)訪問操作,操作GuestbookEntry對象,我們?yōu)槲覀兊膔epository寫個(gè)接口
這個(gè)接口定義了4個(gè)方法,匹配4個(gè)查詢,對應(yīng)GuestbookController
我們定義了包含查詢邏輯的概念上的實(shí)現(xiàn)
1: using GuestInfo.Models;
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Text;
6: using System.Threading.Tasks;
7:
8: namespace Guestbook.Contract
9: {
10: public class GuestbookRepository : IGuestbookRepository
11: {
12: private GuestbookContext _db = new GuestbookContext();
13:
14: public IList<GuestbookEntry> GetMostRecentEntries()
15: {
16: return (from entry in _db.Entries
17: orderby entry.DateAdded descending
18: select entry).Take(20).ToList();
19: }
20:
21: public void AddEntry(GuestbookEntry entry)
22: {
23: entry.DateAdded = DateTime.Now;
24: _db.Entries.Add(entry);
25: _db.SaveChanges();
26:
27: }
28:
29: public GuestbookEntry FindById(int id)
30: {
31: var entry = _db.Entries.Find(id);
32: return entry;
33: }
34:
35: public IList<CommentSummary> GetCommentSummary()
36: {
37: var entries = from entry in _db.Entries
38: group entry by entry.Name into groupedByName
39: orderby groupedByName.Count() descending
40: select new CommentSummary
41: {
42: NumberOfComments = groupedByName.Count(),
43: UserName = groupedByName.Key
44: };
45: return entries.ToList();
46: }
47: }
48: }
接下來我們在GuestbookController中添加一些代碼,把接口引進(jìn)來
修改代碼:
using Guestbook.Contract;
using GuestInfo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace GuestInfo.Controllers
{
public class GuestbookController : Controller
{
private GuestbookContext _db = new GuestbookContext();
IGuestbookRepository _respository;
public GuestbookController() {
_respository = new GuestbookRepository();
}
public GuestbookController(IGuestbookRepository gu)
{
_respository = gu;
}
//
// GET: /Guestbook/
public ActionResult Index()
{
////Shift+Tab減少縮進(jìn),Tab增加縮進(jìn)
//var mostRecentEntries = (from o in _db.Entries orderby o.DateAdded descending select o).Take(20);
//// ViewBag.Entries = mostRecentEntries.ToList();
//var model = mostRecentEntries.ToList();
var mostRecent = _respository.GetMostRecentEntries();
return View(mostRecent);
}
public ActionResult Create()
{
return View();
}
[HttpPost]//限制只能是HTTP Post 能夠訪問這個(gè)方法
public ActionResult Create(GuestbookEntry entry)
{
if (ModelState.IsValid)
{
//entry.DateAdded = DateTime.Now;
//_db.Entries.Add(entry);
//_db.SaveChanges();
_respository.AddEntry(entry);
return RedirectToAction("Index");
}
return View(entry);
}
public ActionResult Show(int id)
{
//var entry = _db.Entries.Find(id); //找到該Id的Entries
var entry = _respository.FindById(id);
bool hasPermission = User.Identity.Name == entry.name; //如果登陸人的姓名等于entry錄入人的姓名,就顯示Edit按鈕
ViewData["hasPermission"] = hasPermission;
ViewBag.hasPermission = hasPermission;
return View(entry);
}
public ActionResult CommentSummary()
{
//var entries = from entry in _db.Entries
// group entry by entry.name into groupByName
// orderby groupByName.Count() descending
// select new CommentSummary
// {
// NumberOfComments = groupByName.Count(),
// UserName = groupByName.Key
// };
var entries = _respository.GetCommentSummary();
return View(entries.ToList());
}
}
}
雖然我們已經(jīng)把邏輯查詢部分移出了controller,但是仍然需要查詢和測試。它不再是單元測試的一部分,而是一個(gè)集成測試,鍛煉概念上的respository實(shí)例訪問數(shù)據(jù)庫。
依賴注入(Dependence Injection)
調(diào)把依賴性傳進(jìn)對象的構(gòu)造函中的技術(shù)被稱為依賴注入,我們已經(jīng)手動(dòng)地完成了依賴注入,通過在我們的類中添加很多構(gòu)造函數(shù)。在第18章,我們將學(xué)習(xí)怎樣使用依賴注入容器去避免這個(gè)多構(gòu)造函數(shù)的需求。更多關(guān)于依賴注入的信息 http://manning.com/seeman
此時(shí)我們已經(jīng)有能力繞開數(shù)據(jù)庫測試我們的controller actions了,我們達(dá)到這個(gè)目的,我們要在我們的IGuestbookRepository接口的實(shí)現(xiàn)類做些假數(shù)據(jù)。我們將創(chuàng)建一個(gè)新類,實(shí)現(xiàn)這個(gè)接口,把所有的操作放進(jìn)內(nèi)存中,我們將使用mocking framework比如說moq,RHino Mocks(都可以通過Nuget安裝),他們能夠?yàn)槲覀冏詣?dòng)創(chuàng)建含有假數(shù)據(jù)的接口的實(shí)現(xiàn)類
1: using Guestbook.Contract;
2: using GuestInfo.Models;
3: using System;
4: using System.Collections.Generic;
5: using System.Linq;
6: using System.Web;
7:
8: namespace GuestInfo.Interface
9: {
10: public class FakeGuestbookRepository : IGuestbookRepository
11: {
12: private List<GuestbookEntry> _entries
13: = new List<GuestbookEntry>();
14:
15: public IList<GuestbookEntry> GetMostRecentEntries()
16: {
17: return new List<GuestbookEntry>
18: {
19: new GuestbookEntry
20: {
21: DateAdded = new DateTime(2011, 6, 1),
22: Id = 1,
23: Message = "Test message",
24: name = "Jeremy"
25: }
26: };
27: }
28:
29: public void AddEntry(GuestbookEntry entry)
30: {
31: _entries.Add(entry);
32: }
33: public GuestbookEntry FindById(int id)
34: {
35: return _entries.SingleOrDefault(x => x.Id == id);
36: }
37:
38: public IList<CommentSummary> GetCommentSummary()
39: {
40: return new List<CommentSummary>
41: {
42: new CommentSummary
43: {
44: UserName = "Jeremy", NumberOfComments = 1
45: }
46: };
47: }
48: }
49: }
這個(gè)假實(shí)現(xiàn)暴露了相同的方法作為一個(gè)真實(shí)的版本,
除了那些簡單的內(nèi)存的集合,GetCommentSummary和GetMostRecentEntries方法是不能夠響應(yīng)獲得的,其他方法都可以獲得進(jìn)行測試。新建一個(gè)單元測試的項(xiàng)目
這是兩個(gè)測試用例
1: [TestMethod]
2: public void Index_RendersView()
3: {
4: var controller = new GuestbookController(
5: new FakeGuestbookRepository());
6: var result = controller.Index() as ViewResult;
7: Assert.IsNotNull(result);
8: }
9:
10: [TestMethod]
11: public void Index_gets_most_recent_entries()
12: {
13: var controller = new GuestbookController(
14: new FakeGuestbookRepository());
15: var result = (ViewResult)controller.Index();
16: var guestbookEntries = (IList<GuestbookEntry>) result.Model;
17: Assert.AreEqual(1, guestbookEntries.Count);
18:
19: }