七天學會ASP.NET MVC (六)——線程問題、異常處理、自定義URL
本節又帶了一些常用的,卻很難理解的問題,本節從文件上傳功能的實現引出了線程使用,介紹了線程饑餓的解決方法,異常處理方法,了解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