1. 程式人生 > >七天學會ASP.NET MVC (六)——執行緒問題、異常處理、自定義URL

七天學會ASP.NET MVC (六)——執行緒問題、異常處理、自定義URL

本文參考自:http://www.codeproject.com/Articles/1002109/Learn-MVC-Project-in-days-Day-6

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

 下載

本節又帶了一些常用的,卻很難理解的問題,本節從檔案上傳功能的實現引出了執行緒使用,介紹了執行緒飢餓的解決方法,異常處理方法,瞭解RouteTable自定義路徑 。

系列文章

目錄

實驗27——新增批量上傳選項

關於實驗27

實驗27存在的問題

解決方法

實驗28——解決執行緒飢餓問題

實驗29——異常處理—顯示自定義錯誤頁面

關於實驗29

理解實驗29中的限制 

實驗30—異常處理—日誌異常

關於實驗30

理解RouteTable

理解Asp.net MVC 請求週期

實驗31—實現使用者友好URLs

關於實驗31

總結

實驗27——新增批量上傳選項

在實驗27中,我們將提供一個選項,供使用者選擇上傳Employee記錄檔案(CSV格式)。

我們會學習以下知識:

1. 如何使用檔案上傳控制元件

2. 非同步控制器

1. 建立 FileUploadViewModel

在ViewModels資料夾下新建類“FileUploadViewModel”,如下:

   1:  public class
FileUploadViewModel: BaseViewModel
   2:  {
   3:      public HttpPostedFileBase fileUpload {get; set ;}
   4:  }

HttpPostedFileBase 將通過客戶端提供上傳檔案的訪問入口。

2. 建立 BulkUploadController 和Index action 方法

新建 controller“BulkUploadController”,並實現Index Action 方法,如下:

   1:  public class BulkUploadController : Controller
   2:  {
   3:          [HeaderFooterFilter]
   4:          [AdminFilter]
   5:          public ActionResult Index()
   6:          {
   7:              return View(new FileUploadViewModel());
   8:          } 
   9:  }

Index方法與 HeaderFooterFilter 和 AdminFilter屬性繫結。HeaderFooterFilter會確保頁首和頁尾資料能夠正確傳遞到ViewModel中,AdminFilter限制非管理員使用者的訪問。
3.建立上傳View

建立以上Action方法的View。View名稱應為 index.cshtml,且存放在“~/Views/BulkUpload”資料夾下。

4. 設計上傳View

在View中輸入以下內容:

   1:  @using WebApplication1.ViewModels
   2:  @model FileUploadViewModel
   3:  @{
   4:      Layout = "~/Views/Shared/MyLayout.cshtml";
   5:  }
   6:   
   7:  @section TitleSection{
   8:      Bulk Upload
   9:  }
  10:  @section ContentBody{
  11:      <div> 
  12:      <a href="/Employee/Index">Back</a>
  13:          <form action="/BulkUpload/Upload" method="post" enctype="multipart/form-data">
  14:              Select File : <input type="file" name="fileUpload" value="" />
  15:              <input type="submit" name="name" value="Upload" />
  16:          </form>
  17:      </div>
  18:  }

如上,FileUploadViewModel中屬性名稱與 input[type="file"]的名稱類似,都稱為“fileUpload”。我們在Model Binder中已經講述了名稱屬性的重要性,注意:在表單標籤中,有一個額外的屬性是加密的,會在實驗結尾處講解。

5. 建立業務層上傳方法

在  EmployeeBusinessLayer中新建方法 UploadEmployees,如下:

   1:  public void UploadEmployees(List<Employee> employees)
   2:  {
   3:      SalesERPDAL salesDal = new SalesERPDAL();
   4:      salesDal.Employees.AddRange(employees);
   5:      salesDal.SaveChanges();
   6:  }<employee>
   7:  </employee>

6. 建立Upload Action 方法

建立Action 方法,並命名為 “BulkUploadController”,如下:

   1:  [AdminFilter]
   2:  public ActionResult Upload(FileUploadViewModel model)
   3:  {
   4:      List<Employee> employees = GetEmployees(model);
   5:      EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
   6:      bal.UploadEmployees(employees);
   7:      return RedirectToAction("Index","Employee");
   8:  }
   9:   
  10:  private List<Employee> GetEmployees(FileUploadViewModel model)
  11:  {
  12:      List<Employee> employees = new List<Employee>();
  13:      StreamReader csvreader = new StreamReader(model.fileUpload.InputStream);
  14:      csvreader.ReadLine(); // Assuming first line is header
  15:      while (!csvreader.EndOfStream)
  16:      {
  17:          var line = csvreader.ReadLine();
  18:          var values = line.Split(',');//Values are comma separated
  19:          Employee e = new Employee();
  20:          e.FirstName = values[0];
  21:          e.LastName = values[1];
  22:          e.Salary = int.Parse(values[2]);
  23:          employees.Add(e);
  24:      }
  25:      return employees;
  26:  }

AdminFilter會繫結到Upload action方法中,限制非管理員使用者的訪問。

7. 建立BulkUpload連結

開啟 “Views/Employee”資料夾下的 AddNewLink.cshtml 檔案,輸入BulkUpload連結,如下:

<a href="/Employee/AddNew">Add New</a>
  
<a href="/BulkUpload/Index">BulkUpload</a>

8.執行

8.1 建立一個樣本檔案來測試,如圖所示

8.2 執行,點選BulkUpload連結

選擇檔案並點選確認

關於實驗 27

為什麼在實驗27中不需要驗證?

在該選項中新增客戶端和伺服器端驗證需要讀者自行新增的,以下是新增驗證的提示:

  • 伺服器端驗證可使用Data Annotations。
  • 客戶端驗證可利用客戶端的資料解釋和執行jQuery的驗證。必須手動設定自定義資料屬性,因為並沒有將Htmlhelper 方法設定為檔案輸入。
  • 客戶端驗證可編寫JavaScript 程式碼,通過點選按鈕來實現。這個方法並不是很難,由於檔案輸入是由輸入控制元件完成,值可以在JavaScript中獲取及驗證 。

什麼是 HttpPostedFileBase?

HttpPostedFileBase將通過客戶端提供檔案上傳的訪問入口,Model Binder 會在Post請求期間更新 FileUploadViewModel類中的所有屬性值。我們在FileUploadViewModel內部只有一個屬性,Model Binder會通過客戶端設定它實現檔案上傳。

是否會提供多檔案的輸入控制元件?

是,有兩種方法可以實現:

1. 建立多檔案輸入控制元件,每個控制元件有唯一的名稱,FileUploadViewModel類會為每個控制元件建立 HttpPostedFileBase型別的屬性,每個屬性名稱應該與控制元件名稱匹配。

2. 建立多檔案輸入控制元件,每個控制元件有相同的名稱,建立型別的List列表,代替建立多個HttpPostedFileBase型別的屬性。

enctype="multipart/form-data" 是用來做什麼的?

該屬性指定了post 資料的編碼型別,預設屬性值是”application/x-www-form-urlencoded“

例1—登入窗體會給伺服器傳送以下Post 請求

   1:  POST /Authentication/DoLogin HTTP/1.1
   2:  Host: localhost:8870
   3:  Connection: keep-alive
   4:  Content-Length: 44
   5:  Content-Type: application/x-www-form-urlencoded
   6:  ...
   7:  ...
   8:  UserName=Admin&Passsword=Admin&BtnSubmi=Login

所有輸入值會被作為傳送的值的一部分,以”key/value“的形式傳送。

當 enctype="multipart/form-data" 屬性被加入Form標籤中,以下post 請求會被髮送到伺服器。

   1:  POST /Authentication/DoLogin HTTP/1.1
   2:  Host: localhost:8870
   3:  Connection: keep-alive
   4:  Content-Length: 452
   5:  Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywHxplIF8cR8KNjeJ
   6:  ...
   7:  ...
   8:  ------WebKitFormBoundary7hciuLuSNglCR8WC
   9:  Content-Disposition: form-data; name="UserName"
  10:   
  11:  Admin
  12:  ------WebKitFormBoundary7hciuLuSNglCR8WC
  13:  Content-Disposition: form-data; name="Password"
  14:   
  15:  Admin
  16:  ------WebKitFormBoundary7hciuLuSNglCR8WC
  17:  Content-Disposition: form-data; name="BtnSubmi"
  18:   
  19:  Login
  20:  ------WebKitFormBoundary7hciuLuSNglCR8WC--

如上所示,Form會在多部分post傳送,每部分都是被分界線分割的,每部分包含單值。

如果form標籤包含檔案輸入控制元件的話,enctype必須被設定為”multipart/form-data“。

為什麼有時候需要設定 encType 為 “multipart/form-data”,而有時候不需要設定?

當encType  設定為”multipart/form-data“,將會實現Post資料和上傳檔案的功能,當然也會增加請求的size 增加,請求size 越大意味著效能越低。因此得出的最佳實踐經驗需要設定為預設的”application/x-www-form-urlencoded“。

為什麼在實驗27中建立ViewModel?

在View中已經有一個控制元件了,我們需要通過直接新增 HttpPostedFileBase型別的引數,並命名為”fileUpload“實現相同的結果,從而替代建立獨立的ViewModel。

   1:  public ActionResult Upload(HttpPostedFileBase fileUpload)
   2:  {
   3:  }

建立 ViewModel是最好的方法,Controller應該以 ViewModel的形式給View傳送資料,且資料必須來自Controller。

以上問題的解決方法

是否存在疑慮,當傳送請求時,如何獲取響應?

眾人皆知的程式設計規則,程式中任何事件都是由執行緒執行的,請求事件也是。

Asp.net  framework 維護執行緒池,每次當請求傳送到webserver時,會從執行緒池中分配空閒的執行緒處理此請求。這種執行緒被稱為worker執行緒。

當請求處理完成,該執行緒無法服務其他請求時,worker 執行緒會被阻塞。現在我們來了解什麼是執行緒飢餓,如果一個應用程式接收到很多請求,且處理每個請求都非常耗時。在這種情況下,我們就必須指定一個點來結束請求,當有新的請求進入狀態時,沒有worker 執行緒可使用,這種現象稱為執行緒飢餓。

在我們的示例程式中只包含2個員工記錄,而在實際使用情況下,會包含成千上萬的記錄,這就意味著將耗費大量的時間來處理請求。這種情況就可能導致執行緒飢餓.

執行緒飢餓的解決方法:

截至現在我們討論的請求型別都是同步請求。如果使用非同步請求來代替同步請求,那麼執行緒飢餓的問題就得到解決了。

  • 非同步請求的情況下,會分配worker執行緒來服務請求。
  • worker 執行緒初始化非同步操作,並返回到執行緒池服務其他請求。非同步操作可使用CLR 執行緒來繼續執行。
  • 存在的問題就是,CLR 執行緒無法返回響應,一旦它完成了非同步操作,它會通知Asp.net。
  • Webserver 再次獲取一個worker執行緒來處理剩餘的請求,並返回響應。

上述使用場景中,會獲取兩次worker 執行緒,這兩次獲取的執行緒可能相同,也可能會不同。

檔案讀取是I/O操作,不需要使用worker 執行緒處理。因此最好將同步請求轉換為非同步。

同步請求的響應時間能提升嗎?

不可以,響應時間是相同的,執行緒會被釋放來服務其他請求。

實驗28——解決執行緒飢餓問題

在Asp.net MVC中會通過將同步Action方法轉換為非同步Action方法,將同步請求轉換為非同步請求。

1. 建立非同步控制器

在控制器中將基類 UploadController修改為 AsynController。

   1:  {
   2:      public class BulkUploadController : AsyncController
   3:      {

2. 轉換同步Action方法

該功能通過兩個關鍵字就可實現:“async “和” await”

   1:  [AdminFilter]
   2:  public async Task<ActionResult> Upload(FileUploadViewModel model)
   3:  {
   4:      int t1 = Thread.CurrentThread.ManagedThreadId;
   5:      List<Employee> employees = await Task.Factory.StartNew<List<Employee>>
   6:          (() => GetEmployees(model));
   7:      int t2 = Thread.CurrentThread.ManagedThreadId;
   8:      EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
   9:      bal.UploadEmployees(employees);
  10:      return RedirectToAction("Index", "Employee");
  11:  }<actionresult><employee><list<employee>
  12:  </list<employee></employee></actionresult>

在action方法的開始或結束處,使用變數儲存執行緒ID。

理一下思路:

  • 當上傳按鈕被點選時,新請求會被髮送到伺服器。
  • Webserver從執行緒池中產生Worker執行緒 ,並分配給伺服器請求。
  • worker執行緒會使Action 方法執行
  • Worker方法在 Task.Factory.StartNew方法的輔助下,開啟非同步操作
  • 使用async關鍵字將Action 方法標記為非同步方法,由此會保證非同步操作一旦開啟,Worker 執行緒就會釋放。
  • 使用await關鍵字也可標記非同步操作,能夠保證非同步操作完成時才能夠繼續執行下面的程式碼。
  • 一旦非同步操作在Action 方法中完成執行,必須執行worker執行緒。因此webserver將會新建一個空閒worker 執行緒,並用來服務剩下的請求,提供響應。

3. 測試執行

執行應用程式,並跳轉到BulkUpload頁面。會在程式碼中顯示斷點,輸入樣本檔案,點選上傳。

如圖所示,在專案啟動或關閉時有的執行緒ID是不同的。

實驗29——異常處理—顯示自定義錯誤頁面

如果一個專案不考慮異常處理,那麼可以說這個專案是不完整的。到目前為止,我們已經瞭解了MVC中的兩個過濾器:Action filter和 Authorization filter。現在我們來學習第三個過濾器,異常過濾器(Exception Filters)。

什麼是異常過濾器(Exception Filters)?

異常過濾器與其他過濾器的用法相同,可當作屬性使用。使用異常過濾器的基本步驟:

1. 使它們可用

2. 將過濾器作為屬性,應用到action 方法或控制器中。我們也可以在全域性層次使用異常過濾器。

異常過濾器的作用是什麼?,是否有自動執行的異常過濾器?

一旦action 方法中出現異常,異常過濾器就會控制程式的執行過程,開始內部自動寫入執行的程式碼。MVC為我們提供了編寫好的異常過濾器:HandeError。

當action方法中發生異常時,過濾器就會在“~/Views/[current controller]”或“~/Views/Shared”目錄下查詢到名稱為”Error”的View,然後建立該View的ViewResult,並作為響應返回。

接下來我們會講解一個Demo,幫助我們更好的理解異常過濾器的使用。

已經實現的上傳檔案功能,很有可能會發生輸入檔案格式錯誤。因此我們需要處理異常。

1. 建立含錯誤資訊的樣本檔案,包含一些非法值,如圖,Salary就是非法值。

2. 執行,查詢異常,點選上傳按鈕,選擇已建立的樣本資料,選擇上傳。

3. 啟用異常過濾器

當自定義異常被捕獲時,異常過濾器變為可用。為了能夠獲得自定義異常,開啟Web.config檔案,在System.Web.Section下方新增自定義錯誤資訊。

   1:  <system.web>
   2:      <customErrors mode="On"></customErrors>

4. 建立Error View

在“~/Views/Shared”資料夾下,會發現存在“Error.cshtml”檔案,該檔案是由MVC 模板提供的,如果沒有自動建立,該檔案也可以手動完成。

   1:  @{
   2:      Layout = null;
   3:  }
   4:   
   5:  <!DOCTYPE html>
   6:  <html>
   7:  <head>
   8:      <meta name="viewport" content="width=device-width" />
   9:      <title>Error</title>
  10:  </head>
  11:  <body>
  12:      <hgroup>
  13:          <h1>Error.</h1>
  14:          <h2>An error occurred while processing your request.</h2>
  15:      </hgroup>
  16:  </body>
  17:  </html>

5. 繫結異常過濾器

將過濾器繫結到action方法或controller上,不需要手動執行,開啟 App_Start folder資料夾中的 FilterConfig.cs檔案。在 RegisterGlobalFilters 方法中會看到 HandleError 過濾器已經以全域性過濾器繫結成功。

   1:  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
   2:  {
   3:      filters.Add(new HandleErrorAttribute());//ExceptionFilter
   4:      filters.Add(new AuthorizeAttribute());
   5:  }

如果需要刪除全域性過濾器,那麼會將過濾器繫結到action 或controller層,但是不建議這麼做,最好是在全域性中應用如下:

   1:  [AdminFilter]
   2:  [HandleError]
   3:  public async Task<ActionResult> Upload(FileUploadViewModel model)
   4:  {<actionresult>
   5:  </actionresult>

6. 執行

7. 在View中顯示錯誤資訊

將Error View轉換為HandleErrorInfo類的強型別View,並在View中顯示錯誤資訊。

   1:  @model HandleErrorInfo
   2:  @{
   3:      Layout = null;
   4:  }
   5:   
   6:  <!DOCTYPE html>
   7:  <html>
   8:  <head>
   9:      <meta name="viewport" content="width=device-width" />
  10:      <title>Error</title>
  11:  </head>
  12:  <body>
  13:      <hgroup>
  14:          <h1>Error.</h1>
  15:          <h2>An error occurred while processing your request.</h2>
  16:      </hgroup>
  17:          Error Message :@Model.Exception.Message<br />
  18:          Controller: @Model.ControllerName<br />
  19:          Action: @Model.ActionName
  20:  </body>
  21:  </html>

8. 執行測試

Handle error屬效能夠確保無論是否出現異常,自定義View都能夠顯示,但是它的能力在controller和action 方法中是受限的。不會處理“Resource not found”這型別的錯誤。

執行應用程式,輸一些奇怪的URL

9. 建立 ErrorController控制器,並建立Index方法,程式碼如下:

   1:  public class ErrorController : Controller
   2:  {
   3:      // GET: Error
   4:      public ActionResult Index()
   5:      {
   6:          Exception e=new Exception("Invalid Controller or/and Action Name");
   7:          HandleErrorInfo eInfo = new HandleErrorInfo(e, "Unknown", "Unknown");
   8:          return View("Error", eInfo);
   9:      }
  10:  }

10. 在非法URL中顯示自定義Error檢視

可在 web.config中定義“Resource not found error”的設定,如下:

   1:  <system.web>
   2:      <customErrors mode="On">
   3:        <error statusCode="404" redirect="~/Error/Index"/>
   4:      </customErrors>

11. 使 ErrorController 全域性可訪問。

將AllowAnonymous屬性應用到 ErrorController中,因為錯誤控制器和index方法不應該只繫結到認證使用者,也很有可能使用者在登入之前已經輸入錯誤的URL。

   1:  [AllowAnonymous]
   2:  public class ErrorController : Controller
   3:  {

12. 執行

關於實驗29

View的名稱是否可以修改?

可以修改,不一定叫Error,也可以指定其他名字。如果Error View的名稱改變了,當繫結HandleError過濾器時,必須制定View的名稱。

   1:  [HandleError(View="MyError")]
   2:  Or
   3:  filters.Add(new HandleErrorAttribute()
   4:                  {
   5:                      View="MyError"
   6:                  });

是否可以為不同的異常獲取不同的Error View?

可以,在這種情況下,必須多次應用Handle error filter。

   1:  [HandleError(View="DivideError",ExceptionType=typeof(DivideByZeroException))]
   2:  [HandleError(View = "NotFiniteError", ExceptionType = typeof(NotFiniteNumberException))]
   3:  [HandleError]
   4:   
   5:  OR
   6:   
   7:  filters.Add(new HandleErrorAttribute()
   8:      {
   9:          ExceptionType = typeof(DivideByZeroException),
  10:          View = "DivideError"
  11:      });
  12:  filters.Add(new HandleErrorAttribute()
  13:  {
  14:      ExceptionType = typeof(NotFiniteNumberException),
  15:      View = "NotFiniteError"
  16:  });
  17:  filters.Add(new HandleErrorAttribute());

前兩個Handle error filter都指定了異常,而最後一個更為常見更通用,會顯示所有其他異常的Error View。

上述實驗中並沒有處理登入異常,我們會在實驗30中講解登入異常。

實驗30——異常處理—登入異常

1. 建立 Logger 類

在根目錄下,新建資料夾,命名為Logger。在Logger 資料夾下新建類 FileLogger

   1:  namespace WebApplication1.Logger
   2:  {
   3:      public class FileLogger
   4:      {
   5:          public void LogException(Exception e)
   6:          {
   7:              File.WriteAllLines("C://Error//" + DateTime.Now.ToString("dd-MM-yyyy mm hh ss")+".txt", 
   8:                  new string[] 
   9:                  {
  10:                      "Message:"+e.Message,
  11:                      "Stacktrace:"+e.StackTrace
  12:                  });
  13:          }
  14:      }
  15:  }

2.  建立 EmployeeExceptionFilter 類

在 Filters資料夾下,新建 EmployeeExceptionFilter類

   1:  namespace WebApplication1.Filters
   2:  {
   3:      public class EmployeeExceptionFilter
   4:      {
   5:      }
   6:  }

3. 擴充套件 Handle Error實現登入異常處理

讓 EmployeeExceptionFilter 繼承 HandleErrorAttribute類,重寫 OnException方法:

   1:  public class EmployeeExceptionFilter:HandleErrorAttribute
   2:  {
   3:      public override void OnException(ExceptionContext filterContext)
   4:      {
   5:          base.OnException(filterContext);
   6:      }
   7:  }

4. 定義 OnException 方法

在 OnException 方法中包含異常登入程式碼。

   1:  public override void OnException(ExceptionContext filterContext)
   2:  {
   3:      FileLogger logger = new FileLogger();
   4:      logger.LogException(filterContext.Exception);
   5:      base.OnException(filterContext);
   6:  }

5. 修改預設的異常過濾器

開啟 FilterConfig.cs檔案,刪除 HandErrorAtrribute,新增上步中建立的。

   1:  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
   2:  {
   3:      //filters.Add(new HandleErrorAttribute());//ExceptionFilter
   4:      filters.Add(new EmployeeExceptionFilter());
   5:      filters.Add(new AuthorizeAttribute());
   6:  }

6. 執行

會在C盤中建立“Error”資料夾,存放一些error檔案。

關於實驗30

當異常出現後,Error View 是如何返回響應的?

檢視 OnException 方法的最後一行程式碼:

   1:  base.OnException(filterContext);

即基類的 OnException 方法執行並返回Error View 的ViewResult。

在 OnException 中,是否可以返回其他結果?

可以,程式碼如下:

   1:  public override void OnException(ExceptionContext filterContext)
   2:  {
   3:      FileLogger logger = new FileLogger();
   4:      logger.LogException(filterContext.Exception);
   5:      //base.OnException(filterContext);
   6:      filterContext.ExceptionHandled = true;
   7:      filterContext.Result = new ContentResult()
   8:      {
   9:          Content="Sorry for the Error"
  10:      };
  11:  }

當返回自定義響應時,做的第一件事情就是通知MVC 引擎,手動處理異常,因此不需要執行預設的操作,不會顯示預設的錯誤頁面。使用以下語句可完成:

   1:  filterContext.ExceptionHandled = true

Routing

到目前為止,我們已經解決了MVC的很多問題,但忽略了最基本的最重要的一個問題:當用戶傳送請求時,會發生什麼?

最好的答案是“執行Action 方法”,但仍存在疑問:對於一個特定的URL請求,如何確定控制器和action 方法。在開始實驗31之前,我們首先來解答上述問題,你可能會困惑為什麼這個問題會放在最後來講,因為了解內部結構之前,需要更好的瞭解MVC。

理解RouteTable

在Asp.net mvc中有RouteTable這個概念,是用來儲存URL 路徑的,簡而言之,是儲存已定義的應用程式的可能的URL pattern的集合。

預設情況下,路徑是專案模板組成的一部分。可在 Global.asax 檔案中檢查到,在 Application_Start中會發現以下語句:

   1:  RouteConfig.RegisterRoutes(RouteTable.Routes);

App_Start資料夾下的 RouteConfig.cs檔案,包含以下程式碼塊:

   1:  namespace WebApplication1
   2:  {
   3:      public class RouteConfig
   4:      {
   5:          public static void RegisterRoutes(RouteCollection routes)
   6:          {
   7:              routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   8:   
   9:              routes.MapRoute(
  10:                  name: "Default",
  11:                  url: "{controller}/{action}/{id}",
  12:                  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  13:              );
  14:          }
  15:      }
  16:  }

RegisterRoutes方法已經包含了由routes.MapRoute 方法定義的預設的路徑。已定義的路徑會在請求週期中確定執行的是正確的控制器和action 方法。如果使用 route.MapRoute建立了多個路徑,那麼內部路徑的定義就意味著建立Route物件。

MapRoute 方法也可與 RouteHandler 關聯。

理解ASP.NET MVC 請求週期

在本節中我們只講解請求週期中重要的知識點

1.  UrlRoutingModule

當終端使用者傳送請求時,會通過UrlRoutingModule 物件傳遞,UrlRoutingModule 是HTTP 模組。

2. Routing

UrlRoutingModule 會從route table集合中獲取首次匹配的Route 物件,為了能夠匹配成功,請求URL會與route中定義的URL pattern 匹配。

當匹配的時候必須考慮以下規則:

  • 數字引數的匹配(請求URL和URL pattern中的數字)

  • URL pattern中的可選引數:

  • 引數中定義的靜態引數


3. 建立MVC Route Handler

一旦Route 物件被選中,UrlRoutingModule會獲得 Route物件的 MvcRouteHandler物件。

4. 建立 RouteData 和 RequestContext

UrlRoutingModule使用Route物件建立RouteData,可用於建立RequestContext。RouteData封裝了路徑的資訊如Controller名稱,action名稱以及route引數值。

Controller 名稱

為了從URL 中獲取Controller名稱,需要按規則執行如在URL pattern中{Controller}是標識Controller名稱的關鍵字。

Action Method 名稱

為了獲取action 方法名稱,{action}是標識action 方法的關鍵字。

Route 引數

URL pattern能夠獲得以下值:

1.{controller}

2.{action}

3. 字串,如 “MyCompany/{controller}/{action}”,“MyCompany”是字串。

4. 其他,如“{controller}/{action}/{id}”,”id“是路徑的引數。

例如:

Route pattern - > “{controller}/{action}/{id}”

請求 URL ->http://localhost:8870/BulkUpload/Upload/5

測試1

   1:  public class BulkUploadController : Controller
   2:  {
   3:      public ActionResult Upload (string id)
   4:      {
   5:         //value of id will be 5 -> string 5
   6:         ...
   7:      }
   8:  }

測試2

   1:  public class BulkUploadController : Controller
   2:  {
   3:      public ActionResult Upload (int id)
   4:      {
   5:         //value of id will be 5 -> int 5
   6:         ...
   7:      }
   8:  }

測試3

   1:  public class BulkUploadController : Controller
   2:  {
   3:      public ActionResult Upload (string MyId)
   4:      {
   5:         //value of MyId will be null
   6:         ...
   7:      }
   8:  }

5. 建立MVC Handler

MvcRouteHandler 會建立 MVCHandler的例項傳遞 RequestContext物件

6. 建立Controller例項

MVCHandler會根據 ControllerFactory的幫助建立Controller例項

7. 執行方法

MVCHandler呼叫Controller的執行方法,執行方法是由Controller的基類定義的。

8. 呼叫Action 方法

每個控制器都有與之關聯的 ControllerActionInvoker物件。在執行方法中ControllerActionInvoker物件呼叫正確的action 方法。

9. 執行結果

Action方法會接收到使用者輸入,並準備好響應資料,然後通過返回語句返回執行結果,返回型別可能是ViewResult或其他。

實驗31——實現對使用者有好的URL

1. 重新定義 RegisterRoutes  方法

RegisterRoutes 方法中包含 additional route

   1:  public static void RegisterRoutes(RouteCollection routes)
   2:  {
   3:      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   4:   
   5:      routes.MapRoute(
   6:      name: "Upload",
   7:      url: "Employee/BulkUpload",
   8:      defaults: new { controller = "BulkUpload", action = "Index" }
   9:      );
  10:   
  11:      routes.MapRoute(
  12:          name: "Default",
  13:          url: "{controller}/{action}/{id}",
  14:          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  15:      );
  16:  }

2. 修改URL 引用

開啟“~/Views/Employee”檔案下的 AddNewLink.cshtml ,修改BulkUpload 連結,如下:

   1:   
   2:  <a href="/Employee/BulkUpload">BulkUpload</a>

3. 執行測試

關於實驗31

之前的URL 現在是否起作用?

是,仍然有用。BulkUploadController中的Index 方法可通過兩個URL 訪問。

1. ”http://localhost:8870/Employee/BulkUpload“

2. “http://localhost:8870/BulkUpload/Index”

Route 引數和Query 字串有什麼區別?

  • Query 字串本身是有大小限制的,而無法定義Route 引數的個數。
  • 無法在Query 字串值中新增限制,但是可以在Route 引數中新增限制。
  • 可能會設定Route引數的預設值,而Query String不可能有預設值。
  • Query 字串可使URL 混亂,而Route引數可保持它有條理。

如何在Route 引數中使用限制?

可使用正則表示式。

如:

   1:  routes.MapRoute(
   2:      "MyRoute",
   3:      "Employee/{EmpId}",
   4:      new {controller=" Employee ", action="GetEmployeeById"},
   5:      new { EmpId = @"\d+" }
   6:   );

Action 方法:

   1:  public ActionResult GetEmployeeById(int EmpId)
   2:  {
   3:     ...
   4:  }

為了保證每個路徑引數都能獨立,因此引數名稱必須與Route Parameter一致。

是否需要將action 方法中的引數名稱與Route 引數名稱保持一致?

Route Pattern 也許會包含一個或多個RouteParameter,為了區分每個引數,必須保證action 方法的引數名稱與Route 引數名稱相同。

定義路徑的順序重要嗎?

有影響,在上面的實驗中,我們定義了兩個路徑,一個是自定義的,一個是預設的。預設的是最先定義的,自定義路徑是在之後定義的。

當用戶輸入“http://.../Employee/BulkUpload”地址後傳送請求,UrlRoutingModule會搜尋與請求URL 匹配的預設的route pattern ,它會將 Employee作為控制器的名稱,“BulkUpload”作為action 方法名稱。因此定義的順序是非常重要的,更常用的路徑應放在最後。

是否有什麼簡便的方法來定義Action 方法的URL pattern?

我們可使用基於 routing 的屬性。

1.  基本的routing 屬性可用

在 RegisterRoutes 方法中在 IgnoreRoute語句後輸入程式碼如下:

   1:  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   2:   
   3:  routes.MapMvcAttributeRoutes();
   4:   
   5:  routes.MapRoute(
   6:  ...

2. 定義action 方法的 route pattern

   1:  [Route("Employee/List")]
   2:  public ActionResult Index()
   3:  {

3. 執行測試

routing 屬性可定義route 引數,如下:

   1:  [Route("Employee/List/{id}")]
   2:  publicActionResult Index (string id) { ... }

IgnoreRoutes 的作用是什麼?

當我們不想使用routing作為特別的擴充套件時,會使用IgnoreRoutes。作為MVC模板的一部分,在RegisterRoute 方法中下列語句是預設的:

   1:  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

這就是說如果使用者傳送以“.axd”為結束的請求,將不會有任何路徑載入的操作,請求將直接定位到物理資源。

總結

本節內容中講述的執行緒問題是我們在MVC開發過程中經常遇到的,所以希望大家深入學習。同時在進行MVC開發時,還可以藉助一些開發工具來幫助開發過程。是一款針對 MVC 平臺的控制元件包,它與 Visual Studio 無縫整合,完全與 MVC6 和 ASP.NET 5.0 相容,將大幅提高工作效率。

6天的MVC 學習已經完成了,希望大家能夠將所講的知識充分理解,充分吸收。第7章我們會使用MVC,JQUery 和Ajax建立簡單的頁面應用。歡迎大家持續關注!

相關閱讀: