7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

目錄

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 屏幕上。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

上述的錯(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

步驟如下。

  1. 通過(guò)點(diǎn)擊「Add New」鏈接導(dǎo)航到 AddNew 屏幕。

  2. 保持 First Name 為空。

  3. 將 Salary 設(shè)置為 56。

  4. 點(diǎn)擊「Save Employee」按鈕。

這樣會(huì)使得兩個(gè)認(rèn)證是失敗的。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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

  • Test 2
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

正如你所看見(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)證。

  1. FirstName 不應(yīng)為空。

  2. LastName 的長(zhǎng)度不能超出5。

  3. Salary 不應(yīng)為空。

  4. Salary 應(yīng)該為一個(gè)正確的數(shù)字。

  5. 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)。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

第二步:創(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
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
  • Test 2
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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)證工作。

  1. 終端用戶(hù)通過(guò)瀏覽器發(fā)送一個(gè)表單認(rèn)證的請(qǐng)求。

  2. 瀏覽器將發(fā)送所有請(qǐng)求相關(guān)的Cookies,存儲(chǔ)在客戶(hù)機(jī)器上。

  3. 當(dāng)請(qǐng)求被服務(wù)器端接收到,服務(wù)器會(huì)檢查請(qǐng)求并檢查特殊的 Cookie,即「Authentication Cookie」。

  4. 如果發(fā)現(xiàn)了合法的認(rèn)證 Cookie,服務(wù)器就會(huì)證實(shí)用戶(hù)的身份,或者簡(jiǎn)單來(lái)說(shuō),認(rèn)為用戶(hù)是一個(gè)合法的用戶(hù),并允許他進(jìn)行下一步。

  5. 如果沒(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」。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

第五步:授權(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”。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

正如你所看見(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)。

  1. 通過(guò)觸發(fā)業(yè)務(wù)層功能來(lái)檢查用戶(hù)的合法性。

  2. 如果用戶(hù)是一個(gè)合法用戶(hù),就創(chuàng)建一個(gè)認(rèn)證的 Cookie。它能夠使得未來(lái)的請(qǐng)求是認(rèn)證過(guò)的請(qǐng)求。

  3. 如果用戶(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
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
  • Test 2
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

Lab 17 的 Q&A

為什么 DoLogin 會(huì)附上 HttpPost 屬性?

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

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

我們還有其它類(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>
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

正如你所看見(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ì)看到如下輸出。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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)用。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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」。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

安裝「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)用。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 4 天

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 官方博客。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容