1. 程式人生 > >七天學會ASP.NET MVC (四)——使用者授權認證問題

七天學會ASP.NET MVC (四)——使用者授權認證問題

本文參考自:http://www.codeproject.com/Articles/996832/Learn-MVC-Project-in-Days-Day

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

小編應各位的要求,快馬加鞭,馬不停蹄的終於:七天學會 Asp.Net MVC 第四篇出爐,在第四天的學習中,我們主要了學習如何在MVC中如何實現認證授權等問題,本節主要講了驗證錯誤時的錯誤值,客戶端驗證,授權認證及登入登出功能的實現。

系列文章

day4

目錄

實驗15 ——有關錯誤驗證的保留值。

實驗16——新增客戶端驗證

實驗17——新增授權認證

實驗18——在View中顯示使用者名稱

實驗19——實現登出操作

實驗20——實現登入頁面驗證

實驗21——實現登入頁面客戶端驗證

總結

實驗15——有關錯誤驗證的保留值

在上一節的實驗13,我們介紹了伺服器端的身份驗證,實驗14中添加了客戶端驗證的支援,希望每位讀者都能夠把實驗14理解透徹,逐行程式碼檢視,保證每行程式碼都理解了,這樣才有助於理解我們接下來的實驗。

實驗15中將學習如何在驗證失敗時,填充值。

1. 建立 CreateEmployeeViewModel 類。

在ViewModel資料夾下,新建類:

public class CreateEmployeeViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Salary { get; set; }
}

2. 修改  SaveEmployee 方法

為了重新生成,重用Model Binder建立的 Employee 物件,修改 SaveEmployee 方法。

   1:  public ActionResult SaveEmployee(Employee e, string BtnSubmit)
   2:  {
   3:      switch (BtnSubmit)
   4:      {
   5:          case "Save Employee":
   6:              if (ModelState.IsValid)
   7:              {
   8:                  EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
   9:                  empBal.SaveEmployee(e);
  10:                  return RedirectToAction("Index");
  11:              }
  12:              else
  13:              {
  14:                  CreateEmployeeViewModel vm = new CreateEmployeeViewModel();
  15:                  vm.FirstName = e.FirstName;
  16:                  vm.LastName = e.LastName;
  17:                  if (e.Salary.HasValue)
  18:                  {
  19:                      vm.Salary = e.Salary.ToString();                        
  20:                  }
  21:                  else
  22:                  {
  23:                      vm.Salary = ModelState["Salary"].Value.AttemptedValue;                       
  24:                  }
  25:                  return View("CreateEmployee", vm); // Day 4 Change - Passing e here
  26:              }
  27:          case "Cancel":
  28:              return RedirectToAction("Index");
  29:      }
  30:      return new EmptyResult();
  31:  }

3. 填充View的值

3.1 將View設定為強型別的View

在 CreateEmployee View檔案開始新增以下程式碼:

   1:  @using WebApplication1.ViewModels
   2:  @model CreateEmployeeViewModel

3.2 在響應控制元件中顯示Model值

   1:  ...
   2:   
   3:  ...
   4:   
   5:  ...
   6:   
   7:  <input type="text" id="TxtFName" name="FirstName" value="@Model.FirstName" />
   8:   
   9:  ...
  10:   
  11:  ...
  12:   
  13:  ...
  14:   
  15:  <input type="text" id="TxtLName" name="LastName" value="@Model.LastName" />
  16:   
  17:  ...
  18:   
  19:  ...
  20:   
  21:  ...
  22:   
  23:  <input type="text" id="TxtSalary" name="Salary" value="@Model.Salary" />
  24:   
  25:  ...
  26:   
  27:  ...
  28:   
  29:  ...

4. 執行點選Add New連結

瀏覽器提示錯誤。我們在實驗講述完之後,再來解釋為什麼會出現錯誤。

5. 修改AddNew方法

   1:  public ActionResult AddNew()
   2:  {
   3:      return View("CreateEmployee”, new CreateEmployeeViewModel());
   4:  }

6.  執行測試

測試1

  • 點選 “Add New”跳轉到  AddNew 頁面。
  • 設定名字為空
  • 輸入工資值 56
  • 點選“Save Employee”按鈕。

會出現驗證失敗,但是數字 56 仍然顯示在 Salary 文字框中。

測試2

如圖所示,姓名仍然保留在文字框中,卻未保留工資,接下來我們來討論上述問題的解決辦法。

關於實驗15

是否是真的將值保留?

不是,是從post資料中重新獲取的。

為什麼需要在初始化請求時,在Add New 方法中傳遞 new CreateEmployeeViewModel()?

View中,試著將Model中的資料重新顯示在文字框中。

如:

<input id="TxtSalary" name="Salary" type="text" value="@Model.Salary" />

如上所示,可以訪問當前Model的“First Name”屬性,如果Model 為空,會丟擲類無法例項化的異常“Object reference not set to an instance of the class”。

當點選”Add New“超連結時,請求會通過Add New方法處理,在該Action 方法中,可以不傳遞任何資料。即就是,View中的Model屬性為空。因此會丟擲“Object reference not set to an instance of the class”異常。為了解決此問題,所以會在初始化請求時,傳”new CreateEmployeeViewModel()“。

上述的這些功能,有什麼方法可以自動生成?

使用HTML 幫助類就可以實現。在實驗16中我們會講解HTML 幫助類。

實驗16——新增客戶端驗證

首先了解,需要驗證什麼?

1. FirstName 不能為空

2. LastName字元長度不能大於5

3. Salary不能為空,且應該為數字型別

4. FirstName 不能包含@字元

接下來,實現客戶端驗證功能

1. 建立JavaScript 驗證檔案

在Script檔案下,新建JavaScript檔案,命名為“Validations.js”

2. 建立驗證函式

在“Validations.js”檔案中建立驗證函式:

   1:  function IsFirstNameEmpty() {
   2:      if (document.getElementById('TxtFName').value == "") {
   3:          return 'First Name should not be empty';
   4:      }
   5:      else { return ""; }
   6:  }
   7:   
   8:  function IsFirstNameInValid() {    
   9:      if (document.getElementById('TxtFName').value.indexOf("@") != -1) {
  10:          return 'First Name should not contain @';
  11:      }
  12:      else { return ""; }
  13:  }
  14:  function IsLastNameInValid() {
  15:      if (document.getElementById('TxtLName').value.length>=5) {
  16:          return 'Last Name should not contain more than 5 character';
  17:      }
  18:      else { return ""; }
  19:  }
  20:  function IsSalaryEmpty() {
  21:      if (document.getElementById('TxtSalary').value=="") {
  22:          return 'Salary should not be empty';
  23:      }
  24:      else { return ""; }
  25:  }
  26:  function IsSalaryInValid() {
  27:      if (isNaN(document.getElementById('TxtSalary').value)) {
  28:          return 'Enter valid salary';
  29:      }
  30:      else { return ""; }
  31:  }
  32:  function IsValid() {
  33:   
  34:      var FirstNameEmptyMessage = IsFirstNameEmpty();
  35:      var FirstNameInValidMessage = IsFirstNameInValid();
  36:      var LastNameInValidMessage = IsLastNameInValid();
  37:      var SalaryEmptyMessage = IsSalaryEmpty();
  38:      var SalaryInvalidMessage = IsSalaryInValid();
  39:   
  40:      var FinalErrorMessage = "Errors:";
  41:      if (FirstNameEmptyMessage != "")
  42:          FinalErrorMessage += "\n" + FirstNameEmptyMessage;
  43:      if (FirstNameInValidMessage != "")
  44:          FinalErrorMessage += "\n" + FirstNameInValidMessage;
  45:      if (LastNameInValidMessage != "")
  46:          FinalErrorMessage += "\n" + LastNameInValidMessage;
  47:      if (SalaryEmptyMessage != "")
  48:          FinalErrorMessage += "\n" + SalaryEmptyMessage;
  49:      if (SalaryInvalidMessage != "")
  50:          FinalErrorMessage += "\n" + SalaryInvalidMessage;
  51:   
  52:      if (FinalErrorMessage != "Errors:") {
  53:          alert(FinalErrorMessage);
  54:          return false;
  55:      }
  56:      else {
  57:          return true;
  58:      }
  59:  }

3. 在 “CreateEmployee”View 中新增 Validations.js檔案引用:

   1:  <script src="~/Scripts/Validations.js"></script>

4.  在點選 SaveEmployee按鈕時,呼叫驗證函式,如下:

<input type="submit" name="BtnSubmit" value="Save Employee" onclick="IsValid();"/>

5.  執行測試

點選 Add New 連結,跳轉到 ”Add  New“頁面

測試1

測試2

關於實驗16

為什麼在點選”SaveEmployee “按鈕時,需要返回關鍵字?

如之前實驗9討論的,當點選提交按鈕時,是給伺服器傳送請求,驗證失敗時對伺服器請求沒有意義。通過新增”return false“程式碼,可以取消預設的伺服器請求。

在 IsValid函式將返回false,表示驗證失敗來實現預期的功能。

除了提示使用者,是否可以在當前頁面顯示錯誤資訊?

是可以得,只需要為每個錯誤建立span 標籤,預設設定為不可見,當提交按鈕點選時,如果驗證失敗,使用JavaScript修改錯誤的可見性。

自動獲取客戶端驗證還有什麼方法?

是,當使用Html 幫助類,可根據服務端驗證來獲取自動客戶端驗證,在以後會詳細討論。

伺服器端驗證還有沒有必須使用?

在一些JavaScript指令碼程式碼無法使用時,伺服器端可以替代使用。

實驗 17 新增授權認證

在實驗17中,會改進GetView方法,使其變得更加安全,只有合法的使用者才能夠訪問該方法。

在本系列的第一講中,我們瞭解了Asp.Net和MVC的意義,知道MVC是Asp.net的一部分,MVC繼承了ASP.NET的所有特徵,包含表單認證。

先來了解ASP.NET是如何進行Form認證的。

  1. 終端使用者在瀏覽器的幫助下,傳送Form認證請求。
  2. 瀏覽器會發送儲存在客戶端的所有相關的使用者資料。
  3. 當伺服器端接收到請求時,伺服器會檢測請求,檢視是否存在 “Authentication Cookie”的Cookie。
  4. 如果查詢到認證Cookie,伺服器會識別使用者,驗證使用者是否合法。
  5. 如果為找到“Authentication Cookie”,伺服器會將使用者作為匿名(未認證)使用者處理,在這種情況下,如果請求的資源標記著 protected/secured,使用者將會重定位到登入頁面。

1. 建立 AuthenticationController Login 行為方法

右擊controller資料夾,選擇新增新Controller,新建並命名為”Authentication“即Controller的全稱為”AuthenticationController“。

新建Login action方法:

   1:  public class AuthenticationController : Controller
   2:  {
   3:      // GET: Authentication
   4:      public ActionResult Login()
   5:      {
   6:          return View();
   7:      }
   8:  }

2. 建立Model

在Model 資料夾下新建Model,命名為 UserDetails。

   1:  namespace WebApplication1.Models
   2:  {
   3:      public class UserDetails
   4:      {
   5:          public string UserName { get; set; }
   6:          public string Password { get; set; }
   7:      }
   8:  }

3.  建立Login View

在“~/Views/Authentication”資料夾下,新建View命名為Login,並將UserDetails轉換為強View型別。

在View中新增以下程式碼:

   1:  @model WebApplication1.Models.UserDetails
   2:   
   3:  @{
   4:   
   5:      Layout = null;
   6:   
   7:  }
   8:   
   9:  <!DOCTYPE html>
  10:   
  11:  <html>
  12:   
  13:  <head>
  14:   
  15:      <meta name="viewport" content="width=device-width" />
  16:   
  17:      <title>Login</title>
  18:   
  19:  </head>
  20:   
  21:  <body>
  22:   
  23:      <div>
  24:   
  25:          @using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
  26:   
  27:          {
  28:   
  29:              @Html.LabelFor(c=>c.UserName)
  30:   
  31:              @Html.TextBoxFor(x=>x.UserName)
  32:   
  33:             
  34:   
  35:              <br />
  36:   
  37:              @Html.LabelFor(c => c.Password)
  38:   
  39:              @Html.PasswordFor(x => x.Password)
  40:   
  41:              <br />
  42:   
  43:   
  44:              <input type="submit" name="BtnSubmit" value="Login" />
  45:   
  46:          }
  47:   
  48:      </div>
  49:   
  50:  </body>
  51:   
  52:  </html>

在上述程式碼中可以看出,使用HtmlHelper類在View中替代了純HTML程式碼。

  • View中可使用”Html”呼叫HtmlHelper類
  • HtmlHelper類函式返回html字串

示例1:

   1:  @Html.TextBoxFor(x=>x.UserName)

轉換為HTML程式碼

<input id="UserName" name="UserName" type="text" value="" />

示例2:

   1:  @using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
   2:  {
   3:  }

轉換為HTML程式碼:

   1:  <form action="/Authentication/DoLogin" method="post">
   2:  </form>

4. 執行測試

輸入Login action方法的URL:“http://localhost:8870/Authentication/Login”

5. 實現Form認證

開啟 Web.config檔案,在System.Web部分,找到Authentication的子標籤。如果不存在此標籤,就在檔案中新增Authentication標籤。

設定Authentication的Mode為Forms,loginurl設定為”Login”方法的URL.

   1:  <authentication mode="Forms">
   2:  <forms loginurl="~/Authentication/Login"></forms>
   3:  </authentication>
6. 讓Action 方法更安全 在Index action 方法中新增認證屬性 [Authorize].
   1:  [Authorize]
   2:  public ActionResult Index()
   3:  {
   4:      EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
   5:  ......

7. 執行測試,輸入 EmployeeController 的 Index action的URL:“http://localhost:8870/Employee/Index”


對於Index action的請求會自動重連結到 login action。

8. 建立業務層功能

開啟 EmployeeBusinessLayer 類,新建 IsValidUser方法:

   1:  public bool IsValidUser(UserDetails u)
   2:  {
   3:      if (u.UserName == "Admin" && u.Password == "Admin")
   4:      {
   5:          return true;
   6:      }
   7:      else
   8:      {
   9:          return false;
  10:      }
  11:  }

9. 建立 DoLogin  action 方法

開啟 AuthenticationController 類,新建action 方法命名為 DoLogin。

當點選登入時,Dologin action 方法會被呼叫。

Dologin 方法的功能:

  1. 通過呼叫業務層功能檢測使用者是否合法。
  2. 如果是合法使用者,建立認證Cookie。可用於以後的認證請求過程中。
  3. 如果是非法使用者,給當前的ModelState新增新的錯誤資訊,將錯誤資訊顯示在View中。
   1:  [HttpPost]
   2:  public ActionResult DoLogin(UserDetails u)
   3:  {
   4:      EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
   5:      if (bal.IsValidUser(u))
   6:      {
   7:          FormsAuthentication.SetAuthCookie(u.UserName, false);
   8:          return RedirectToAction("Index", "Employee");
   9:      }
  10:      else
  11:      {
  12:          ModelState.AddModelError("CredentialError", "Invalid Username or Password");
  13:          return View("Login");
  14:      }
  15:  }

10.在View 中顯示資訊

開啟Login View,在 @Html.BeginForm中 新增以下程式碼

   1:  @Html.ValidationMessage("CredentialError", new {style="color:red;" })
   2:  @using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
   3:  {

11. 執行測試

測試1

測試2

關於實驗17

為什麼Dologin會新增 HttpPost 屬性,還有其他類似的屬性嗎?

該屬性可使得DoLogin 方法開啟Post 請求。如果有人嘗試獲取DoLogin,將不會起作用。還有很多類似的屬性如HttpGet,HttpPut和HttpDelete屬性.

FormsAuthentication.SetAuthCookie是必須寫的嗎?

是必須寫的。讓我們瞭解一些小的工作細節。

  • 客戶端通過瀏覽器給伺服器傳送請求。
  • 當通過瀏覽器生成,所有相關的Cookies也會隨著請求一起傳送。
  • 伺服器接收請求後,準備響應。
  • 請求和響應都是通過HTTP協議傳輸的,HTTP是無狀態協議。每個請求都是新請求,因此當同一客戶端發出二次請求時,伺服器無法識別,為了解決此問題,伺服器會在準備好的請求包中新增一個Cookie,然後返回。
  • 當客戶端的瀏覽器接收到帶有Cookie的響應,會在客戶端建立Cookies。
  • 如果客戶端再次給伺服器傳送請求,伺服器就會識別。

FormsAuthentication.SetAuthCookie將新增 “Authentication”特殊的Cookie來響應。

是否意味著沒有Cookies,FormsAuthentication 將不會有作用?

不是的,可以使用URI代替Cookie。

開啟Web.Config檔案,修改Authentication/Forms部分:

   1:  <forms cookieless="UseUri" loginurl="~/Authentication/Login"></forms>

授權的Cookie會使用URL傳遞。

通常情況下,Cookieless屬性會被設定為“AutoDetect“,表示認證工作是通過Cookie完成的,是不支援URL傳遞的。

FormsAuthentication.SetAuthCookie中第二個引數”false“表示什麼?

false決定了是否建立永久有用的Cookie。臨時Cookie會在瀏覽器關閉時自動刪除,永久Cookie不會被刪除。可通過瀏覽器設定或是編寫程式碼手動刪除。

當憑證錯誤時,UserName 文字框的值是如何被重置的?

HTML 幫助類會從Post 資料中獲取相關值並重置文字框的值。這是使用HTML 幫助類的一大優勢。

Authorize屬性有什麼用?

Asp.net MVC中提供四種過濾器來過濾請求和響應的,Authorize屬性是在Authorize過濾器之後執行的,可以確保授權請求Action 方法處理。

需要為每個Action 方法新增授權屬性嗎?

不需要,可以將授權屬性新增到Controller 層或 Global 層。

實驗18——在View中顯示UserName

在本實驗中,我們會在View中顯示已登入的使用者名稱

1. 在ViewModel 中新增 UserName

開啟 EmployeeListViewModel,新增屬性叫:UserName。

   1:  public class EmployeeListViewModel
   2:  {
   3:      public List<EmployeeViewModel><employeeviewmodel> Employees { get; set; }
   4:      public string UserName { get; set; }
   5:  }
   6:  </employeeviewmodel>

2. 給 ViewModel  UserName 設定值

修改 EmployeeController,修改 Index 方法。

   1:  public ActionResult Index()
   2:  {
   3:      EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
   4:      employeeListViewModel.UserName = User.Identity.Name; //New Line
   5:  ......

3.  顯示 View UserName

   1:  <body>
   2:   
   3:    <div style="text-align:right"> Hello, @Model.UserName </div>
   4:   
   5:    <hr />
   6:   
   7:    <a  href="/Employee/AddNew">Add New</a>
   8:   
   9:      <div>
  10:   
  11:         <table border="1"><span style="font-size: 9pt;"> 
  12:  </span>

4. 執行

實驗 19——實現登出功能 

1. 建立登出連結,開啟Index.cshtml 建立 Logout 連結如下:

   1:  <body>
   2:   
   3:      <div style="text-align:right">Hello, @Model.UserName
   4:   
   5:      <a href="/Authentication/Logout">Logout</a></div>
   6:   
   7:      <hr />
   8:   
   9:      <a  href="/Employee/AddNew">Add New</a>
  10:   
  11:      <div>
  12:   
  13:          <table border="1">

2.  建立Logout Action 方法

開啟 AuthenticationController 新增新的Logout action方法:

   1:  public ActionResult Logout()
   2:  {
   3:      FormsAuthentication.SignOut();
   4:      return RedirectToAction("Login");
   5:  }

3.  執行

實驗20——實現登入頁面驗證

1. 新增 data annotation

開啟  UserDetails.cs,新增  Data Annotation:

   1:  public class UserDetails
   2:  {
   3:   
   4:  [StringLength(7,MinimumLength=2, ErrorMessage = "UserName length should be between 2 and 7")]
   5:      public string UserName { get; set; }
   6:      public string Password { get; set; }
   7:  }

2. 在View 中顯示錯誤資訊

修改 Login.cshtml能夠提示錯誤資訊。

   1:  @using (Html.BeginForm("DoLogin", "Authentication", FormMethod.Post))
   2:  {
   3:      @Html.LabelFor(c=>c.UserName)
   4:      @Html.TextBoxFor(x=>x.UserName)
   5:      @Html.ValidationMessageFor(x=>x.UserName)
   6:  ......

3. 修改 DoLogin

修改 DoLogin action 方法:

   1:  [HttpPost]
   2:  public ActionResult DoLogin(UserDetails u)
   3:  {
   4:      if (ModelState.IsValid)
   5:      {
   6:          EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
   7:          if (bal.IsValidUser(u))
   8:          {
   9:              FormsAuthentication.SetAuthCookie(u.UserName, false);
  10:              return RedirectToAction("Index", "Employee");
  11:          }
  12:          else
  13:          {
  14:              ModelState.AddModelError("CredentialError", "Invalid Username or Password");
  15:              return View("Login");
  16:          }
  17:      }
  18:      else
  19:      {
  20:          return View("Login");
  21:      }
  22:  }

4.  執行

實驗 21——登入頁面實現客戶端驗證

在本實驗中介紹一種方法實現客戶端驗證

1. 下載 jQuery unobtrusive Validation檔案

右擊專案,選擇“Manage Nuget packages”,點選線上查詢”jQuery Unobtrusive“,安裝”Microsoft jQuery Unobtrusive Valiadtion“

2. 在View 中新增 jQuery Validation 引用

在Scripts檔案中,新增以下 JavaScript檔案

  • jQuery-Someversion.js
  • jQuery.valiadte.js
  • jquery.validate.unobtrusive

開啟 Login.cshtml,在檔案頂部包含這三個js檔案:

   1:  <script src="~/Scripts/jquery-1.8.0.js"></script>
   2:  <script src="~/Scripts/jquery.validate.js"></script>
   3:  <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>

3. 執行

關於實驗21

客戶端驗證是如何實現的?

如上所述,客戶端驗證並不是很麻煩,在Login View中,HTML元素能夠使用幫助類來生成,Helper 函式能夠根據Data Annotation屬性的使用生成帶有屬性的HTML 標記元素。

例如:

   1:  @Html.TextBoxFor(x=>x.UserName)
   2:  @Html.ValidationMessageFor(x=>x.UserName)

根據以上程式碼生成的HTML 程式碼如下:

   1:  <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="" />
   2:  <span class="field-validation-error" data-valmsg-for="UserName" data-valmsg-replace="true"> </span>

jQuery Unobtrusive驗證檔案會使用這些自定義的HTML 屬性,驗證會在客戶端自動生成。自動進行客戶端驗證是使用HTML 幫助類的又一大好處。

是否可以使用不帶HTML 幫助類的JavaScript  驗證?

是,可手動新增屬性。

總結

這就是本節所講的使用者授權與客戶端驗證的實現,在第五天我們會講到更高階的應用,請持續關注,不要走開哦!

有了本節MVC關於使用者授權與客戶端驗證的講解,相信會對大家的MVC開發過程有所幫助。在使用MVC進行開發時,還可以利用一些開發工具。使用  這款輕量級控制元件,在開發效率大大提高的同時,工作量也會大大減少。

相關閱讀: