目錄
0. 前言
歡迎來(lái)到第四天的 MVC 系列學(xué)習(xí)中。如果你直接開(kāi)始學(xué)習(xí)今天的課程,我強(qiáng)烈建議你先完成之前的學(xué)習(xí)內(nèi)容再來(lái)到這里。
1. Lab 15 — 認(rèn)證錯(cuò)誤的保留值
在 Lab 13 中,我們介紹了服務(wù)器端的認(rèn)證,并且在 Lab 14 中,我們通過(guò)添加自定義認(rèn)證的方式將其提示到一個(gè)新的層級(jí)。
我強(qiáng)烈建議你再回顧一下 Lab 14。再次執(zhí)行應(yīng)用,并且能夠很好地理解代碼以及輸出。
在 Lab 15 中,我們將學(xué)習(xí)如何在認(rèn)證失敗時(shí)填充值。
第一步:創(chuàng)建 CreateEmployeeViewModel
在 ViewModel 文件夾下創(chuàng)建一個(gè)新的類(lèi)。
public class CreateEmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }
}
第二步:改變 SaveEmployee 行為方法
我們將重新使用 Model Binder 創(chuàng)建的 Employee 對(duì)象來(lái)重新生成。改變 SaveEmployee 行為方法如下。
public ActionResult SaveEmployee(Employee e, string BtnSubmit)
{
switch (BtnSubmit)
{
case "Save Employee":
if (ModelState.IsValid)
{
EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
empBal.SaveEmployee(e);
return RedirectToAction("Index");
}
else
{
CreateEmployeeViewModel vm = new CreateEmployeeViewModel();
vm.FirstName = e.FirstName;
vm.LastName = e.LastName;
if (e.Salary.HasValue)
{
vm.Salary = e.Salary.ToString();
}
else
{
vm.Salary = ModelState["Salary"].Value.AttemptedValue;
}
return View("CreateEmployee", vm); // Day 4 Change - Passing e here
}
case "Cancel":
return RedirectToAction("Index");
}
return new EmptyResult();
}
第三步:在視圖中重新填值
- 將 View 成為一個(gè)強(qiáng)類(lèi)型視圖
在 CreateEmployee 視圖的頂部,放置如下代碼。
@using WebApplication1.ViewModels
@model CreateEmployeeViewModel
-
在相應(yīng)控件中呈現(xiàn)從 Model 中獲取的值
...
...
<input type="text" id="TxtFName" name="FirstName" value="@Model.FirstName" />
...
...
<input type="text" id="TxtLName" name="LastName" value="@Model.LastName" />
...
...
<input type="text" id="TxtSalary" name="Salary" value="@Model.Salary" />
...
...
第四步:執(zhí)行并測(cè)試
按下 F5 執(zhí)行應(yīng)用。通過(guò)點(diǎn)擊“Add New”鏈接導(dǎo)航到 AddNew 屏幕上。


上述的錯(cuò)誤將會(huì)在實(shí)驗(yàn)結(jié)束后探討?,F(xiàn)在讓我們來(lái)實(shí)現(xiàn)解決方案。
第五步:改變 AddNew 行為方法
public ActionResult AddNew()
{
return View("CreateEmployee”, new CreateEmployeeViewModel());
}
第六步:執(zhí)行并測(cè)試
按下 F5,并執(zhí)行應(yīng)用。
- Test
步驟如下。
通過(guò)點(diǎn)擊「Add New」鏈接導(dǎo)航到 AddNew 屏幕。
保持 First Name 為空。
將 Salary 設(shè)置為 56。
點(diǎn)擊「Save Employee」按鈕。
這樣會(huì)使得兩個(gè)認(rèn)證是失敗的。

正如你所看見(jiàn)的那樣,值56 仍然保留在 Salary 文本框內(nèi)。
- Test 2

正如你所看見(jiàn)的,F(xiàn)irstName 和 LastName 文本框內(nèi)的值仍然有所保留。
但是奇怪的是,Salary 并沒(méi)有保留值。我們將會(huì)在實(shí)驗(yàn)的最后探討原因并給出解決方案。
Lab 15 的 Q&A
我們真的將值保留了嗎?
答案是否定的。實(shí)際上,我們重填的值是從 Posted 數(shù)據(jù)中獲取的。
為什么在初始的請(qǐng)求中,在「AddNew」行為方法中需要傳輸「new CreateEmployeeViewModel()」?
在視圖中,我們嘗試將模型中的值重新填充到文本框內(nèi)。例如:
<input id="TxtSalary" name="Salary" type="text" value="@Model.Salary" />
正如你所看見(jiàn)的,在代碼區(qū)域,我們?cè)L問(wèn)當(dāng)前模型的 FirstName 屬性。如果 Model 為 Null,那么將會(huì)拋出「Object reference not set to an instance of the class」的異常。
當(dāng)點(diǎn)擊「Add New」超鏈接時(shí),請(qǐng)求將會(huì)被「Add New」行為方法處理。在該方法中,我們可以在返回視圖時(shí)不傳輸任何數(shù)據(jù)。這意味著視圖中 Model 的屬性為 Null,并且會(huì)拋出「Object reference not set to an instance of the class」的異常。為了解決這個(gè)問(wèn)題,在初始請(qǐng)求中,需要傳輸「new CreateEmployeeViewModel()」。
我們可以通過(guò)自動(dòng)的方式來(lái)達(dá)到上述同樣的功能效果嗎?
答案是肯定的。我們可以運(yùn)用 HTML Helper 類(lèi)來(lái)解決。我們將會(huì)在接下來(lái)的實(shí)驗(yàn)中探討這個(gè)問(wèn)題。
2. Lab 16 — 添加客戶(hù)端認(rèn)證
首先我們列舉所需要的所有認(rèn)證。
FirstName 不應(yīng)為空。
LastName 的長(zhǎng)度不能超出5。
Salary 不應(yīng)為空。
Salary 應(yīng)該為一個(gè)正確的數(shù)字。
FirstName 應(yīng)該不能包含「@」符號(hào)。
讓我們來(lái)實(shí)現(xiàn)它吧。
第一步:創(chuàng)建一個(gè) JavaScript 認(rèn)證文件
創(chuàng)建一個(gè) JavaScript 文件,命名為「Validations.js」,并且把它放到 Scripts 文件夾內(nèi)。

第二步:創(chuàng)建認(rèn)證函數(shù)
在「Validations.js」文件內(nèi),創(chuàng)建一個(gè)認(rèn)證函數(shù)。
function IsFirstNameEmpty() {
if (document.getElementById('TxtFName').value == "") {
return 'First Name should not be empty';
}
else { return ""; }
}
function IsFirstNameInValid() {
if (document.getElementById('TxtFName').value.indexOf("@") != -1) {
return 'First Name should not contain @';
}
else { return ""; }
}
function IsLastNameInValid() {
if (document.getElementById('TxtLName').value.length>=5) {
return 'Last Name should not contain more than 5 character';
}
else { return ""; }
}
function IsSalaryEmpty() {
if (document.getElementById('TxtSalary').value=="") {
return 'Salary should not be empty';
}
else { return ""; }
}
function IsSalaryInValid() {
if (isNaN(document.getElementById('TxtSalary').value)) {
return 'Enter valid salary';
}
else { return ""; }
}
function IsValid() {
var FirstNameEmptyMessage = IsFirstNameEmpty();
var FirstNameInValidMessage = IsFirstNameInValid();
var LastNameInValidMessage = IsLastNameInValid();
var SalaryEmptyMessage = IsSalaryEmpty();
var SalaryInvalidMessage = IsSalaryInValid();
var FinalErrorMessage = "Errors:";
if (FirstNameEmptyMessage != "")
FinalErrorMessage += "\n" + FirstNameEmptyMessage;
if (FirstNameInValidMessage != "")
FinalErrorMessage += "\n" + FirstNameInValidMessage;
if (LastNameInValidMessage != "")
FinalErrorMessage += "\n" + LastNameInValidMessage;
if (SalaryEmptyMessage != "")
FinalErrorMessage += "\n" + SalaryEmptyMessage;
if (SalaryInvalidMessage != "")
FinalErrorMessage += "\n" + SalaryInvalidMessage;
if (FinalErrorMessage != "Errors:") {
alert(FinalErrorMessage);
return false;
}
else {
return true;
}
}
第三步:在 View 中包含認(rèn)證文件
在「CreateEmployee」視圖的頂部,將「Validations.js」文件引入。
<script src="~/Scripts/Validations.js"></script>
第四步:附加認(rèn)證
在 SaveEmployee 按鈕點(diǎn)擊時(shí)觸發(fā) IsValid 函數(shù)。
<input type="submit" name="BtnSubmit" value="Save Employee" onclick="return IsValid();" />
第五步:執(zhí)行并測(cè)試
按下 F5,執(zhí)行應(yīng)用。
通過(guò)點(diǎn)擊「Add New」鏈接導(dǎo)航到 AddNew 屏幕。
- Test 1

- Test 2

Lab 16 的 Q&A
為什么點(diǎn)擊 SaveEmployee 按鈕時(shí),關(guān)鍵字需要被返回?
正如我們?cè)?Lab 9 中所探討的,提交按鈕被點(diǎn)擊時(shí),它將會(huì)向服務(wù)器發(fā)送請(qǐng)求。當(dāng)認(rèn)證失敗時(shí),向服務(wù)器發(fā)送請(qǐng)求是沒(méi)有意義的。通過(guò)在提交按鈕中寫(xiě)下「return false」,我們可以阻止默認(rèn)的服務(wù)器請(qǐng)求。
在我們的這個(gè)例子中,當(dāng)認(rèn)證失敗時(shí),IsValid 函數(shù)將會(huì)返回 false,因此我們達(dá)到了預(yù)期的功能。
除了彈出的警告以外,我們可以在頁(yè)面本身上顯示錯(cuò)誤信息嗎?
答案是肯定的。只需要為每個(gè)錯(cuò)誤創(chuàng)建一個(gè) Span 標(biāo)簽。運(yùn)用 CSS 使它在開(kāi)始時(shí)不可見(jiàn),當(dāng)提交按鈕點(diǎn)擊后,如果認(rèn)證失敗了,運(yùn)用 JavaScript 使其可見(jiàn)。
是否存在客戶(hù)端自動(dòng)認(rèn)證的方法?
答案是肯定的。當(dāng)我們運(yùn)用 HTML Helper 就可以基于服務(wù)端認(rèn)證實(shí)現(xiàn)自動(dòng)客戶(hù)端認(rèn)證。我們將會(huì)在后面的實(shí)驗(yàn)中探討這個(gè)問(wèn)題。
服務(wù)器端的認(rèn)證還被需要嗎?
答案是肯定的。在一些場(chǎng)景下,JavaScript 無(wú)法使用,服務(wù)器端認(rèn)證可以替代它使用。
3. Lab 17 — 添加授權(quán)認(rèn)證
在這個(gè)實(shí)驗(yàn)中,我們將會(huì)使得 GetView 方法更加安全。我們將會(huì)確保只有合法用戶(hù)才能訪(fǎng)問(wèn)行為方法。
在第一天的系列學(xué)習(xí)中,我們了解了 ASP.NET 和 MVC 的真實(shí)含義。我們理解到 ASP.NET MVC 是 ASP.NET 的一部分。許多 ASP.NET 的功能都被 ASP.NET MVC 所繼承。其中一個(gè)功能便是表單的認(rèn)證。
在我們開(kāi)始實(shí)驗(yàn)之前,我們先了解下在 ASP.NET 中如何進(jìn)行表單認(rèn)證工作。
終端用戶(hù)通過(guò)瀏覽器發(fā)送一個(gè)表單認(rèn)證的請(qǐng)求。
瀏覽器將發(fā)送所有請(qǐng)求相關(guān)的Cookies,存儲(chǔ)在客戶(hù)機(jī)器上。
當(dāng)請(qǐng)求被服務(wù)器端接收到,服務(wù)器會(huì)檢查請(qǐng)求并檢查特殊的 Cookie,即「Authentication Cookie」。
如果發(fā)現(xiàn)了合法的認(rèn)證 Cookie,服務(wù)器就會(huì)證實(shí)用戶(hù)的身份,或者簡(jiǎn)單來(lái)說(shuō),認(rèn)為用戶(hù)是一個(gè)合法的用戶(hù),并允許他進(jìn)行下一步。
如果沒(méi)有發(fā)現(xiàn)合法的認(rèn)證 Cookie,服務(wù)器就會(huì)認(rèn)為用戶(hù)是匿名(未認(rèn)證)的用戶(hù)。在這種情況下,如果被請(qǐng)求的資源被標(biāo)記為 Protected/Secured,那么用戶(hù)將會(huì)被重新導(dǎo)向到登錄頁(yè)面。
第一步:創(chuàng)建一個(gè) AuthenticationController 和一個(gè)登錄行為方法
右擊 Controller 文件夾,選擇「Add New Controller」,然后創(chuàng)建一個(gè)控制器,命名為「Authentication」。在這種情況下,全名應(yīng)該為「AuthenticationController」。
在控制器內(nèi)創(chuàng)建一個(gè)行為方法,命名為 Login。
public class AuthenticationController : Controller
{
// GET: Authentication
public ActionResult Login()
{
return View();
}
}
第二步:創(chuàng)建 Model
在 Model 文件夾下創(chuàng)建一個(gè) Model 類(lèi),命名為 UserDetails。
namespace WebApplication1.Models
{
public class UserDetails
{
public string UserName { get; set; }
public string Password { get; set; }
}
}
第三步:創(chuàng)建 Login 視圖
在「~/Views/Authentication」文件夾下創(chuàng)建一個(gè)新的視圖,命名為 Login。使它成為一個(gè) UserDetails 的強(qiáng)類(lèi)型視圖。
在視圖內(nèi)放置如下 HTML 代碼。
@model WebApplication1.Models.UserDetails
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Login</title>
</head>
<body>
<div>
@using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
{
@Html.LabelFor(c=>c.UserName)
@Html.TextBoxFor(x=>x.UserName)
<br />
@Html.LabelFor(c => c.Password)
@Html.PasswordFor(x => x.Password)
<br />
<input type="submit" name="BtnSubmit" value="Login" />
}
</div>
</body>
</html>
正如你所看見(jiàn)的,這次我們生成的視圖運(yùn)用了 HtmlHelper 類(lèi),而不是純 HTML。
在視圖中我們已經(jīng)創(chuàng)建了一個(gè) HtmlHelper 類(lèi)的對(duì)象,即「Html」。
HtmlHelper 類(lèi)的功能簡(jiǎn)單返回 HTML 字符串。
Example 1
@Html.TextBoxFor(x=>x.UserName)
上述的代碼將產(chǎn)生如下的 HTML。
<input id="UserName" name="UserName" type="text" value="" />
Example 2
@using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
{
}
上述的代碼將產(chǎn)生如下的 HTML。
<form action="/Authentication/DoLogin" method="post">
</form>
第四步:執(zhí)行并測(cè)試
按下 F5,執(zhí)行應(yīng)用。在地址欄輸入 Login 方法的 URL。在這個(gè)例子中,將會(huì)是「http://localhost:8870/Authentication/Login」。

第五步:授權(quán)表單認(rèn)證
打開(kāi) Web.config 文件。導(dǎo)航到 System.Web 區(qū)域。找到子區(qū)域 Authentication。如果這里不存在就創(chuàng)建一個(gè)。設(shè)置 Authentication 的模式是 Forms,Login URL 指向第一步所創(chuàng)建的「Login」的行為方法。
<authentication mode="Forms">
<forms loginurl="~/Authentication/Login"></forms>
</authentication>
第六步:讓行為方法更加安全
打開(kāi) EmployeeController,然后向 Index 方法附上 Authorize 屬性。
[Authorize]
public ActionResult Index()
{
EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
......
第七步:執(zhí)行并測(cè)試
按下 F5 并執(zhí)行應(yīng)用。在地址欄中輸入 EmployeeController 的 Index 行為方法的 URL。在這個(gè)情景中,URL 為“http://localhost:8870/Employee/Index”。

正如你所看見(jiàn)的,Index 行為的請(qǐng)求將會(huì)重指向 Login 行為。
第八步:創(chuàng)建一個(gè)業(yè)務(wù)層功能
打開(kāi) EmployeeBusinessLayer 類(lèi),然后創(chuàng)建一個(gè)方法叫做 IsValidUser。
public bool IsValidUser(UserDetails u)
{
if (u.UserName == "Admin" && u.Password == "Admin")
{
return true;
}
else
{
return false;
}
}
注:在業(yè)務(wù)層,我們對(duì)比 UserName 和 Password 的硬編碼值。在真實(shí)場(chǎng)景中,我們能夠調(diào)用數(shù)據(jù)庫(kù)層,并對(duì)比真實(shí)的值。
第九步:創(chuàng)建 DoLogin 行為方法
打開(kāi) AuthenticationController 類(lèi),然后創(chuàng)建一個(gè)新的行為方法,叫做 DoLogin。
這個(gè) DoLogin 方法會(huì)在 Login 按鈕點(diǎn)擊時(shí)被觸發(fā)。
現(xiàn)在我們來(lái)列舉一下 DoLogin 中需要做的幾點(diǎn)。
通過(guò)觸發(fā)業(yè)務(wù)層功能來(lái)檢查用戶(hù)的合法性。
如果用戶(hù)是一個(gè)合法用戶(hù),就創(chuàng)建一個(gè)認(rèn)證的 Cookie。它能夠使得未來(lái)的請(qǐng)求是認(rèn)證過(guò)的請(qǐng)求。
-
如果用戶(hù)是不合法的,就向當(dāng)前的 ModelState 增加一個(gè)錯(cuò)誤。這個(gè)錯(cuò)誤將會(huì)呈現(xiàn)在視圖中。
[HttpPost]
public ActionResult DoLogin(UserDetails u)
{
EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
if (bal.IsValidUser(u))
{
FormsAuthentication.SetAuthCookie(u.UserName, false);
return RedirectToAction("Index", "Employee");
}
else
{
ModelState.AddModelError("CredentialError", "Invalid Username or Password");
return View("Login");
}
}
讓我們理解下上述代碼區(qū)域。
如果你記得「第三天 — Lab 13」中我們所談?wù)摰?ModelState,那么就能理解它包含了模型的狀態(tài)。它包含了當(dāng)前模型的錯(cuò)誤信息。在上述的代碼片段中,當(dāng)用戶(hù)是一個(gè)非法用戶(hù)時(shí),就增加一個(gè)新的錯(cuò)誤。(錯(cuò)誤由鍵值「CredentialError」和信息「Invalid UserName or Password」組成)
表單認(rèn)證。SetAuthCookie 將會(huì)在客戶(hù)機(jī)器上創(chuàng)建一個(gè)新的 Cookie。
第十步:在視圖中呈現(xiàn)信息
打開(kāi) Login 視圖,在 @Html.BeginForm 上增加一小段代碼。
@Html.ValidationMessage("CredentialError", new {style="color:red;" })
@using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
第十一步:執(zhí)行并測(cè)試
按下 F5 并執(zhí)行應(yīng)用。直切發(fā)起一個(gè)指向 Login Action 的請(qǐng)求。我相信你現(xiàn)在已經(jīng)知道如何去做。
注:如果你想發(fā)起一個(gè)指向 EmployeeController 中 Index Action 的請(qǐng)求,它會(huì)重新導(dǎo)向到 Login Action。
- Test 1

- Test 2

Lab 17 的 Q&A
為什么 DoLogin 會(huì)附上 HttpPost 屬性?
這個(gè)屬性可以使 DoLogin 行為方法只為 Post 請(qǐng)求開(kāi)啟。如果想試圖得到一個(gè) DoLogin 的請(qǐng)求,將不會(huì)起作用。

我們還有其它類(lèi)似的屬性嗎?
答案是肯定的。我們有 HttpGet,HttpPut 和 HttpDelete。作為一個(gè)最佳實(shí)踐,每一個(gè)行為方法都應(yīng)該被附上這樣的屬性。
注意:為了保持代碼和學(xué)習(xí)是輕松并簡(jiǎn)單的,我們?cè)谶@個(gè)系列學(xué)習(xí)中并沒(méi)有全部都遵循最佳實(shí)踐,但是我們建議你在項(xiàng)目中遵循。
在我們持續(xù)的學(xué)習(xí)中,我們將會(huì)繼續(xù)談?wù)撟罴褜?shí)踐。
FormsAuthentication.SetAuthCookie 是必須要寫(xiě)的嗎?
答案是肯定的。
讓我們來(lái)理解一個(gè)小的程序。
用戶(hù)通過(guò)瀏覽器向服務(wù)器發(fā)送請(qǐng)求。
當(dāng)請(qǐng)求通過(guò)瀏覽器發(fā)出,所有相關(guān)聯(lián)的 Cookies 將會(huì)伴隨著請(qǐng)求。
服務(wù)器接收到請(qǐng)求并且準(zhǔn)備發(fā)出響應(yīng)。
現(xiàn)在我們已經(jīng)知道請(qǐng)求和響應(yīng)是通過(guò) HTTP 協(xié)議,并且 HTTP 是無(wú)關(guān)國(guó)籍的。對(duì)于服務(wù)器而言,每一個(gè)請(qǐng)求都是一個(gè)新的請(qǐng)求,因此當(dāng)相同的用戶(hù)發(fā)出兩次請(qǐng)求時(shí),服務(wù)器并不能夠識(shí)別它。為了解決這個(gè)問(wèn)題,服務(wù)器在準(zhǔn)備響應(yīng)的時(shí)候增加了 Cookie,然后返回響應(yīng)。
當(dāng)客戶(hù)端瀏覽器接收到伴隨著 Cookie 的響應(yīng)后,它將會(huì)在客戶(hù)端創(chuàng)建 Cookies。
現(xiàn)在客戶(hù)又一次發(fā)出請(qǐng)求,服務(wù)器將會(huì)識(shí)別他,因?yàn)檎?qǐng)求中包含了 Cookies。
FormsAuthentication.SetAuthCookie 將會(huì)向響應(yīng)增加一個(gè)特殊的 Cookie,命名為「Authentication」。
如果沒(méi)有 Cookies,是不是意味著 FormsAuthentication 就不會(huì)起作用?
答案是否定的。我們有替代物。我們可以運(yùn)用 URI,而不是 Cookies。
打開(kāi) Web.config,然后更改「Authentication/Forms」區(qū)域如下。
<forms cookieless="UseUri" loginurl="~/Authentication/Login"></forms>

正如你所看見(jiàn)的,現(xiàn)在認(rèn)證 Cookie 通過(guò) URL 它自己傳輸。
默認(rèn)情況下,Cookieless 屬性會(huì)被設(shè)置為「AutoDetect」。這意味著認(rèn)證工作通過(guò) Cookie 完成,在這種情景下,Cookies 將不會(huì)支持通過(guò) URL 傳輸。
FormsAuthentication.SetAuthCookie 的第二個(gè)參數(shù)起到什么作用?
這決定了我們是否愿意創(chuàng)建一個(gè)永久的 Cookie。非永久的 Cookie 將會(huì)在瀏覽器關(guān)閉時(shí)自動(dòng)刪除。永久性的 Cookies 將不會(huì)被自動(dòng)刪除。我們可以通過(guò)代碼或者瀏覽器設(shè)置手動(dòng)刪除。
通過(guò)代碼做到登出?
我們將會(huì)在以后的 Lab 中學(xué)習(xí)。
當(dāng)認(rèn)證失敗時(shí),UserName 文本框?qū)?huì)如何被填充?
這是 HTML Helper 類(lèi)的神奇之處。它們將會(huì)在控件中填充來(lái)自 Posted 數(shù)據(jù)的值。這是運(yùn)用 HTML Helper 類(lèi)的其中一個(gè)優(yōu)勢(shì)。
@Html.ValidationMessage 是做什么的?
我們?cè)?Lab 13 中已經(jīng)探討過(guò)了。它根據(jù)鍵值呈現(xiàn) ModelState 的錯(cuò)誤。
Authorize 屬性是做什么的?
在 ASP.NET MVC 中,存在一個(gè)概念叫做 Filters。運(yùn)用它可以過(guò)濾請(qǐng)求和響應(yīng)。這里存在四種過(guò)濾器。我們將會(huì)在第七天的學(xué)習(xí)中探討它們。Authorize 屬性通過(guò) Authorization 過(guò)濾器得出。這樣可以確保只有認(rèn)證過(guò)的請(qǐng)求允許指向動(dòng)作方法。
我們可以在一個(gè)行為方法中同時(shí)附上 HttpPost 和 Authorize 屬性嗎?
答案是肯定的。
在這個(gè)例子中,為什么沒(méi)有 ViewModel?
在 Lab 6 中我們所探討的,View 應(yīng)該和 Model 直接相關(guān)聯(lián)。我們必須有 View 和 Model 中的 ViewModel。視圖是展示視圖或者數(shù)據(jù)入口視圖都不重要,它們都應(yīng)該和 ViewModel 相關(guān)聯(lián)。在真實(shí)項(xiàng)目中,我強(qiáng)烈希望你全部都運(yùn)用上 ViewModel。
對(duì)于每一個(gè)行為方法都必須附上 Authorize 屬性嗎?
答案是否定的。我們可以附上它 Controller 級(jí)別或者 Global 級(jí)別。當(dāng)附上 Controller 級(jí)別,它將可以適用于一個(gè)控制器里的所有行為方法。當(dāng)附上 Global 級(jí)別,它將會(huì)適用于所有控制器里的所有行為方法。
-
Controller 級(jí)別:
[Authorize]
public class EmployeeController : Controller
{
.... Global 級(jí)別:
第一步:從 App_start 文件夾下打開(kāi) FilterConfig.cs 文件
第二步:在 RegisterGlobalFilters 中增加一行
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());//Old line
filters.Add(new AuthorizeAttribute());//New Line
}
第三步:向 AuthenticationController 附上 AllowAnonymous 屬性
[AllowAnonymous]
public class AuthenticationController : Controller
{
第四步:執(zhí)行并測(cè)試應(yīng)用
「filters.Add(new HandleErrorAttribute())」是用來(lái)做什么的?
我們將會(huì)在日后的試驗(yàn)中探討這個(gè)問(wèn)題的細(xì)節(jié)。
為什么 AuthenticationController 中需要加上 AllowAnonymous 屬性?
我們已經(jīng)附上了 Global 級(jí)別的 Authorize 過(guò)濾器。這意味著所有的方法都將是受保護(hù)的,包括 Login 和 DoLogin 行為方法。AllowAnonymous 為未認(rèn)證的請(qǐng)求開(kāi)啟了行為方法。
FilterConfig 類(lèi)中的 RegisterGlobalFilters 方法如何被觸發(fā)的?
它通過(guò) Global.asax 文件內(nèi)的 Application_Start 事件所觸發(fā)。
4. Lab 18 — 在視圖中展示 UserName
在本實(shí)驗(yàn)中,我們將在視圖中呈現(xiàn)當(dāng)前登錄的用戶(hù)名。
第一步:在 ViewModel 中增加 UserName
打開(kāi) EmployeeListViewModel,然后增加一個(gè)新的屬性,即 UserName。
public class EmployeeListViewModel
{
public List<EmployeeViewModel><employeeviewmodel> Employees { get; set; }
public string UserName { get; set; }
}
第二步:向 ViewModel 的UserName 設(shè)置值
打開(kāi) EmployeeController,然后更改 Index 方法如下。
public ActionResult Index()
{
EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
employeeListViewModel.UserName = User.Identity.Name; //New Line
......
第三步:在視圖中呈現(xiàn) UserName
打開(kāi) Index.cshtml 視圖,呈現(xiàn) UserName。
<body>
<div style="text-align:right"> Hello, @Model.UserName </div>
<hr />
<a href="/Employee/AddNew">Add New</a>
<div>
<table border="1"><span style="font-size: 9pt;">
</span>
第四步:執(zhí)行并測(cè)試
按下 F5 并執(zhí)行應(yīng)用。完成登錄操作后,你將會(huì)看到如下輸出。

5. Lab 19 — 實(shí)現(xiàn)登出
第一步:創(chuàng)建登出鏈接
打開(kāi) Index.cshtml 文件然后創(chuàng)建登出鏈接。
<body>
<div style="text-align:right">Hello, @Model.UserName
<a href="/Authentication/Logout">Logout</a></div>
<hr />
<a href="/Employee/AddNew">Add New</a>
<div>
<table border="1">
第二步:創(chuàng)建登出行為方法
打開(kāi) AuthenticationController,然后增加一個(gè)行為方法,命名為 Logout。
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return RedirectToAction("Login");
}
第三步:執(zhí)行并測(cè)試
按下 F5 并執(zhí)行應(yīng)用。

6. Lab 20 — 在 Login 頁(yè)實(shí)現(xiàn)認(rèn)證
第一步:增加數(shù)據(jù)注釋
打開(kāi) UserDetails.cs,然后增加 Data Annotation。
public class UserDetails
{
[StringLength(7,MinimumLength=2, ErrorMessage = "UserName length should be between 2 and 7")]
public string UserName { get; set; }
public string Password { get; set; }
}
第二步:在視圖中呈現(xiàn)錯(cuò)誤信息
改變 Login.cshtml,呈現(xiàn)錯(cuò)誤信息。
@using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
{
@Html.LabelFor(c=>c.UserName)
@Html.TextBoxFor(x=>x.UserName)
@Html.ValidationMessageFor(x=>x.UserName)
......
注:這次我們運(yùn)用 Html.ValidationMessageFor,而不是Html.ValidationMessage。兩個(gè)做的是同一件事。Html.ValidationMessageFor 只能用于強(qiáng)類(lèi)型視圖。
第三步:改變 DoLogin
改變 DoLogin 行為方法如下:
[HttpPost]
public ActionResult DoLogin(UserDetails u)
{
if (ModelState.IsValid)
{
EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
if (bal.IsValidUser(u))
{
FormsAuthentication.SetAuthCookie(u.UserName, false);
return RedirectToAction("Index", "Employee");
}
else
{
ModelState.AddModelError("CredentialError", "Invalid Username or Password");
return View("Login");
}
}
else
{
return View("Login");
}
}
第四步:執(zhí)行并測(cè)試
按下 F5 并執(zhí)行應(yīng)用

7. Lab 21 — 在 Login 頁(yè)實(shí)現(xiàn)客戶(hù)端的認(rèn)證
這次我們將以不同的方式實(shí)現(xiàn)客戶(hù)端的認(rèn)證。
第一步:下載 JQuery Unobtrusive Validation 文件
右擊項(xiàng)目,選擇「Manage Nuget Packages」。
點(diǎn)擊 Online,然后搜索「JQuery Unobtrusive」。

安裝「Microsoft JQuery Unobtrusive Validation」。
第二步:在視圖中引入 JQuery Validation 文件
在 Scripts 文件夾下增加三個(gè) JavaScripts 文件。
JQuery-Someversion.js
JQuery.validate.js
JQuery.validate.unobtrusive
打開(kāi) Login.cshtml 文件,然后在頭部引入這三個(gè) JavaScript 文件。
<script src="~/Scripts/jquery-1.8.0.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
第三步:執(zhí)行并測(cè)試
按下 F5,執(zhí)行應(yīng)用。

8. Lab 21 的 Q&A
客戶(hù)端的認(rèn)證是如何實(shí)現(xiàn)的?
正如你所看見(jiàn)的,不費(fèi)多少力氣就實(shí)現(xiàn)了客戶(hù)端的認(rèn)證。在 Login 視圖中,HTML 元素通過(guò) HTML Helper 類(lèi)產(chǎn)生。Helper 函數(shù)能夠根據(jù)數(shù)據(jù)注釋屬性來(lái)生成帶有屬性附加的 HTML 標(biāo)記。
例如:
@Html.TextBoxFor(x=>x.UserName)
@Html.ValidationMessageFor(x=>x.UserName)
上述的代碼產(chǎn)生如下的 HTML 代碼。
<input data-val="true" data-val-length="UserName length should be between 2 and 7" data-val-length-max="7" data-val-length-min="2" id="UserName" name="UserName" type="text" value="" />
<span class="field-validation-error" data-valmsg-for="UserName" data-valmsg-replace="true">
</span>
這些自定義的 HTML 屬性將會(huì)被「JQuery Unobtrusive Validation」文件使用,因此在客戶(hù)端自動(dòng)實(shí)現(xiàn)認(rèn)證。
自動(dòng)的客戶(hù)端認(rèn)證是 HTML Helper 類(lèi)的第二個(gè)優(yōu)勢(shì)。
Unobtrusive JavaScript 的意思是什么?
下面是 Wikipedia 所述。譯文為:
Unobtrusive JavaScript 是一個(gè)在 Web 頁(yè)面中常用的 JavaScript 方法。盡管它沒(méi)有被正式定義,但是它的幾個(gè)基本的準(zhǔn)則可以被大概理解:
從 Web 頁(yè)面的結(jié)構(gòu)或者內(nèi)容中分離的功能("Behaviour Layer"),并且展示。
傳統(tǒng)的 JavaScript 編程中,最佳避免問(wèn)題的實(shí)踐。(例如瀏覽器的不一致性和缺乏伸縮性)
逐步增強(qiáng)支持用戶(hù)可能不先進(jìn)的 JavaScript 功能。
讓我以外行來(lái)定義一下。
「以一種方式來(lái)寫(xiě)你的 JavaScript,這種 JavaScript 不能與 HTML 強(qiáng)聯(lián)系。JavaScript 可能訪(fǎng)問(wèn) DOM 元素,JavaScript 可能操作 DOM 元素,但是并不和它們直接聯(lián)系。」
在上述例子中,JQuery Unobtrusive JavaScript 簡(jiǎn)單地運(yùn)用一些輸入元素屬性和實(shí)現(xiàn)客戶(hù)端認(rèn)證。
我們可以運(yùn)用這些 JavaScript 來(lái)認(rèn)證,而不是采用 HTML Helper 類(lèi)嗎?
答案是肯定的,對(duì)于這種方式,我們需要手動(dòng)地向元素附件屬性。
哪個(gè)更推崇,是 HTML Helper 函數(shù)還是純 HTML?
我個(gè)人更傾向于純 HTML,因?yàn)?HTML Helper 函數(shù)又一次用到了「Full Control over HTML」,而這個(gè)弊端我們?cè)懻撨^(guò)。
其次我們討論下使用 JavaScript 框架或庫(kù),而不是使用 JQuery 的項(xiàng)目情況。一些框架例如 Angular。在這種情況下,大多數(shù)我們都會(huì)考慮 Angular 認(rèn)證,并且自定義的 HTML 認(rèn)證屬性將會(huì)拋棄。
9. 總結(jié)
現(xiàn)在我們已經(jīng)完成了第四天的學(xué)習(xí)。在第五天中,我們將會(huì)有更進(jìn)一步的學(xué)習(xí),會(huì)更有樂(lè)趣。
原文地址:Learn MVC Project in 7 days
本文系 OneAPM 工程師編譯整理。OneAPM 是應(yīng)用性能管理領(lǐng)域的新興領(lǐng)軍企業(yè),能幫助企業(yè)用戶(hù)和開(kāi)發(fā)者輕松實(shí)現(xiàn):緩慢的程序代碼和 SQL 語(yǔ)句的實(shí)時(shí)抓取。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問(wèn) OneAPM 官方博客。