歡迎來到第六天的 MVC 系列學習中。但願你在閱讀此篇文章的時候,已經學習了前五天的內容,這也是第六天學習的前提條件。html
在這個實驗中,咱們將會建立一個選項,用於從 CSV 文件中上傳多個 Employees。web
咱們將會作兩件事。正則表達式
學會如何運用文件上傳控件。編程
異步控制器。瀏覽器
第一步:建立 FileUploadViewModel安全
在 ViewModels 文件夾下建立一個類,命名爲 FileUploadViewModel。服務器
public class FileUploadViewModel: BaseViewModel { public HttpPostedFileBase fileUpload {get; set ;} }
HttpPostedFileBase 將會經過客戶端提供上傳文件的訪問入口。mvc
第二步:建立 BulkUploadController 和 Index 行爲方法app
建立一個新的控制器,命名爲 BulkUploadController,以及一個行爲方法,命名爲 Index。異步
public class BulkUploadController : Controller { [HeaderFooterFilter] [AdminFilter] public ActionResult Index() { return View(new FileUploadViewModel()); } }
正如你所看見的,Index 行爲方法附上了 HeaderFooterFilter 和 AdminFilter 屬性。HeaderFooterFilter 確保了正確了頁眉和頁腳數據傳輸到 ViewModel,AdminFilter 限制了 Non-Admin 用戶訪問行爲方法。
第三步:建立上傳視圖
爲上述行爲方法建立一個視圖。
須要注意的是,視圖的名稱應該爲 Index.cshtml,而且應該放置在「~/Views/BulkUpload」文件夾下。
第四步:設計上傳視圖
在視圖中放置以下內容。
@using WebApplication1.ViewModels @model FileUploadViewModel @{ Layout = "~/Views/Shared/MyLayout.cshtml"; } @section TitleSection{ Bulk Upload } @section ContentBody{ <div> <a href="/Employee/Index">Back</a> <form action="/BulkUpload/Upload" method="post" enctype="multipart/form-data"> Select File : <input type="file" name="fileUpload" value="" /> <input type="submit" name="name" value="Upload" /> </form> </div> }
正如你所看見的,在 FileUploadViewModel 中,屬性的名稱和 input[type="file"] 的名稱是同樣的,都是「FileUpload」。咱們在 Model Binder 實驗中已經講述了名稱屬性的重要性。
注意:在 Form 標籤中,有一個額外的指定加密屬性,咱們將會在實驗結尾處討論它。
第五步:建立業務層上傳方法
在 EmployeeBusinessLayer 中建立一個新的方法,命名爲 UploadEmployees。
public void UploadEmployees(List<Employee> employees) { SalesERPDAL salesDal = new SalesERPDAL(); salesDal.Employees.AddRange(employees); salesDal.SaveChanges(); }
第六步:建立上傳行爲方法
在 BulkUploadController 中建立一個新的行爲方法,命名爲 Upload。
[AdminFilter] public ActionResult Upload(FileUploadViewModel model) { List<Employee> employees = GetEmployees(model); EmployeeBusinessLayer bal = new EmployeeBusinessLayer(); bal.UploadEmployees(employees); return RedirectToAction("Index","Employee"); } private List<Employee> GetEmployees(FileUploadViewModel model) { List<Employee> employees = new List<Employee>(); StreamReader csvreader = new StreamReader(model.fileUpload.InputStream); csvreader.ReadLine(); // Assuming first line is header while (!csvreader.EndOfStream) { var line = csvreader.ReadLine(); var values = line.Split(',');//Values are comma separated Employee e = new Employee(); e.FirstName = values[0]; e.LastName = values[1]; e.Salary = int.Parse(values[2]); employees.Add(e); } return employees; }
在 Upload 中附上 AdminFilter 是用於限制 Non-Admin 用戶訪問。
第七步:爲 BulkUpload 建立連接
在「Views/Employee」文件夾下打開 AddNewLink.cshtml 文件,爲 BulkUpload 附上連接。
<a href="/Employee/AddNew">Add New</a> <a href="/BulkUpload/Index">BulkUpload</a>
第八步:執行並測試
爲測試建立一個簡單的文件
建立一個簡單的文件以下,而後將其保存在電腦中。
執行並測試
按下 F5,而後執行應用。完成登陸操做,而後經過點擊連接導航到 BulkUpload 選項。
選擇一個文件,而後點擊上傳。
注意:在上述的例子中,咱們沒有在視圖中用到任何客戶端或者服務器端的認證。它也許會致使以下的錯誤。
「Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.」
爲了發現這個錯誤的確切緣由,只須要在異常發生的時候添加以下的表達式。
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors。
表達式「$exception」呈現了任何從當前上下文中拋出的錯誤,即便它沒有被捕獲或者支配到一個變量中。
爲何咱們沒有在這裏用到認證?
爲選項增長客戶端和服務器端的認證將會留給讀者完成,我在這裏給出一些暗示。
運用 Data Annotations 來進行服務器端的認證。
你能夠運用 Data Annotations 或者實現 JQuery Unobtrusive Validation 來實現客戶端認證。明顯的是,這一次你須要手動設置自定義數據屬性,由於咱們沒有爲文件輸入建立 HtmlHelper 方法。
對於客戶端的認證,你能夠寫一些自定義的 JavaScript,而後經過點擊安全觸發它。這並非很難,由於文件輸入是一個輸入控件,值能夠經過在 JavaScript 中獲取並認證。
什麼是HttpPostedFileBase?
HttpPostedFileBase 能夠經過客戶端提供文件上傳的訪問接口。Model Binder 將會在發送 Post 請求時更新全部 FileUploadViewModel 類的屬性值。如今 FileUploadViewModel 裏只有一個屬性值,Model Binder 將會經過客戶端來設置這個屬性值,實現文件上傳。
提供多個文件輸入控件是否可行?
答案是確定的。咱們能夠經過兩種方式實現它。
建立多個文件輸入控件。每個控件都須要有惟一的名字。在 FileUploadViewModel 類中爲每一個控件建立一個 HttpPostedFileBase 的類型屬性。每個屬性的名稱應該與控件的名稱相匹配。剩下的工做會由 ModelBinder 來處理。
建立多個文件輸入控件。每個控件都須要有惟一的名字。此次不是建立多個 HttpPostedFileBase 的屬性,而是建立一個類型 List。 注意:上述的情形對於全部控件均可行。當你擁有多個相同名稱的控件時,若是要更新的屬性值是一個簡單參數,Model Binder 將會更新第一個控件的屬性值。若是更新的屬性值是一個 List,Model Binder 會將每個屬性值設置到控件中。
enctype="multipart/form-data"是用於作什麼的?
這個對知道與否並不重要,可是知道確實會好一點。
這個屬性指定了編碼類型,在傳輸數據時使用。屬性的默認值是「application/x-www-form-urlencoded」。
例如,咱們的登陸表單將會隨着 Post 請求向服務器發送以下數據。
POST /Authentication/DoLogin HTTP/1.1 Host: localhost:8870 Connection: keep-alive Content-Length: 44 Content-Type: application/x-www-form-urlencoded ... ... UserName=Admin&Passsword=Admin&BtnSubmi=Login
當 enctype="multipart/form-data"屬性被添加到表單標籤時,隨着 Post 請求會發送到服務器上。
POST /Authentication/DoLogin HTTP/1.1 Host: localhost:8870 Connection: keep-alive Content-Length: 452 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywHxplIF8cR8KNjeJ ... ... ------WebKitFormBoundary7hciuLuSNglCR8WC Content-Disposition: form-data; name="UserName" Admin ------WebKitFormBoundary7hciuLuSNglCR8WC Content-Disposition: form-data; name="Password" Admin ------WebKitFormBoundary7hciuLuSNglCR8WC Content-Disposition: form-data; name="BtnSubmi" Login ------WebKitFormBoundary7hciuLuSNglCR8WC—
正如你所看見的,表單以多個部分被髮送。每個部分都經過 Content-Type 被一條邊界線所分隔,而且每個部分都包含一個值。
若是表單標籤中包含文件輸入控件時,編碼類型須要設定爲「multipart/form-data」。
注意:每一次請求發生時,邊界線會隨機生成。你可能會看到不一樣的邊界線。
爲何咱們不老是將 EncTyp 設置爲「multipart/form-data」?
當 EncTyp 被設置爲「multipart/form-data」,它將會作兩件事,Post 數據以及上傳文件。這就是爲何咱們不老是將其設置爲「multipart/form-data」。
答案就是,這樣會增長請求的整體大小。請求的大小越大,意味着性能越差。由於最佳實踐應該是將其設置爲默認的值,即「application/x-www-form-urlencoded」。
爲何咱們須要建立 ViewModel?
在咱們的視圖中有一個控件。咱們能夠經過直接向 HttpPostedFileBase 類型增長一個參數來實現一樣的結果,這裏咱們須要在上傳方法中命名爲 「fileUpload」,而不是建立一個單獨的 ViewModel。代碼以下所示。
public ActionResult Upload(HttpPostedFileBase fileUpload) { }
建立 ViewModel 是最佳實踐。Controller 應該老是向視圖發送以 ViewModel 爲格式的數據,而且來自視圖的數據應該以 ViewModel 發送給 Controller。
你是否想知道,當你發送一個請求時,如何得到響應的?
如今不要去說,是經過行爲方法接到請求而後怎樣怎樣的。儘管這是正確的答案,我仍然指望一些不一樣的答案。個人問題是在最開始的時候發生了什麼。
一個簡單的編程規則,程序中全部都經過線程執行,儘管是請求。
在 Web 服務器上的 ASP.NET,.NET Framework 維護着線程池。每一次請求發送到 Web 服務器上時,就會把一個線程池中一個空閒的線程分配給服務器,用於處理請求。這個線程被稱爲 Worker 線程。
Worker 線程在請求正常處理的過程當中處於阻塞狀態,而且不能處理其它請求。
如今來假設一種場景,一個應用接收到了不少請求,而且每一個請求都會花費許多時間來處理進程。在這種情形下,沒有 Worker 線程可用於服務器請求,因此當新的請求想要獲取該線程進行處理狀態時,咱們可能須要在這時候終止它。這個咱們稱之爲 Thread Starvation(線程飢餓)。
在咱們的例子樣本文件中,只存在了兩個僱員記錄,而在真實場景中,可能存在成千上萬的記錄,這意味着請求也許會花費大量時間來完成進程。這樣會致使線程飢餓。
迄今爲止咱們所討論的請求都是同步請求類型。
若是客戶端發出的是異步請求,而不是同步請求,那麼線程飢餓的問題就解決了。
在異步請求的情形下,請求將會從線程池分配中得到一般的 Worker 線程,用於服務請求。
Worker 線程將會初始化異步操做,而後返回線程池來服務其它請求。異步操做將會繼續被 CLR 線程處理。
如今的問題是,CLR 線程不能返回響應,因此一旦當完成異步操做後,它就會通知 ASP.NET。
Web 服務器將會再一次從線程池中獲得 Worker 線程,用於處理剩餘的請求和響應。
在上述的完整的場景中,兩個 Worker 線程從線程池中獲取。這兩個 Worker 線程也許是同一個,也許不是。
在咱們的例子中,文件讀取是經過 I/O 操做的,這個操做不須要 Worker 線程來處理。因此最好是將同步請求轉換爲異步請求。
異步請求會提高響應時間嗎?
答案是否認的。響應時間是相同的。這裏線程將會被釋放,用於服務其它請求。
在 ASP.NET MVC 中,咱們能夠經過轉換同步行爲方法到異步行爲方法,來將同步請求轉換爲異步請求。
第一步:建立異步控制器
將 UploadController 的基類改成AsynController。
public class BulkUploadController : AsyncController {
第二步:轉換同步行爲方法到異步行爲方法
經過關鍵字,「async」和「await」,能夠很容易作這件事。
[AdminFilter] public async Task<ActionResult> Upload(FileUploadViewModel model) { int t1 = Thread.CurrentThread.ManagedThreadId; List<Employee> employees = await Task.Factory.StartNew<List<Employee>> (() => GetEmployees(model)); int t2 = Thread.CurrentThread.ManagedThreadId; EmployeeBusinessLayer bal = new EmployeeBusinessLayer(); bal.UploadEmployees(employees); return RedirectToAction("Index", "Employee"); }
正如你所看見的,咱們在行爲方法的開始和結束的地方將線程 ID 存儲在變量中。
如今讓我理解下代碼。
當客戶端點擊上傳按鈕時,一個新的請求將被髮送到服務器。
Webserver 從線程池中獲取一個 Worker 線程,而後將其分配給請求用於服務。
Worker 線程使得行爲方法用於執行。
Worker 方法經過 Task.Factory.StartNew 方法執行異步操做。
正如你所看見的,行爲方法經過關鍵字 Async被標記爲異步的,這將會確保一旦異步方法操做開始執行,Worker 線程就會獲得釋放。這個時候邏輯的異步操做將會經過獨立的 CLR 線程繼續在後臺執行。
如今異步操做調用將被標記爲 Await 關鍵字。這將會確保接下來的代碼行不會被執行,除非異步操做完成。
一旦異步操做完成了,接下來的行爲方法中的代碼就須要被執行。所以又要須要一個 Worker 線程。所以 Webserver 將會從線程池中取出一個空閒線程,而後將其分配給剩餘的請求用於服務,並返回響應。
第三步:執行並測試
執行應用。導航到 BulkUpload 選項。
在你作任何操做以前,先導航到代碼,而後在最後一行代碼中打個斷點。
如今選擇一個簡單的文件,而後點擊 Upload。
正如你所看見的,在方法的開始和結束時,線程 ID 是不一樣的。輸出的結果和以前的實驗結果同樣。
若是一個項目沒有正確的異常處理,就不能算是一個完整的項目。
迄今爲止,咱們討論過 ASP.NET MVC 中的兩個過濾器,即 Action 過濾器和 Authentication 過濾器。如今是時候討論第三個過濾器了,即 Exception 過濾器。
什麼是 Exception 過濾器?
Exception 過濾器的使用方式同其它過濾器同樣。咱們將以屬性的方式運用。
運用 Exception 過濾器的步驟。
使它們可用
將它們做爲行爲方法或者控制器的屬性。咱們也能夠將它們應用到 Global 級別。
它們是用來作什麼的?
一旦在行爲方法內部發生異常時,Exception 過濾器就將會控制執行並開始自動執行其內部的代碼。
是否存在自動的 Exception 過濾器?
ASP.NET MVC 提供給咱們一個已經編寫好的 Exception 過濾器,稱做 HandleError。
正如咱們以前所說的,當行爲方法中,一旦異常發生,過濾器就將被執行。這個過濾器將會在「~/Views/[current controller]」或者「~/Views/Shared」文件夾內發現一個名稱爲「Error」的視圖,爲這個視圖建立一個 ViewResult,而後返回響應。
讓咱們看一個 Demo,用於更好地理解。在項目的實驗最後,咱們將會實現 BulkUpload 選項。如今存在着較高的輸入文件的錯誤可能性。
第一步:建立一個簡單的帶有錯誤的 Upload 文件
建立一個簡單的上傳文件,就像以前同樣。可是此次,文件中包含一些非法值。
正如你所看見的,Salary 是非法的。
第二步:執行並測試應用
按下 F5,執行應用。導航到 Bulk Upload 選項,選擇上述的文件,而後點擊 Upload。
第三步:使異常過濾器可用
自定義異常開啓後,異常過濾器也被開啓。爲了開啓自定義異常,打開 Web.config 文件,而後導航到 System.Web 區域,在該區域下增長自定義錯誤,以下所示。
<system.web> <customErrors mode="On"></customErrors>
第四步:建立錯誤視圖
在「~Views/Shared」文件夾下,能夠看到一個文件,即「Error.cshtml」。這個文件做爲 MVC 樣本文件的一部分在開始的時候被建立。若是沒有被建立,就手動建立。
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Error</title> </head> <body> <hgroup> <h1>Error.</h1> <h2>An error occurred while processing your request.</h2> </hgroup> </body> </html>
第五步:附上 Exception 過濾器
正如咱們以前所討論的,一旦咱們使異常過濾器可用,咱們將會把它綁定到一個行爲方法或者控制器中。
好的消息是咱們無需手動附上過濾器。
在 App_Start 文件夾下打開 FilterConfig.cs 文件。在 RegisterGlobalFilter 方法下,你能夠看到 HandleError 過濾器已經被附上 Global 級別。
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute());//ExceptionFilter filters.Add(new AuthorizeAttribute()); }
若是須要移除 Global 過濾器,將會被附上方法或者控制器級別。
[AdminFilter] [HandleError] public async Task<ActionResult> Upload(FileUploadViewModel model) {
可是不建議這麼作,最好仍是應用 Global 級別。
第六步:執行並測試
像以前的方式同樣,讓咱們來看一下應用的測試結果。
第七步:在視圖中展現錯誤信息
爲了達到這個目的,咱們須要將錯誤視圖轉換爲 HandleErrorInfo 類的強類型視圖,而後在視圖中展現錯誤信息。
@model HandleErrorInfo @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Error</title> </head> <body> <hgroup> <h1>Error.</h1> <h2>An error occurred while processing your request.</h2> </hgroup> Error Message :@Model.Exception.Message<br /> Controller: @Model.ControllerName<br /> Action: @Model.ActionName </body> </html>
第八步:執行並測試
此次測試結果,咱們將會獲得以下的錯誤視圖。
咱們是否錯失了什麼?
Handle Error 屬性確保了不管什麼時候行爲方法發生異常時,自定義視圖都會被呈現。可是僅限於控制器和行爲方法。它不會處理「Resource not found」錯誤。
執行應用,輸入一些古怪的 URL。
第九步:建立 ErrorController
在 Controller 文件夾下建立一個名爲 ErrorController 的控制器,而後建立一個行爲方法,命名爲 Index。
public class ErrorController : Controller { // GET: Error public ActionResult Index() { Exception e=new Exception("Invalid Controller or/and Action Name"); HandleErrorInfo eInfo = new HandleErrorInfo(e, "Unknown", "Unknown"); return View("Error", eInfo); } }
HandleErrorInfo 控制器擁有三個參數,即異常對象,控制器名稱和行爲方法名稱。
第十步:在非法的 URL 中呈現自定義錯誤視圖
在 Web.config 中設定「Resource not found error」定義。
<system.web> <customErrors mode="On"> <error statusCode="404" redirect="~/Error/Index"/> </customErrors>
第十一步:使全部人可訪問 ErrorController
在 ErrorController 中應用 AllowAnonymous 屬性,Index 方法不該該被綁定到一個有權限的用戶。由於用戶可能在登陸前就輸入了非法的 URL。
[AllowAnonymous] public class ErrorController : Controller {
第十二步:執行並測試
執行應用程序,而後在瀏覽器地址欄輸入一些非法的 URL。
能夠改變視圖的名稱嗎?
答案是確定的,保持視圖名稱爲「Error」不是老是必須的。
在這種情形下,當附上 HandleError 過濾器時,咱們須要指定視圖的名稱。
[HandleError(View="MyError")]
或者是
filters.Add(new HandleErrorAttribute() { View="MyError" });
對於不一樣的異常,獲取不一樣的錯誤視圖,是否可行?
答案是確定的,這是可行的。在這種情形下,咱們須要應用 Handle Error 過濾器屢次。
[HandleError(View="DivideError",ExceptionType=typeof(DivideByZeroException))] [HandleError(View = "NotFiniteError", ExceptionType = typeof(NotFiniteNumberException))] [HandleError]
或者是
filters.Add(new HandleErrorAttribute() { ExceptionType = typeof(DivideByZeroException), View = "DivideError" }); filters.Add(new HandleErrorAttribute() { ExceptionType = typeof(NotFiniteNumberException), View = "NotFiniteError" }); filters.Add(new HandleErrorAttribute());
在上述的例子中,咱們增長了三個 Handle Error 過濾器。前兩個爲指定的異常,然後一個更加通用一些,它將會爲全部其它異常展現錯誤視圖。
上述實驗存在惟一的侷限,即是咱們沒有將異常日誌輸出。
第一步:建立 Logger 類
在項目的根目錄下建立一個新的文件夾,稱爲 Logger。
在 Logger 文件夾下建立一個類,命名爲 FileLogger。
namespace WebApplication1.Logger { public class FileLogger { public void LogException(Exception e) { File.WriteAllLines("C://Error//" + DateTime.Now.ToString("dd-MM-yyyy mm hh ss")+".txt", new string[] { "Message:"+e.Message, "Stacktrace:"+e.StackTrace }); } } }
第二步:建立 EmployeeExceptionFilter 類
在 Filters 文件夾下建立一個新的類,命名爲 EmployeeExceptionFilter。
namespace WebApplication1.Filters { public class EmployeeExceptionFilter { } }
第三步:擴展 Handle Error 用於實現日誌記錄
讓 EmployeeExceptionFilter 類繼承 HandleErrorAttribute 類,而後重寫 OnException 方法。
public class EmployeeExceptionFilter:HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { base.OnException(filterContext); } }
注意:確保在 HandleErrorAttribute 類中的頂部引用了 System.Web.MVC。
第四步:定義 OnException 方法
在 OnException 方法中包含異常日誌記錄代碼,以下所示。
public override void OnException(ExceptionContext filterContext) { FileLogger logger = new FileLogger(); logger.LogException(filterContext.Exception); base.OnException(filterContext); }
第五步:改變默認的異常過濾器
打開 FilterConfig.cs 文件,移除 HandleErrorAttribute,而後附上咱們上一步驟中所建立的。
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { //filters.Add(new HandleErrorAttribute());//ExceptionFilter filters.Add(new EmployeeExceptionFilter()); filters.Add(new AuthorizeAttribute()); }
第六步:執行並測試
首先在 C 盤下建立一個文件夾,命名爲「Error」。這個文件夾會存放錯誤的日誌文件。
注意:能夠更改路徑爲你所指望的路徑。
按下 F5,而後執行應用。導航到 Bulk Upload 選項。選擇文件,而後點擊 Upload。
此次的輸出將會有所不一樣,咱們將會獲得一些錯誤視圖,就像以前同樣。惟一的不一樣即是咱們會在「C:\Errors」文件夾發現一些錯誤日誌文件。
異常發生時,錯誤視圖是如何做爲響應返回的?
在上述實驗中,咱們重寫了 OnException 方法,而後實現了異常日誌的功能。如今的問題是,默認的錯誤處理過濾器是如何繼續工做的?答案是簡單地,查看 OnException 方法的最後一行代碼。
base.OnException(filterContext);
這意味着,基類 OnException 將會作剩餘的工做,基類 OnException 將會返回錯誤視圖的 ViewResult。
在 OnException 中,咱們能夠返回其它結果嗎?
答案是確定的,查看以下代碼。
public override void OnException(ExceptionContext filterContext) { FileLogger logger = new FileLogger(); logger.LogException(filterContext.Exception); //base.OnException(filterContext); filterContext.ExceptionHandled = true; filterContext.Result = new ContentResult() { Content="Sorry for the Error" }; }
當咱們想要返回自定義響應時,首先要作的事即是,通知 MVC 引擎,告知其咱們已經手動處理異常了,因此不須要作默認的行爲,即不須要呈現默認的錯誤屏幕。這一切能夠經過以下代碼來實現。
filterContext.ExceptionHandled = true
迄今爲止咱們討論過許多概念,咱們也回答了許多有關 MVC 的問題,可是除了一個基本和重要的概念。
「當用戶發出請求時,確切發生了什麼」?
一個很好的答案即是「行爲方法的執行」。可是確切的答案是控制器和犯法是如何被一個特定的 URL 請求識別的?
當咱們開始「實現用戶友好的 URLs」的實驗時,咱們首先須要回答上述的問題。你也許會奇怪爲何這個主題會放置到最後。我故意將其放置到最後,是由於我想讓更多的人在理解內部以前,先了解 MVC。
在 ASP.NET MVC 中,存在一個概念,稱做 RouteTable。這裏存儲了應用的 URL 路由。用簡單的話說,它承載了一個應用的 URL 模式的集合。
默認狀況下,一個路由將會做爲項目模板的一部分被添加。能夠經過 Global.asax 文件查看它。在 Application_Start 中,你將會發現以下的代碼。
RouteConfig.RegisterRoutes(RouteTable.Routes);
你將會在 App_Start 文件夾下發現 RouteConfig.cs 文件,它包含了以下代碼。
namespace WebApplication1 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
正如你所看見的,RegisterRoutes 方法已經經過 Route.MapRoutes 方法定義了一個默認的路由。
在 RegisterRoutes 方法中定義的路由將會在 ASP.NET MVC 請求週期中被用到,用於決定執行確切的控制器和方法。
若是須要,咱們能夠經過使用 Route.MapRoutes 函數,建立多個路由。內部定義路由意味着建立 Route 對象。
MapRoute 函數也能夠把路由對象附上 RouteHandler,這樣將會是 MVCRouteHandler。
在咱們開始以前,你須要清楚,咱們將要 100% 地解釋請求週期。咱們將要接觸到以前未講到的重要概念。
第一步:UrlRoutingModule
當終端用戶發出請求後,首先會經過 UrlRoutingModule 對象。UrlRoutingModule 是一個 HTTP 模塊。
第二步:路由
UrlRoutingModule 首先會從路由集合中匹配 Route 對象。對於匹配,請求的 URL 將會與路由中定義的 URL 模式相對比。
下述的規則將會在匹配中被考慮到。
第三步:建立 MVC Route Handler
一旦路由對象被選中,UrlRoutingModule 將會從路由對象中得到 MvcRouteHandler。
第四步:建立 RouteData 和 RequestContext
UrlRoutingModule 對象將會經過 Route 對象建立 RouteData,它將會用於建立 RequestContext。
RouteData 封裝了關於路由的信息,如控制器的名稱,行爲方法的名稱,路由參數的值。
Controller 名稱
爲了從請求 URL 中得到控制器的名稱,須要遵循以下的簡單規則。即「在 URL 模式中{Controller} 是識別控制器名稱的關鍵詞」。
例如:
當URL 模式是 {Controller}/{Action}/{Id},而請求 URL 是「http://localhost:8870/BulkUpload/Upload/5」時,BulkUpload 是控制器的名稱。
當 URL 模式是 {Action}/{Controller}/{Id},而請求 URL 是 「http://localhost:8870/BulkUpload/Upload/5」時,Upload 是控制器的名稱。
行爲方法名稱
爲了得到請求 URL 中的行爲方法,須要遵循以下的簡單規則。即「在 URL 模式中 {Action} 是行爲方法名稱的關鍵詞」。
例如:
當URL 模式是 {Controller}/{Action}/{Id},而請求 URL 是「http://localhost:8870/BulkUpload/Upload/5」時,Upload 是行爲方法的名稱。
當 URL 模式是 {Action}/{Controller}/{Id},而請求 URL 是 「http://localhost:8870/BulkUpload/Upload/5」時,BulkUpload 是行爲方法的名稱。
路由參數
一個基本的 URL 模式包含以下四個要素。
{Controller},用於識別控制器名稱。
{Action},識別行爲方法名稱。
一些字符串,例如「MyCompany/{Controller}/{Action}」,在這個模式中,「MyCompany」是一個必須的字符串。
{Something},例如「{Controller}/{Action}/{Id}」,在這個模式中「Id」是路由參數。在請求的 URL 中,路由參數能夠被用於獲取 URL 的值。
咱們來看一下以下示例。
路由模式是 {Controller}/{Action}/{Id}。
請求 URL 是「http://localhost:8870/BulkUpload/Upload/5」。
測試一:
public class BulkUploadController : Controller { public ActionResult Upload (string id) { //value of id will be 5 -> string 5 ... } }
測試二:
public class BulkUploadController : Controller { public ActionResult Upload (int id) { //value of id will be 5 -> int 5 ... } }
測試三:
public class BulkUploadController : Controller { public ActionResult Upload (string MyId) { //value of MyId will be null ... } }
第五步:建立 MVCHandler
MvcRouteHandler 將會建立 MVCHandler 的實例,傳輸 RequestContext 對象。
第六步:建立控制器實例
MVCHandler 將會經過 ControllerFactory(默認的是 DefaultControllerFactory) 建立控制器實例。
第七步:執行方法
MVCHandler 將會觸發控制器的執行方法。執行方法在控制器基類中被定義。
第八步:觸發行爲方法
每個控制器都與一個 ControllerActionInvoker 對象相關聯。在執行方法中,ControllerActionInvoker 觸發正確的行爲方法。
第九步:執行結果
行爲方法接收到用戶的輸入,而後準備合適的響應數據,並經過返回一個類型來執行結果。如今返回的結果多是 ViewResult,多是 RedirectToRoute 結果或者多是其它。
如今,我相信你已經對路由的概念有了很好的理解,因此讓咱們經過路由來使得項目的 URLs 更友好吧。
第一步:從新定義 RegisterRoutes 方法
在 RegisterRoutes 方法中包含額外的路由。
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Upload", url: "Employee/BulkUpload", defaults: new { controller = "BulkUpload", action = "Index" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
正如你所看見的,咱們如今已經不止定義一個路由了。
第二步:更改 URL 引用
從「~/Views/Employee」文件夾下打開 AddNewLink.cshtml 文件,而後更改 BulkUpload 連接以下。
<a href="/Employee/BulkUpload">BulkUpload</a>
第三步:執行並測試
執行應用,將會看到神奇的地方。
正如你所看見的,URL 再也不是「Controller/Action」的形式。它看起來更加用戶友好,可是輸出是同樣的。
我建議你定義更多的路由,嘗試更多的 URLs。
以前的 URL 仍是否起做用?
答案是確定的,以前的 URL 也會起做用。
如今 BulkUploadController 中的 Index 方法能夠經過兩個 URLs 訪問。
默認路由中的「Id」是什麼?
咱們以前提到過它。它被稱做路由參數。它能夠經過 URL 來用於獲取值。它是一個可被替換的查詢字符串。
路由參數和查詢字符串的區別是什麼?
查詢字符串有大小限制,然而咱們能夠定義路由參數的任意數字。
咱們不能向查詢字符串值添加限制,可是咱們能夠向路由參數添加限制。
能夠設定路由參數的默認值,然而查詢字符串的默認值不可設定。
查詢字符串使得 URL 凌亂,可是路由參數保持 URL 整潔。
如何向路由參數應用限制?
能夠經過正則表達式來完成這件事。例如,查看以下路由。
routes.MapRoute( "MyRoute", "Employee/{EmpId}", new {controller=" Employee ", action="GetEmployeeById"}, new { EmpId = @"\d+" } );
行爲方法將以下所示。
public ActionResult GetEmployeeById(int EmpId) { ... }
如今若是用戶經過 URL「http://..../Employee/1」 或者 「http://..../Employee/111」來發出請求,行爲方法將會獲得執行,可是若是用戶經過 URL「http://..../Employee/Sukesh」 ,他將會獲得「Resource Not Found」的錯誤。
行爲方法中的參數名稱和路由參數名稱須要保持一致嗎?
從根本上說,路由模式也許包含多個 RouteParameters。爲了單獨地識別每個路由參數,須要保持行爲方法中的參數名稱和路由參數名稱一致。
定義自定義路由的次序重要嗎?
答案是確定的,次序是重要的。UrlRoutingModule 將會匹配第一個路由對象。
在上述的實驗中,咱們已經定義了兩個路由。一個是自定義路由,一個是默認路由。如今咱們來討論一種狀況,默認路由被首先定義,自定義路由被第二個定義。
在這種狀況下,終端用戶發起一個請求 URL,即「http://…/Employee/BulkUpload」。在匹配階段,UrlRoutingModules 將會發現請求的 URL 與默認的路由模式匹配,它將會認爲「Employee」是控制器的名稱,「BulkUpload」是行爲方法的名稱。
所以次序在定義路由時是很是重要的。大多數通用的路由應該被放置到最後。
是否存在更簡單的方式來定義行爲方法的 URL 模式?
咱們能夠運用基於路由的屬性來解決這個問題。讓咱們來試一下。
第一步:使基於路由的屬性可用
在 RegisterRoutes 方法中的 IgnoreRoute 語句後添加以下代碼。
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); routes.MapRoute( ...
第二步:爲行爲方法定義路由模式
在 EmployeeController 中的 Index 行爲方法中附上 Route 屬性。
[Route("Employee/List")] public ActionResult Index() {
第三步:執行並測試
執行應用程序,而後完成登陸操做。
正如你所看見的,咱們擁有相同的輸出結果,可是不一樣的是擁有了更加用戶友好性的 URL。
咱們能夠經過基於路由的屬性來定義路由參數嗎?
答案是確定的,能夠查看以下語法。
[Route("Employee/List/{id}")] publicActionResult Index (string id) { ... }
在這種狀況下的限制呢?
這將會變得更加容易。
[Route("Employee/List/{id:int}")]
咱們能夠擁有以下限制。
{x:alpha} – 字符串認證
{x:bool} – 布爾認證
{x:datetime} – Date Time 認證
{x:decimal} – Decimal 認證
{x:double} – 64 位 Float 認證
{x:float} – 32 位 Float 認證
{x:guid} – GUID 認證
{x:length(6)} – 長度認證
{x:length(1,20)} – 最小和最大長度認證
{x:long} – 64 位 Int 認證
{x:max(10)} – 最大 Integer 長度認證
{x:maxlength(10)} – 最大長度認證
{x:min(10)} – 最小 Integer 長度認證
{x:minlength(10)} – 最小長度認證
{x:range(10,50)} – 整型 Range 認證
{x:regex(SomeRegularExpression)} – 正則表達式認證
在 RegisterRoutes 方法中 IgnoreRoutes 是用於作什麼的?
當咱們不想運用路由作指定擴展時,咱們能夠運用 IgnoreRoutes。做爲 MVC 模板的一部分,以下的代碼已經寫入 RegisterRoutes 方法中。
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
這意味着,當終端用戶發出一個帶有「.axd」擴展的請求時,將不會執行任何路由操做。請求將會直接定位到物理資源。咱們也能夠定義本身的 IgnoreRoute 語句。
在第 6 天的學習中,咱們完成了簡單的 MVC 項目。但願你可以享受完成系列學習的樂趣。
稍等一下!第 7 天的學習呢?
在第 7 天中,咱們將會運用 MVC, JQuery 和 Ajax 來建立一個 Single Page 應用。這將會更加有趣,並富有挑戰。
保持學習的熱情吧!
原文地址:Learn MVC Project in 7 days
OneAPM for .NET 可以深刻到全部 .NET 應用內部完成應用性能管理和監控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實用戶體驗監控、服務器監控和端到端的應用性能管理。想閱讀更多技術文章,請訪問 OneAPM 官方博客。