日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由

發(fā)布時間:2025/4/5 asp.net 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文:Routing to Controller Actions
作者:Ryan Nowak、Rick Anderson
翻譯:婁宇(Lyrics)
校對:何鎮(zhèn)汐、姚阿勇(Dr.Yao)

ASP.NET Core MVC 使用路由 中間件 來匹配傳入請求的 URL 并映射到具體的操作。路由通過啟動代碼或者特性定義。路由描述 URL 路徑應(yīng)該如何匹配到操作。路由也同樣用于生成響應(yīng)中返回的 URL(用于鏈接)。

這篇文章將解釋 MVC 和路由之間的相互作用,以及典型的 MVC 應(yīng)用程序如何使用路由特性。查看 路由 獲取更多高級路由信息。

配置路由中間件

在你的 Configure 方法中也許能看到以下代碼:

app.UseMvc(routes => {routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); });

其中對 UseMvc,MapRoute 的調(diào)用用來創(chuàng)建單個路由,我們稱之為 default 路由。大部分 MVC 應(yīng)用程序使用的路由模板類似 default 路由。

路由模板 "{controller=Home}/{action=Index}/{id?}" 能夠匹配路由比如 /Products/Details/5 并會通過標(biāo)記路徑提取路由值 { controller = Products, action = Details, id = 5 }。MVC 將嘗試定位名為 ProductsController 的控制器并運行操作 Details:

public class ProductsController : Controller {public IActionResult Details(int id) { ... } }

注意這個例子,當(dāng)調(diào)用這個操作時,模型綁定會使用 id = 5 的值來將 id 參數(shù)設(shè)置為 5。查看 模型綁定 獲取更多信息。
使用 default 路由:

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

路由模板:

  • {controller=Home} 定義 Home 作為默認(rèn)的 controller
  • {action=Index} 定義 Index 作為默認(rèn)的 action
  • {id?} 定義 id 為可選項

默認(rèn)和可選路由參數(shù)不需要出現(xiàn)在 URL 路徑,查看 Routing 獲取路由模板語法的詳細(xì)描述。

"{controller=Home}/{action=Index}/{id?}" 可以匹配 URL 路徑 / 并產(chǎn)生路由值 { controller = Home, action = Index }。 controller 和 action 使用默認(rèn)值,因為在 URL 路徑中沒有響應(yīng)的片段,所以 id 不會產(chǎn)生值。MVC會使用這些路由值選擇 HomeController 和 Index 操作:

public class HomeController : Controller {public IActionResult Index() { ... } }

使用這個控制器和路由模板, HomeController.Index 操作會被以下任一 URL 路徑執(zhí)行:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

簡便的方法 UseMvcWithDefaultRoute:

app.UseMvcWithDefaultRoute();

可以被替換為:

app.UseMvc(routes => {routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); });

UseMvc 和 UseMvcWithDefaultRoute 添加一個 RouterMiddleware 的實例到中間件管道。MVC 不直接與中間件交互,使用路由來處理請求。MVC 通過 MvcRouteHandler 的實例連接到路由。UseMvc 中的代碼類似于下面:

var routes = new RouteBuilder(app);// 添加連接到 MVC,將通過調(diào)用 MapRoute 連接。 routes.DefaultHandler = new MvcRouteHandler(...);// 執(zhí)行回調(diào)來注冊路由。 // routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");// 創(chuàng)建路由集合并添加中間件。 app.UseRouter(routes.Build());

UseMvc 不會直接定義任何路由,它為 特性 路由在路由集合中添加了一個占位符。UseMvc(Action<IRouteBuilder>) 這個重載讓你添加自己的路由并且也支持特性路由。UseMvc 和它所有的重載都為特性路由添加占位符,不管你如何配置 UseMvc ,特性路由總是可用的。 UseMvcWithDefaultRoute 定義一個默認(rèn)路由并支持特性路由。
特性路由 章節(jié)包含了特性路由的信息。

常規(guī)路由

default 路由:

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

是一個 常規(guī)路由 的例子。我們將這種風(fēng)格稱為 常規(guī)路由 因為它為 URL 路徑建立了一個 約定 :

  • 第一個路徑片段映射控制器名。
  • 第二個片段映射操作名。
  • 第三個片段是一個可選的 id 用于映射到模型實體。

使用這個 default 路由,URL 路徑 /Products/List 映射到 ProductsController.List 操作,/Blog/Article/17 映射到 BlogController.Article。這個映射只基于控制器名和操作名,與命名空間、源文件位置或者方法參數(shù)無關(guān)。

小技巧
使用默認(rèn)路由的常規(guī)路由使你可以快速構(gòu)建應(yīng)用程序,而不必為你定義的每一個操作想新的 URL 模式。對于 CRUD 風(fēng)格操作的應(yīng)用程序,保持訪問控制器 URL 的一致性可以幫助簡化你的代碼并使你的 UI 更加可預(yù)測。

警告
id 在路由模板中定義為可選,意味著你可以執(zhí)行操作且不需要在 URL 中提供 ID。通常在 URL 中忽略 id 會通過模型綁定設(shè)置為 0,并且沒有實體會通過在數(shù)據(jù)庫中匹配 id == 0 被找到。特性路由可以提供細(xì)粒度控制使 ID 在某些操作中必傳以及其他操作中不必傳。按照慣例,當(dāng)可選參數(shù)可能出現(xiàn)在正確的用法時,文檔將包括它們,比如 id。

多路由

你可以在 UseMvc 中通過添加 MapRoute 調(diào)用來添加多個路由。這樣做讓你可以定義多個約定,或者添加專用于一個特定操作的常規(guī)路由,比如:

app.UseMvc(routes => {routes.MapRoute("blog", "blog/{*article}",defaults: new { controller = "Blog", action = "Article" });routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }

blog 路由在這里是一個 專用常規(guī)路由,意味著它使用常規(guī)路由系統(tǒng),但是專用于一個特殊的操作。由于 controller 和 action 不會作為參數(shù)出現(xiàn)在路由模板中,它們只能擁有默認(rèn)值,因此這個路由將總是映射到操作 BlogController.Article。

路由在路由集合中是有序的,并將按照它們添加的順序處理。所以在這個例子中,blog 路由會在 default 路由之前嘗試。

注解
專用常規(guī)路由 通常捕捉所有參數(shù),比如使用 {*article} 捕捉 URL 路徑的剩余部分。這樣使得路由 '太貪婪',這意味著它將匹配所有你打算與其他路由規(guī)則匹配的路由。把 'greedy' 路由在路由表中置后來解決這個問題。

回退

作為請求處理的一部分,MVC 將驗證路由值是否可以用來在你的應(yīng)用程序中找到控制器和操作。如果路由值不匹配任何操作,則不會認(rèn)為路由匹配成功,將會嘗試下一個路由。這叫做 回退,它的目的是簡化路由重疊的情況。

消除歧義操作

當(dāng)兩個操作通過路由匹配,MVC 必須消除歧義來選擇‘最好的’候選,或者拋出一個異常,比如:

public class ProductsController : Controller {public IActionResult Edit(int id) { ... }[HttpPost]public IActionResult Edit(int id, Product product) { ... } }

這個控制器定義兩個操作,它們都會匹配 URL 路徑 /Products/Edit/17 以及路由數(shù)據(jù)是 { controller = Products, action = Edit, id = 17 }。這是 MVC 控制器中一個典型模式,其中 Edit(int) 顯示編輯產(chǎn)品的表單,Edit(int, Product) 處理提交上來的表單。為了確保這樣可行,MVC 需要在請求是 HTTP POST 時選擇 Edit(int, Product),并在其他 HTTP 謂詞時選擇 Edit(int)。

HttpPostAttribute ( [HttpPost] ) 是 IActionConstraint 的一個實現(xiàn),它僅允許 HTTP 謂詞為 POST 的請求訪問操作。IActionConstraint 的存在使得 Edit(int, Product) 比 Edit(int) 更好匹配,所以會先首先嘗試 Edit(int, Product)。查看 理解 IActionConstraint 獲取更多信息。

你只會在專門的場景才需要編寫自定義的 IActionConstraint 實現(xiàn),但重要的是要理解特性的作用,比如 HttpPostAttribute —— 以及為其他 HTTP 謂詞定義的類似的特性。在常規(guī)路由中,當(dāng)操作是“顯示表單 -> 提交表單”工作流時,操作使用相同的名字是很常見的。在回顧 URL 的生成 章節(jié)后,這種模式的方便將變得更加明顯。

如果多個路由都匹配,并且 MVC 不能找到‘最好的’路由,將會拋出一個 AmbiguousActionException 異常。

路由名稱

在下面例子中的 "blog" 和 "default" 字符串是路由名稱:

app.UseMvc(routes => {routes.MapRoute("blog", "blog/{*article}",defaults: new { controller = "Blog", action = "Article" });routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); });

路由名稱給予路由一個邏輯名稱,以便被命名的路由可以用于 URL 的生成。這在路由命令可能使 URL 的生成變得復(fù)雜時,大大簡化了 URL 的創(chuàng)建。路由名稱在應(yīng)用程序內(nèi)必須唯一。

路由名稱對 URL 匹配或者處理請求沒有任何影響;它們只用于 URL 的生成。更多關(guān)于 URL 生成的詳細(xì)信息參見 路由 ,包括在具體的 MVC 幫助器中生成 URL。

特性路由

特性路由使用一組特性來直接將操作映射到路由模板。在下面的例子中,在 Configure 中使用 app.UseMvc(); 且沒有傳入路由。HomeController 會匹配一組類似于 {controller=Home}/{action=Index}/{id?} 的默認(rèn)路由 URL:

public class HomeController : Controller {[Route("")][Route("Home")][Route("Home/Index")]public IActionResult Index(){return View();}[Route("Home/About")]public IActionResult About(){return View();}[Route("Home/Contact")]public IActionResult Contact(){return View();} }

HomeController.Index() 操作會被 /、/Home 或者 /Home/Index 中任一 URL 路徑執(zhí)行。

注解
這個例子突出了特性路由與常規(guī)路由一個關(guān)鍵的不同之處。特性路由需要更多的輸入來指定一個路由;常規(guī)路由處理路由更加的簡潔。然而,特性路由允許(也必須)精確控制每個操作的路由模板。

控制器名和操作名在特性路由中是 不會 影響選擇哪個操作的。這個例子會匹配與上個例子相同的 URL。

public class MyDemoController : Controller {[Route("")][Route("Home")][Route("Home/Index")]public IActionResult MyIndex(){return View("Index");}[Route("Home/About")]public IActionResult MyAbout(){return View("About");}[Route("Home/Contact")]public IActionResult MyContact(){return View("Contact");} }

注解
上面的路由模板沒有定義針對 action、area 以及 controller 的路由參數(shù)。實際上,這些參數(shù)不允許出現(xiàn)在特性路由中。因為路由模板已經(jīng)關(guān)聯(lián)了一個操作,解析 URL 中的操作名是沒有意義的。

特性路由也可以使用 HTTP[Verb] 特性,比如 HttpPostAttribute。所有這些特性都可以接受路由模板。這個例子展示兩個操作匹配同一個路由模板:

[HttpGet("/products")] public IActionResult ListProducts() {// ... }[HttpPost("/products")] public IActionResult CreateProduct(...) {// ... }

對于 /products 這個 URL 路徑來說,ProductsApi.ListProducts 操作會在 HTTP 謂詞是 GET 時執(zhí)行,ProductsApi.CreateProduct 會在 HTTP 謂詞是 POST 時執(zhí)行。特性路由首先匹配路由模板集合中通過路由特性定義的 URL。一旦路由模板匹配,IActionConstraint 約束會應(yīng)用與決定執(zhí)行哪個操作。

小技巧
當(dāng)構(gòu)建一個 REST API,你幾乎不會想在操作方法上使用 [Route(...)]。最好是使用更加具體的 Http*Verb*Attributes 來精確的說明你的 API 支持什么。REST API 的客戶端期望知道映射到具體邏輯操作上的路徑和 HTTP 謂詞。

由于一個特性路由應(yīng)用于一個特定操作,很容易使參數(shù)作為路由模板定義中必須的一部分。在這個例子中,id 是必須作為 URL 路徑中一部分的。

public class ProductsApiController : Controller {[HttpGet("/products/{id}", Name = "Products_List")]public IActionResult GetProduct(int id) { ... } }

ProductsApi.GetProducts(int) 操作會被 URL 路徑 /products/3 執(zhí)行,但不會被 URL 路徑 /products 執(zhí)行。查看 路由 獲取路由模板以及相關(guān)選項的完整描述。

這個路由特性同時也定義了一個 Products_List 的 路由名稱。路由名稱可以用來生成基于特定路由的 URL。路由名稱對路由的 URL 匹配行為沒有影響,只用于 URL 的生成。路由名稱必須在應(yīng)用程序內(nèi)唯一。

注解
常規(guī)的 默認(rèn)路由 定義 id 參數(shù)作為可選項 ({id?})。而特性路由的這種精確指定 API 的能力更有優(yōu)勢,比如把 /products 和 /products/5 分配到不同的操作。

聯(lián)合路由

為了減少特性路由的重復(fù)部分, 控制器上的路由特性會和各個操作上的路由特性進(jìn)行結(jié)合。任何定義在控制器上的路由模板都會作為操作路由模板的前綴。在控制器上放置一個路由特性會使 所有 這個控制器中的操作使用這個特性路由。

[Route("products")] public class ProductsApiController : Controller {[HttpGet]public IActionResult ListProducts() { ... }[HttpGet("{id}")]public ActionResult GetProduct(int id) { ... } }

在這個例子中,URL 路徑 /products 會匹配 ProductsApi.ListProducts,URL 路徑 /products/5 會匹配 ProductsApi.GetProduct(int)。兩個操作都只會匹配 GET,因為它們使用 HttpGetAttribute 進(jìn)行裝飾。

應(yīng)用到操作上的路由模板以 / 開頭不會聯(lián)合控制器上的路由模板。這個例子匹配一組類似 默認(rèn)路由 的 URL 路徑。

[Route("Home")] public class HomeController : Controller {[Route("")] // Combines to define the route template "Home"[Route("Index")] // Combines to define the route template "Home/Index"[Route("/")] // Does not combine, defines the route template ""public IActionResult Index(){ViewData["Message"] = "Home index";var url = Url.Action("Index", "Home");ViewData["Message"] = "Home index" + "var url = Url.Action; = " + url;return View();}[Route("About")] // Combines to define the route template "Home/About"public IActionResult About(){return View();} }

特性路由的順序

與常規(guī)路由的根據(jù)定義順序來執(zhí)行相比,特性路由構(gòu)建一個樹形結(jié)構(gòu)同時匹配所有路由。這種行為看起來像路由條目被放置在一個理想的順序中;最具體的路由會在一般的路由之前執(zhí)行。
比如,路由 blog/search/{topic} 比 blog/{*article} 更加具體。從邏輯上講,blog/search/{topic} 路由先‘運行’,因為在默認(rèn)情況下這是唯一明智的排序。使用常規(guī)路由,開發(fā)者負(fù)責(zé)按所需的順序放置路由。
特性路由可以配置順序,通過使用所有提供路由特性的框架中的 Order 屬性。路由根據(jù) Order 屬性升序處理。默認(rèn)的 Order 是 0。使用 Order = -1 設(shè)置一個路由,這個路由會在沒有設(shè)置 Order 的路由之前運行。使用 Order = 1 會在默認(rèn)路由排序之后運行。

小技巧
避免依賴于 Order。如果你的 URL 空間需要明確的順序值來使路由正確,那么它可能使客戶端混亂。一般的特性路由會通過 URL 匹配選擇正確的路由。如果 URL 的生成的默認(rèn)順序不生效,使用路由名作為重載通常比應(yīng)用 Order 屬性更簡單。

路由模板中的標(biāo)記替換([controller],[action],[area])

為了方便,特性路由支持 標(biāo)記替換 ,通過在方括號中封閉一個標(biāo)記 ([, ]])。標(biāo)記 [action]、 [area] 以及 [controller] 會被替換成路由中定義的操作所對應(yīng)的操作名、區(qū)域名、控制器名。在這個例子中,操作可以匹配注釋中描述的 URL 路徑。

[Route("[controller]/[action]")] public class ProductsController : Controller {[HttpGet] // Matches '/Products/List'public IActionResult List() {// ...}[HttpGet("{id}")] // Matches '/Products/Edit/{id}'public IActionResult Edit(int id) {// ...} }

標(biāo)記替換發(fā)生在構(gòu)建特性路由的最后一步。上面的例子將與下面的代碼相同:

public class ProductsController : Controller {[HttpGet("[controller]/[action]")] // Matches '/Products/List'public IActionResult List() {// ...}[HttpGet("[controller]/[action]/{id}")] // Matches '/Products/Edit/{id}'public IActionResult Edit(int id) {// ...} }

特性路由也可以與繼承相結(jié)合。下面與標(biāo)記替換的集合非常強(qiáng)大。

[Route("api/[controller]")] public abstract class MyBaseController : Controller { ... }public class ProductsController : MyBaseController {[HttpGet] // Matches '/api/Products'public IActionResult List() { ... }[HttpPost("{id}")] // Matches '/api/Products/{id}'public IActionResult Edit(int id) { ... } }

標(biāo)記替換也可以應(yīng)用于在特性路由中定義路由名稱。[Route("[controller]/[action]", Name="[controller]_[action]")] 將為每一個操作生成一個唯一的路由名稱。

多路由

特性路由支持定義多個路由指向同一個操作。最常見的使用是像下面展示一樣模仿 默認(rèn)常規(guī)路由 :

[Route("[controller]")] public class ProductsController : Controller {[Route("")] // Matches 'Products'[Route("Index")] // Matches 'Products/Index'public IActionResult Index() }

放置多個路由特性到控制器上意味著每一個特性都會與每一個操作方法上的路由特性進(jìn)行結(jié)合。

[Route("Store")] [Route("[controller]")] public class ProductsController : Controller {[HttpPost("Buy")] // Matches 'Products/Buy' and 'Store/Buy'[HttpPost("Checkout")] // Matches 'Products/Checkout' and 'Store/Checkout'public IActionResult Buy() }

當(dāng)多個路由特性(IActionConstraint 的實現(xiàn))放置在一個操作上,每一個操作約束都會與特性定義的路由模板相結(jié)合。

[Route("api/[controller]")] public class ProductsController : Controller {[HttpPut("Buy")] // Matches PUT 'api/Products/Buy'[HttpPost("Checkout")] // Matches POST 'api/Products/Checkout'public IActionResult Buy() }

小技巧
雖然使用多個路由到操作上看起來很強(qiáng)大,但最好還是保持應(yīng)用程序的 URL 空間簡單和定義明確。使用多個路由到操作上僅僅在需要的時候,比如支持已經(jīng)存在的客戶端。

使用 IRouteTemplateProvider 自定義路由特性

框架提供的所有路由特性([Route(...)], [HttpGet(...)] 等等。)都實現(xiàn)了 IRouteTemplateProvider 接口。當(dāng)應(yīng)用程序啟動時,MVC 查找控制器類和操作方法上實現(xiàn)了 IRouteTemplateProvider 接口的特性來構(gòu)建初始路由集合。

你可以通過實現(xiàn) IRouteTemplateProvider 來定義你自己的路由特性。每個 IRouteTemplateProvider 允許你定義一個包含自定義路由模板,順序以及名稱的單路由:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider {public string Template => "api/[controller]";public int? Order { get; set; }public string Name { get; set; } }

上面例子中,當(dāng) [MyApiController] 特性被應(yīng)用,會自動設(shè)置 Template 為 "api/[controller]"。

使用應(yīng)用程序模型來自定義特性路由

應(yīng)用程序模型 是一個在啟動時創(chuàng)建的對象模型,它包含了所有 MVC 用來路由和執(zhí)行操作的元數(shù)據(jù)。應(yīng)用程序模型 包含從路由特性中收集的所有數(shù)據(jù)(通過 IRouteTemplateProvider)。你可以在啟動時編寫 約定 修改應(yīng)用程序模型來自定義路由的行為。這個章節(jié)展示了一個使用應(yīng)用程序模型自定義路由的例子。

using Microsoft.AspNetCore.Mvc.ApplicationModels; using System.Linq; using System.Text; public class NamespaceRoutingConvention : IControllerModelConvention {private readonly string _baseNamespace;public NamespaceRoutingConvention(string baseNamespace){_baseNamespace = baseNamespace;}public void Apply(ControllerModel controller){var hasRouteAttributes = controller.Selectors.Any(selector =>selector.AttributeRouteModel != null);if (hasRouteAttributes){// This controller manually defined some routes, so treat this // as an override and not apply the convention here.return;}// Use the namespace and controller name to infer a route for the controller.//// Example://// controller.ControllerTypeInfo -> "My.Application.Admin.UsersController"// baseNamespace -> "My.Application"//// template => "Admin/[controller]"//// This makes your routes roughly line up with the folder structure of your project.//var namespc = controller.ControllerType.Namespace;var template = new StringBuilder();template.Append(namespc, _baseNamespace.Length + 1,namespc.Length - _baseNamespace.Length - 1);template.Replace('.', '/');template.Append("/[controller]");foreach (var selector in controller.Selectors){selector.AttributeRouteModel = new AttributeRouteModel(){Template = template.ToString()};}} }

混合路由

MVC 應(yīng)用程序可以混合使用常規(guī)路由和特性路由。對于給瀏覽器處理頁面的控制器,通常使用常規(guī)路由;對于提供 REST API 的控制器,通常使用特性路由。

操作在常規(guī)路由或者特性路由中二選一。放置一個路由到控制器上或者操作上使操作變?yōu)樘匦月酚伞6x為特性路由的操作不能通過常規(guī)路由訪問,反之亦然。放置在控制器上的 任何 路由特性都會使控制器中的所有操作變?yōu)樘匦月酚伞?/p>

注解
這兩種路由系統(tǒng)的區(qū)別是通過 URL 匹配路由模板的過程。在常規(guī)路由中,匹配中的路由值被用來在所有常規(guī)路由操作的查找表中選擇操作以及控制器。在特性路由中,每個模板已經(jīng)關(guān)聯(lián)了一個操作,進(jìn)一步查找是沒必要的。

URL 的生成

MVC 應(yīng)用程序可以使用路由 URL 的生成特性來生成 URL 鏈接到操作。生成 URL 消除硬編碼 URL,使你的代碼健壯和易維護(hù)。這個章節(jié)關(guān)注 MVC 提供的 URL 生成特性,并只覆蓋如何生成 URL 的基本知識。查看 路由 獲取 URL 生成的詳細(xì)描述。

IUrlHelper 接口是 MVC 與生成 URL 的路由之間基礎(chǔ)設(shè)施的基本塊。你可以通過控制器、視圖以及視圖組件中的 Url 屬性找到一個可用的 IUrlHelper 實例。

在這個例子中,IUrlHelper 接口用于 Controller.Url 屬性來生成一個到其他操作的 URL 。

using Microsoft.AspNetCore.Mvc;public class UrlGenerationController : Controller {public IActionResult Source(){// Generates /UrlGeneration/Destinationvar url = Url.Action("Destination");return Content($"Go check out {url}, it's really great.");}public IActionResult Destination(){return View();} }

如果應(yīng)用程序使用默認(rèn)的常規(guī)路由,url 變量的值會是 URL 路徑字符串 /UrlGeneration/Destination。這個 URL 路徑是將路由值與當(dāng)前請求(環(huán)境值)相結(jié)合而成的,并將值傳遞給 Url.Action 并替換這些值到路由模板:

ambient values: { controller = "UrlGeneration", action = "Source" } values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" } route template: {controller}/{action}/{id?}result: /UrlGeneration/Destination

路由模板中每一個路由參數(shù)的值都被匹配名字的值和環(huán)境值替換。一個路由參數(shù)如果沒有值可以使用默認(rèn)值,或者該參數(shù)是可選的則跳過(就像這個例子中 id 的情況)。任何必須的路由參數(shù)沒有相應(yīng)的值會導(dǎo)致 URL 的生成失敗。如果一個路由中 URL的生成失敗,會嘗試下一個路由,直到所有路由都嘗試完成或者找到匹配的路由。

上面 Url.Action 的例子假設(shè)是傳統(tǒng)路由,但是 URL 的生成工作與特性路由類似,盡管概念是不同的。在路由值常規(guī)路由中,路由值被用來擴(kuò)大一個模板,并且關(guān)于 controller 和 action 的路由值通常出現(xiàn)在那個模板中 —— 這生效了,因為路由匹配的URL 堅持了一個 約定。在特性路由中,關(guān)于 controller 和 action 的路由值不被允許出現(xiàn)在模板中 —— 它們用來查找該使用哪個模板。

這個例子使用特性路由:

// In Startup class public void Configure(IApplicationBuilder app) {app.UseMvc(); } using Microsoft.AspNetCore.Mvc;public class UrlGenerationController : Controller {[HttpGet("")]public IActionResult Source(){var url = Url.Action("Destination"); // Generates /custom/url/to/destinationreturn Content($"Go check out {url}, it's really great.");}[HttpGet("custom/url/to/destination")]public IActionResult Destination() {return View();} }

MVC 構(gòu)建了一個所有特性路由操作的查找表并且會匹配 controller 和 action 值選擇路由模板用于 URL 的生成。在上面的例子中,custom/url/to/destination 被生成了。

通過操作名生成 URL

Url.Action ( IUrlHelper 、 Action)以及所有相關(guān)的重載都是基于通過指定控制器名和操作名來指定想要鏈接到的地方的。

注解
當(dāng)使用 Url.Action,controller 和 action 的當(dāng)前路由值是為你指定的 —— controller 和 action 的值同時是 環(huán)境值 值 的一部分。Url.Action 方法總是使用 controller 和 action 的當(dāng)前值并且生成路由到當(dāng)前操作的 URL 路徑。

路由嘗試使用環(huán)境值中的值來填充信息,以至于在生成 URL 時你不需要提供信息。使用路由如 {a}/{b}/{c}/ozvdkddzhkzd 并且環(huán)境值 { a = Alice, b = Bob, c = Carol, d = David },路由擁有足夠的信息生成路由而不需要任何額外的值 —— 因為所有的路由參數(shù)都有值。如果你添加值 { d = Donovan },那么值 { d = David } 會被忽略,并且生成的 URL 路徑會是 Alice/Bob/Carol/Donovan。

警告
URL 路徑是分層次的。在上面的例子中,如果你添加值 { c = Cheryl },所有的值 { c = Carol, d = David } 會被忽略。在這種情況下,我們不再有 d 的值,且 URL 生成會失敗。你需要指定 c 和 d 所需的值。你可能期望用默認(rèn)路由 ({controller}/{action}/{id?}) 來解決這個問題 —— 但是你很少會在實踐中遇到這個問題,Url.Action 總會明確地指定 controller 和 action 的值。

Url.Action 較長的重載也采取額外的 路由值 對象來提供除了 controller 和 action 意外的路由參數(shù)。你最長看到的是使用 id,比如 Url.Action("Buy", "Products", new { id = 17 })。按照慣例,路由值 通常是一個匿名類的對象,但是它也可以是一個 IDictionary<> 或者一個 普通的 .NET 對象。任何額外的路由值不會匹配放置在查詢字符串中的路由參數(shù)。

using Microsoft.AspNetCore.Mvc;public class TestController : Controller {public IActionResult Index(){// Generates /Products/Buy/17?color=redvar url = Url.Action("Buy", "Products", new { id = 17, color = "red" });return Content(url);} }

小技巧
為了創(chuàng)建一個絕對 URL,使用一個接受 protocol 的重載: Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)

通過路由生成 URL

上面的代碼展示了通過傳遞控制器名和操作名創(chuàng)建 URL。IUrlHelper 也提供 Url.RouteUrl 的系列方法。這些方法類似 Url.Action,但是它們不復(fù)制 action 和 controller 的當(dāng)前值到路由值。最常見的是指定一個路由名來使用具體的路由生成 URL,通常 沒有 指定控制器名或者操作名。

using Microsoft.AspNetCore.Mvc;public class UrlGenerationController : Controller {[HttpGet("")]public IActionResult Source(){var url = Url.RouteUrl("Destination_Route"); // Generates /custom/url/to/destinationreturn Content($"See {url}, it's really great.");}[HttpGet("custom/url/to/destination", Name = "Destination_Route")]public IActionResult Destination() {return View();} }

在 HTML 中生成URL

IHtmlHelper 提供 HtmlHelper 方法 Html.BeginForm 和 Html.ActionLink 來分別生成 <form> 和 <a> 元素。這些方法使用 Url.Action 方法來生成一個 URL 并且它們接受類似的參數(shù)。Url.RouteUrl 相對于 HtmlHelper 的是 Html.BeginRouteForm 和 Html.RouteLink,它們有著類似的功能。查看 :doc:/mvc/views/html-helpers 獲取更多信息。

TagHelper 通過 form 和 <a> TagHelper 生成 URL。這些 都使用了 IUrlHelper 為它們的實現(xiàn)。查看 Working with Forms 獲取更多信息。

內(nèi)部觀點,IUrlHelper 通過 Url 屬性生成任何不包含上述的特定 URL。

在操作結(jié)果中生成 URL

上面的例子展示了在控制器中使用 IUrlHelper,而在控制器中最常見的用法是生成一個 URL 作為操作結(jié)果的一部分。

ControllerBase 和 Controller 基類針對引用其他操作的操作結(jié)果提供了方便的方法。一個典型的使用是接受用戶輸入后重定向。

public Task<IActionResult> Edit(int id, Customer customer) {if (ModelState.IsValid){// Update DB with new details.return RedirectToAction("Index");} }

操作結(jié)果工廠方法遵循 IUrlHelper 中類似模式的方法。

專用常規(guī)路由的特殊情況

常規(guī)路由可以使用一種特殊的路由被稱作 專用常規(guī)路由。在下面的例子中,被命名為 blog 的路由是專用常規(guī)路由。

app.UseMvc(routes => {routes.MapRoute("blog", "blog/{*article}",defaults: new { controller = "Blog", action = "Article" });routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); });

使用這些路由定義,Url.Action("Index", "Home") 會使用默認(rèn)路由生成 URL 路徑 /,但是為什么呢?你可能會猜路由值 { controller = Home, action = Index } 會足以用 blog 路由來生成 URL,并且結(jié)果會是 /blog?action=Index&controller=Home。

專用常規(guī)路由依靠默認(rèn)路由的一個特殊行為,沒有相應(yīng)的路由參數(shù),以防止路由生成 URL “太貪婪”。在這種情況下默認(rèn)的值是 { controller = Blog, action = Article },而不是出現(xiàn)在路由參數(shù)中的 controller 或者 action。當(dāng)路由執(zhí)行 URL 的生成,提供的值必須匹配默認(rèn)路由。URL 的生成使用 blog 將失敗,因為值 { controller = Home, action = Index } 不匹配 { controller = Blog, action = Article }。然后路由回退到嘗試 default,并成功。

區(qū)域

區(qū)域 是一個 MVC 特點,用來組織相關(guān)的功能到一個單獨的路由命名空間(針對控制器操作)的組和單獨的文件夾結(jié)構(gòu)中(針對視圖)。使用區(qū)域允許一個應(yīng)用程序擁有多個同名的路由器 —— 只要它們有不同的 區(qū)域。使用區(qū)域達(dá)到通過添加另一個路由參數(shù)分層的目的,area 到 controller 以及 action。這個章節(jié)將討論如何路由作用于區(qū)域 —— 查看 區(qū)域 獲取區(qū)域如何與視圖配合使用的詳細(xì)信息。

下面的例子使用默認(rèn)常規(guī)路由配置 MVC,以及一個命名為 Blog 的 區(qū)域路由:

app.UseMvc(routes => {routes.MapAreaRoute("blog_route", "Blog","Manage/{controller}/{action}/{id?}");routes.MapRoute("default_route", "{controller}/{action}/{id?}"); });

當(dāng)匹配 URL 路徑如 /Manage/Users/AddUser 時,第一個路由會產(chǎn)生路由值 { area = Blog, controller = Users, action = AddUser }。area 路由值是通過 area 的默認(rèn)值產(chǎn)生的,實際上通過 MapAreaRoute 創(chuàng)建路由和下面的方式是相等的:

app.UseMvc(routes => {routes.MapRoute("blog_route", "Manage/{controller}/{action}/{id?}",defaults: new { area = "Blog" }, constraints: new { area = "Blog" });routes.MapRoute("default_route", "{controller}/{action}/{id?}"); });

MapAreaRoute 創(chuàng)建一個路由同時使用默認(rèn)路由和 area 約束,約束使用提供的區(qū)域名,在這個例子中是 Blog。默認(rèn)值保證路由總是處理 { area = Blog, ... },約束要求值 { area = Blog, ... } 來進(jìn)行 URL 的生成。

小技巧
常規(guī)路由是順序依賴。一般來說,區(qū)域路由需要被放置在路由表的前面,因為沒有比區(qū)域路由更具體的路由了。

使用上述例子,路由值將匹配下面操作:

using Microsoft.AspNetCore.Mvc;namespace MyApp.Namespace1 {[Area("Blog")]public class UsersController : Controller{public IActionResult AddUser(){return View();} } }

AreaAttribute 表示控制器屬于一個區(qū)域的一部分,我們說,這個控制器是在 Blog 區(qū)域。控制器不帶 [Area] 特性則不是任何區(qū)域的成員,并且當(dāng) area 路由值通過路由提供時 不會 匹配。在下面的例子中,只有第一個列出的控制器可以匹配路由值 { area = Blog, controller = Users, action = AddUser }。

using Microsoft.AspNetCore.Mvc;namespace MyApp.Namespace1 {[Area("Blog")]public class UsersController : Controller{public IActionResult AddUser(){return View();} } } using Microsoft.AspNetCore.Mvc;namespace MyApp.Namespace2 {// Matches { area = Zebra, controller = Users, action = AddUser }[Area("Zebra")]public class UsersController : Controller{public IActionResult AddUser(){return View();} } } using Microsoft.AspNetCore.Mvc;namespace MyApp.Namespace3 {// Matches { area = string.Empty, controller = Users, action = AddUser }// Matches { area = null, controller = Users, action = AddUser }// Matches { controller = Users, action = AddUser }public class UsersController : Controller{public IActionResult AddUser(){return View();}} }

注解
為了完整性,將每個控制器的命名空間顯示到這里 —— 否則控制器將會遇到命名沖突并且聲稱一個編譯錯誤。類命名空間不影響 MVC 的路由。

前兩個控制器是區(qū)域的成員,并只匹配通過 area 路由值提供的各自的區(qū)域名。第三個控制器不是任何區(qū)域的成員,只會在路由中沒有 area 值時匹配。

注解
在匹配 no value 方面,缺少 area 值與 area 是 null 或者空字符串是一樣的。

當(dāng)執(zhí)行一個區(qū)域內(nèi)的操作時,area 的路由值可作為用于生成 URL 的 環(huán)境值。這意味著默認(rèn)情況下區(qū)域針對 URL 的生成有 黏性 ,如下面例子所示。

app.UseMvc(routes => {routes.MapAreaRoute("duck_route", "Duck","Manage/{controller}/{action}/{id?}");routes.MapRoute("default", "Manage/{controller=Home}/{action=Index}/{id?}"); }); using Microsoft.AspNetCore.Mvc;namespace MyApp.Namespace4 {[Area("Duck")]public class UsersController : Controller{public IActionResult GenerateURLInArea(){// Uses the 'ambient' value of areavar url = Url.Action("Index", "Home"); // returns /Managereturn Content(url);}public IActionResult GenerateURLOutsideOfArea(){// Uses the empty value for areavar url = Url.Action("Index", "Home", new { area = "" }); // returns /Manage/Home/Indexreturn Content(url);}} }

理解 IActionConstraint

注解
這一節(jié)是框架內(nèi)部的一個深潛和 MVC 如何選擇操作執(zhí)行。通常一個應(yīng)用程序不需要自定義 IActionConstraint

你可能已經(jīng)使用 IActionConstraint 即使你不熟悉這個接口。[HttpGet] 特性以及類似的 [Http-VERB] 特性實現(xiàn) IActionConstraint 接口以用于限制操作方法的執(zhí)行。

public class ProductsController : Controller {[HttpGet]public IActionResult Edit() { }public IActionResult Edit(...) { } }

假設(shè)默認(rèn)的常規(guī)路由,URL 路徑 /Products/Edit 會產(chǎn)生值 { controller = Products, action = Edit },將 同時 匹配這里顯示的兩個操作。在 IActionConstraint 的術(shù)語中,我們會說這兩個操作同時被視為候選項 —— 因為它們都匹配路由數(shù)據(jù)。

當(dāng) HttpGetAttribute 執(zhí)行,它將聲明 Edit() 匹配 GET 并且不匹配其他的 HTTP 謂詞。Edit(...) 操作沒有定義任何約束,所以會匹配任何 HTTP 謂詞。所以假設(shè)有一個 POST 操作 —— 只有 Edit(...) 會匹配。但是如果是 GET 兩個操作都會匹配 —— 然而,一個操作使用了 IActionConstraint 總是被認(rèn)為 更好 與沒有使用的操作。所以因為 Edit() 有 [HttpGet] ,它被視為更加具體,并且在兩個操作都可以匹配時被選中。

從概念上講, IActionConstraint 是 重載 的一種形式,但不是使用相同名稱的重載方法,它是匹配相同 URL 的操作的重載。特性路由也使用 IActionConstraint 并且可能導(dǎo)致不同控制器的操作被視為候選。

實現(xiàn) IActionConstraint

實現(xiàn) IActionConstraint 最簡單的方式是創(chuàng)建一個類派生自 System.Attribute 并且將它放置到你的操作和控制器上。MVC 會自動發(fā)現(xiàn)任何作為特性被應(yīng)用的 IActionConstraint。你可以使用應(yīng)用程序模型來應(yīng)用約束,并且這可能是最靈活的方法,因為它可以允許你對它們?nèi)绾伪粦?yīng)用進(jìn)行元編程。

在下面的例子,一個約束選擇一個操作基于一個來自路由數(shù)據(jù)的 country code 。GitHub 上完整的示例 .

public class CountrySpecificAttribute : Attribute, IActionConstraint {private readonly string _countryCode;public CountrySpecificAttribute(string countryCode){_countryCode = countryCode;}public int Order{get{return 0;}}public bool Accept(ActionConstraintContext context){return string.Equals(context.RouteContext.RouteData.Values["country"].ToString(),_countryCode,StringComparison.OrdinalIgnoreCase);} }

你負(fù)責(zé)實現(xiàn) Accept 方法并選擇一個 ‘Order’ 用于約束執(zhí)行。在這個例子中,Accept 方法返回 true 表示當(dāng) country 路由值匹配時操作是匹配的。這和 RouteValueAttribute 不同,因為它允許回退到一個非特性操作。這個例子展示了如果你定義一個 en-US 操作,然后國家代碼是 fr-FR 會回退到一個更通用的控制器,這個控制器沒有應(yīng)用 [CountrySpecific(...)]。

Order 特性決定約束的部分是哪個階段。操作約束基于 Order 在組中運行。比如,所有框架提供的 HTTP 方法特性使用相同 Order 值,所以他們運行在同一階段。你可以擁有許多階段,來實現(xiàn)你所需要的策略。

小技巧
要決定一個 Order 的值,考慮你的約束是否需要在 HTTP 方法之前被應(yīng)用。數(shù)字越低,運行越早。

總結(jié)

以上是生活随笔為你收集整理的ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。