上一篇中老周給伙伴們介紹了自定義視圖搜索路徑的方法,本篇咱們扯一下有關 URL 路徑規則的名稱問題。在扯今天的話題之前,先補充點東東。在上一篇中設置視圖搜索路徑時用到三個有序參數:{2}{1}{0},分別是 Area、Controller、Action。其中說到幾個特殊的視圖,如_Layout.cshtml、_ViewStart.cshtml等。_Layout.cshtml 頁默認放在 /Views/Shared 目錄下,但,_ViewStart.cshtml 和 _ViewImports.cshtml 這兩個不應該放在 Shared 目錄下,一般應放到 /Views 下,這樣它們可以作用於所有的視圖。如果放到了 Shared 目錄下,它們只對 Shared 目錄中的視圖起作用,而對於 Views 下的其他視圖不起作用。
比如,放到 /Views 下。
Views(目錄)
│ _ViewImports.cshtml
│ _ViewStart.cshtml
│
└─Home(目錄,Controller的名字)
Index.cshtml(視圖,Action)
其中,Home 是子目錄,對應着控制器 Home,Home 中的 Index.cshtml 視圖對應着 Action 名 Index。此時,_ViewStart 和 _ViewImports 中的內容會應用到 /Views 下的所有視圖中(如 Index.cshtml)。
要是改為這樣。
Views
├─Home
│ About.cshtml
│ Index.cshtml
│ _ViewImports.cshtml
│ _ViewStart.cshtml
│
└─Users
AddNew.cshtml
此時,Views 有兩個子目錄,Home 是一個控制器,Users 是另一個控制器,這時候,_ViewStart 和 _ViewImports 只對 Home 下面的視圖起作用,對 Users 目錄下的視圖是不起作用的。
_ViewStart 主要用途是在所有視圖文件執行之前執行,一般我們用它來設置 Layout 屬性,以指定使用的布局頁(相當於頁面母板),這樣一來,我們不需要在每個視圖上都加 Layout = "xxxx" 了。_ViewImports 主要是用來引入要用到的命名空間(就是 C# 中的 using),這樣你不需要在每個視圖中寫一堆 @using Razor 標記了。
這兩個文件都是約定式的,所以你不應該隨便改它的名字,_ViewImports 可以通過 RazorTemplateEngineOptions 類的 ImportsFileName 屬性來修改,不過,_ViewStart 好像不能改,老周看到 asp.net core 源碼中是寫死了的,估計是不能改文件名的。
其實,這兩個文件不應該改名,而且你改了名字也沒啥用,反正功能是不變的,還是遵守約定好一些,這樣人家看你的項目時也看得懂。_Layout.cshtml 文件如非必要,也不應該改名字,如果你的應用要用多個布局視圖,可能建個子目錄,然后每個子目錄下放_Layout,這樣結構清晰一些,畢竟,看到 _Layout.cshtml 就明白它是母板頁了。
規則模板
我們都知道,在 Startup.Configure 方法中,會以此方式來指定 URL 路徑規則。
app.UseMvc(route => { route.MapRoute("main", "{controller=Students}/{action=List}/{sid?}"); route.MapRoute("edit_post", "{controller}-{action}"); });
你可以添加 K 條規則,比如上面的例子,我添加了兩條規則。
{controller} 和 {action} 是約定的名稱,用來識別 Controller 和 Action ,所以你不要自作聰明亂來,必要有些寫死了的參數才能進行 URL 分析,不然,你給個 URL http://dog.org/shopping/pay/500,那應用程序根本不知道哪一段是表示 Controller,哪一段是表示 action。
如果確定了 controller 和 action 這兩個值,那么其他的參數就好分析了。
其他參數如果是可選的,可以在后面加個問號,比如 {controller}/{action}/{id?},這表示 id 的值是可選的。
上面老周添加的兩個規則中,edit_post 那個其實不太規范,URL 中各段最好用 “/” 來分隔,因為 “-” 有時候是不允許用的,比如,id 參數前面就不能用,你不能寫成 {controller}-{action}-{id?},要是 id 中包含了字符“-”,咋辦呢?而“/”則不同,URL Encode 后不會冒出這個字符來。
所以用 / 最好,這里用 - 只是老周故意用來演示而已,URL 嘛,沒必要玩花樣,沒意義。
基於 Attribute 指定的 URL 路由
在 Startup.Configure 方法中指定的 URL 路由是作用於整個應用程序的,如果想為個別控制器或個別 Action 指定路由規則,那么可以考慮使用 Attribute 的形式。
attribute 形式的路由規則和應用程序級別的規則相似,只是,在應用級別時,用大括號來包裹參數名(如 {controller}),而在 Attribute 方案中,是用中括號的,它只能用兩個值:[controller]、[action]。其他參數也是用大括號。比如,[controlloer]/[action]/[id?] 會報錯,你得改為 [controller]/[action]/{id?}。
RouteAttribute 既可以用於 Controller 類型,也可能用於單個 Action 方法上。我舉個例子,像這樣。
[Route("hello/[controller]/[action]")] public class SomethingController : Controller { [Route("{name?}")] public IActionResult SayHi(string name) { …… } }
在類上應用用的 Attribute 中,可以使用這樣的 URL :http://localhost:999/hello/something/sayhi 。而在 SayHi 方法上,又用了 Route Attribute,指定了一個附加參數 name,並且是可選的。於是它可以與類上的 Route attribute 合並,變成:http://localhost:999/hello/something/sayhi/Peter。這時,字符串 Peter 會傳給 SayHi 方法的 name 參數,因為,參數的名字與 Route 中的參數名是相同的,都叫 name。如果 SayHi 中的參數名不叫 name,那你得運用一下 FromRouteAttribute 了。就像這樣。
[Route("{name?}")] public IActionResult SayHi([FromRoute(Name = "name")]string who) { …… }
如果你希望 URL 中給 name 傳入 int 類型的值,你還可以限制它。
[Route("{name:int}")]
其實這些約束條件對應的是 Microsoft.AspNetCore.Routing.Constraints 命名空間下面的類型。
Route Data
Route data 其實就是一個字典,存放的就是 URL 路徑規則中參數與值的 key-value 對。這個很簡單,我舉個例子,你就明白了。
咱們就直接用上面那個例子吧。
[Route("hello/[controller]/[action]")] public class SomethingController : Controller { [Route("{name?}")] public IActionResult SayHi([FromRoute(Name = "name")]string who) { return Json(RouteData.Values); } }
在 SayHi 方法中,咱們把 route data 返回。
運行應用后,輸入地址:http://localhost/hello/something/sayhi/Tom,得到的輸出如下:
不用我解釋了吧。
給路由命名
上面的都是 F 話,本小節才是本文的主題。我們回頭看看上面老周舉過例的那個 route。
app.UseMvc(route => { route.MapRoute("main", "{controller=Students}/{action=List}/{sid?}"); route.MapRoute("edit_post", "{controller}-{action}"); });
每條路由規則都會有自己的 name,為啥要命名?最直接的理由是為了唯一標識每條規則。除了此因素外,我們可以在開發過程中選擇使用哪條規則,有了 name,想找出某條規則就好辦了,就好比你上學的時候,老師點名,要么點姓名,要么點學號。
基於 Attribute 的路由規則也可以命名的,例如。
[Route("hello/[controller]/[action]", Name = "prv")]
這樣就把它命名為 prv 了,你還可以這樣寫。
[Route("hello/[controller]/[action]", Name = "[controller]_[action]")]
這樣也可以用 Controller 和 Action 的名字生成一個唯一的名字,比如 Something_SayHi。但是這種方法太動態了,好像不那么好操控,還是用一個固定的名字好一點。
要在開發的時候選擇使用指定的 URL 路由,需要在 Razor 頁中添加 Tag Helper,標記幫助類可以擴展 HTML 標記的某些功能。在需要使用 tag helper 的頁面,或者統一在 _ViewImports.cshtml 頁中加入這些指令。
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
格式是這樣的:
<類型全路徑>, <程序集>
類型寫在前面(包括 namespace 名),程序集名寫在后面,用逗號分隔。這里用星號(*)是最爽的,它是通配符,表示引入所有 tag helper 類型。這樣快捷,一行代碼了事。
然后在 HTML 中你這樣寫。
<form method="post" asp-route="edit_post"> <div class="form-group"> <label asp-for="Name"></label> <input asp-for="Name" class="form-control"/> <span asp-validation-for="Name" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Age"></label> <input asp-for="Age" class="form-control"/> <span asp-validation-for="Age" class="text-danger"></span> </div> <input asp-for="ID"/> <button type="submit" class="btn btn-dark">提交</button> </form>
其他代碼你不用看了,只看這一句就夠了:
asp-route="edit_post"
它的意思就是使用我剛剛定義的那條規則。
route.MapRoute("edit_post", "{controller}-{action}");
所以,在運行后就會生成這樣的 HTML。
<form method="post" action="/Students-Editdata"> <div class="form-group"> 此處省略 1650 個字 </form>
因為我定義的規則是 {controller}-{action}的形式,所以,Controller 是 Students,Action 是 Editdata,連起來就是 Students-Editdata。
那么,這里它為什么能識別出 controller 和 action 的值呢,你看看我的代碼就知道了。
public class StudentsController : Controller { readonly StudentDBContext m_context; // 接收依賴注入 public StudentsController(StudentDBContext c) { m_context = c; } public IActionResult List() { var q = from s in m_context.Students orderby s.ID select s; return View(q.ToList()); } /***************************************************/ // 以下方法用於編輯頁 [HttpGet] public IActionResult Editdata([FromRoute(Name = "sid")] int id) { var q = from s in m_context.Students where id == s.ID select s; Student stu = q.FirstOrDefault(); if(stu == null) { return Content("在地球上找不到此學員。"); } return View(stu); } [HttpPost] public IActionResult Editdata(Student s) { if (ModelState.IsValid == false) { return View(s); } m_context.Students.Update(s); m_context.SaveChanges(); return RedirectToAction(nameof(List)); } }
我定義了 Editdata 方法的重載,一個用於 get 請求,一個用於 post 請求,form 是以 post 方式提交,因此它能自動識別出 controller 和 action 的名字。
那萬一,如果不是同名的呢,好辦。你用 asp-route-<value> 來指定各個參數的值。比如這樣
<form method="post" asp-route="edit_post" asp-route-controller="Demo" asp-route-action="Runwork" asp-route-sid="1">
在 asp-route- 后面直接跟上路由規則參數的名稱就可以了。
有一點要注意,asp-route 與 asp-controller、asp-action是會沖突的,如果你用了這兩個標記,就不能用 asp-route 標記了,當然 asp-route-xxx 是可以用的。
好了,今天的內容就扯到這兒了,順便把示例的代碼也傳上來,以供伙伴們娛樂。