本文參考自:http://www.codeproject.com/Articles/1002109/Learn-MVC-Project-in-days-Day-6
轉載請注明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
本節又帶了一些常用的,卻很難理解的問題,本節從文件上傳功能的實現引出了線程使用,介紹了線程飢餓的解決方法,異常處理方法,了解RouteTable自定義路徑 。
系列文章
七天學會ASP.NET MVC (一)——深入理解ASP.NET MVC
七天學會ASP.NET MVC (二)——ASP.NET MVC 數據傳遞
七天學會ASP.NET MVC (三)——ASP.Net MVC 數據處理
七天學會ASP.NET MVC (五)——Layout頁面使用和用戶角色管理
七天學會ASP.NET MVC (六)——線程問題、異常處理、自定義URL
目錄
實驗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
相關閱讀:

