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

 

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

系列文章

七天學會ASP.NET MVC (一)——深刻理解ASP.NET MVChtml

七天學會ASP.NET MVC (二)——ASP.NET MVC 數據傳遞web

七天學會ASP.NET MVC (三)——ASP.Net MVC 數據處理正則表達式

七天學會ASP.NET MVC (四)——用戶受權認證問題編程

七天學會ASP.NET MVC (五)——Layout頁面使用和用戶角色管理 服務器

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

目錄

實驗27——添加批量上傳選項app

關於實驗27異步

實驗27存在的問題async

解決方法ide

實驗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

相關文章
相關標籤/搜索