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

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

d+ mit nes 如何 bus blog edi default 繼續

本節又帶了一些常用的,卻很難理解的問題,本節從文件上傳功能的實現引出了線程使用,介紹了線程饑餓的解決方法,異常處理方法,了解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開發時,還可以借助一些開發工具來幫助開發過程。 ComponentOne Studio ASP.NET MVC 是一款針對 MVC 平臺的控件包,它與 Visual Studio 無縫集成,完全與 MVC6 和 ASP.NET 5.0 兼容,將大幅提高工作效率。

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

原文鏈接:http://www.codeproject.com/Articles/1002109/Learn-MVC-Project-in-days-Day-6

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