ASP.NET Core MVC 之路由(Routing)

   ASP.NET Core MVC 路由是創建在ASP.NET Core 路由的,一項強大的URL映射組件,它能夠構建具備理解和搜索網址的應用程序。這使得咱們能夠自定義應用程序的URL命名形式,使得它在搜索引擎優化(SEO)和連接生成中運行良好,而不用關心Web服務器上的文件是怎麼組織的。咱們能夠方便的使用路由模板語法定義路由,路由模板語法支持路由值約束,默認值和可選值。編程

  基於約束的路由容許全局定義應用支持的URL格式,以及這些格式是怎樣各自在給定的控制器中映射到指定的操做方法(Action)。當接受到一個請求時,路由引擎解析URL並將其匹配至一個定義URL格式,而後調用相關的控制器操做方法。 api

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

  特性路由(Attribute Routing) 容許以在控制器和方法使用添加特性的方式指定路由信息來定義應用程序的路由。這意味着路由定義緊鄰它們所關聯的控制器和方法。服務器

  ASP.NET Core MVC 使用路由中間件來匹配傳入請求的URL,並將它們映射到操做方法。路由在啓動代碼或屬性中定義,它描述了網址路徑應如何與操做方法匹配,還用於響應中生成連接併發送。併發

 

1.設置路由中間件app

  建立一個ASP.NET Core Web應用程序,在Startup類的Configure方法中有:框架

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

  在對UseMvc的調用過程當中,MapRoute用於建立單個路由,即默認路由。大多數MVC應用程序都使用與默認路由模板相似的路由。ide

  路由模板{controller=Home}/{action=Index}/{id?} 能夠匹配相似 Blog/Details/5 的URL路徑,而且提取路由值 {controller=Blog,action=Details,id=5}。MVC將嘗試查找名爲BlogController的控制器,並運行操做方法。優化

    {controller=Home}將Home定義爲默認控制器ui

    {action=Index}將Index定義爲默認操做this

    {id?}將id定義爲可選

    默認路徑參數和可選路徑參數能夠不出如今須要匹配的URL路徑中。

  使用{controller=Home}/{action=Index}/{id?}模板,能夠對如下URL路徑都執行HomeController.Index:

    /Home/Index/7

    /Home/Index

    /Home

    /

 

  有個簡單方法 app.UseMvcWithDefaultRoute() 能夠替換掉上面的方法。

  UseMvc 和 UseMvcWithDefaultRoute 都是將RouteMiddleware的實例添加到中間件管道。MVC不直接與中間件交互,而是使用路由來處理請求。MVC經過MvcRouteHandler的實例連接到路由。下面的代碼與UseMvc相似: 

var route = new RouteBuilder(app);
            //添加鏈接到MVC,經過調用MapRoute來回調
            route.DefaultHandler = new MvcRouteHandler(...);
            //執行回調以註冊路由
            route.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
            //建立路由集合並添加至中間件
            app.UseRouter(route.Build());

  UseMvc 不直接定義任何路由,它爲屬性路由的路由集合添加一個佔位符。重載UseMvc 使得咱們能夠添加本身的路由,而且還支持屬性路由。UseMvc 及其全部變體爲屬性路由添加了佔位符,這使得屬性路由始終可用。UseMvcWithDefaultRoute定義默認路由而且支持屬性路由。

 

2.常規路由

   routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");  這是一個常規路由,由於它創建了一個約定的URL路徑:

    第一路徑段映射到控制器名稱

    第二路徑映射到操做名稱

    第三區段是可選id,用於映射到模型實體

 

  使用default路由,URL 路徑 /Blog/Index 將映射到BlogController.Index 操做。該映射是基於控制器和操做名稱,而不是基於命名空間,源文件位置等。

  使用常規路由的默認路由能夠快速構建應用程序,而無需定義每個操做的路由。對於CRUD 操做風格的應用程序,整個控制器的URL具備一致性。

 

3.多路由

  能夠在UseMvc 裏面經過添加MapRoute 來添加多個路由。這樣能夠定義多個約定,或添加專用於特定操做的常規路由: 

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

  這裏的blog 路由是專用常規路由,這意味着它不採用常規路由系統,而是專用於一個特定地的動做。這個路由始終映射到BlogController.Index。

  路由集合中的路由是有序的,而且會按照它們被添加的順序進行處理。

  

  1.回退

  做爲請求處理的一部分,MVC將驗證路由值是否能夠用來查找應用程序中的控制器和操做。若是路由值不匹配操做,那麼該路由被認爲是不匹配的,將嘗試下一個路由。這一過程稱爲回退,由於常規路由有重疊的狀況。

  

  2.行動歧義

  當兩個一致的操做經過路由時,MVC必須消除歧義,選擇最佳操做,不然會拋出異常。例如:

public class BlogController : Controller
    {
        public ActionResult Edit(int id)
        {
            return View();
        }

        [HttpPost]
        public ActionResult Edit(int id, IFormCollection collection)
        {
            try
            {
                // TODO: Add update logic here

                return RedirectToAction(nameof(Index));
            }
            catch
            {
                return View();
            }
        }

    }

  URL /Blog/Edit/7 能夠匹配這兩個操做,這是MVC控制器的典型模式,其中Edit(int)用於顯示編輯的表單,Edit(int,IFormCollection)用於 處理已提交的表單。爲了達到這個目的,MVC須要在HTTP POST時選擇Edit(int,IFormCollection),在其餘HTTP動詞時選擇Edit(int)。

  HttpPostAttribute 是IActionConstraint 的一個實現,它只容許在HTTP動詞爲POST時選擇動做。IActionConstraint的存在使得Edit(int,IFormCollection)比Edit(int)更好匹配。

  若是有多個路由匹配,而且MVC沒法找到一個最佳路由,則會拋出AmbiguousActionException異常。

 

  3.路由名稱

  上面的例子中"blog"和"default"字符串是路由名稱,路由名稱爲路由提供了一個邏輯名稱,以便命名的路由可用於生成URL。在應用程序範圍內路由必須名稱必須是惟一的。

  路由名稱對URL匹配或請求的處理沒有影響,僅用於URL生成。

 

4.路由特性

  特性路由使用一組特性直接將操做映射到路由模板。下面在Configure中調用 app.UseMvc(); 沒有傳遞路由。

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

        [Route("Home/About")]
        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }
}

  HomeController.Index操做將會對 /,/Home 或者/Home/Index 任一URL訪問執行。

  特性路由須要有更多的輸入來指定一個路由,而常規路由處理路由時更加簡潔。然而,特性路由容許精準控制每一個操做的路由模板。

  上面的模板中沒有定義針對 action,area ,controller的路由參數。實際上,這些參數不容許出如今特性路由中,由於路由模板已經關聯了一個操做,解析URL中的操做名是沒有意義的。

  特性路由也可使用HTTP[Verb]特性,如HTTPPost:

        [HttpGet("/Blog")]
        public ActionResult Index()
        {
            return View();
        }

  因爲特性路由適用於特定操做,所以很容易使參數做爲模板定義中必須的一部分。下面的例子,id是URL中必須的一部分:

[HttpGet("Blog/Edit/{id}")]
        public ActionResult Edit(int id)
        {
            return View();
        }

  常規的默認路由定義id參數做爲可選項({id?}),而特性路由的是必須參數,這種能夠精準指定,好比包/Blog/Get 和 /Blog/Get/{id} 分配到不一樣的操做。

 

5.組合路由

  爲了減小特性路由的重複部分,控制器上的路由特性會和各個操做上的路由特性進行結合。任何定義在控制器上的路由模板都會做爲操做路由模板的前綴。

[Route("blog")]
    public class BlogController : Controller
    {
        [HttpGet]
        public ActionResult GetAll()
        {
            return View();
        }
        [HttpGet("{id}")]
        public ActionResult GetById(int id)
        {
            return View();
        }
}

  /blog 匹配GetAll方法, /blog/1 匹配 GetById方法。

  注意,若是操做上路由模板以 / 開頭時不會結合控制器上的路由模板。

 

6.特性路由的順序

  常規路由會根據定義順序來執行,與之相比,特性路由會構建一個樹形結構,同時匹配全部路由。這種看起來像路由條目被放置在一個理想的順序中,最具體的路由會在通常的路由以前執行。好比,路由blog/Edit/4 比 blog/{*article} 更加具體。

  特性路由使用全部框架提供的路由特有的Order屬性來配置順序,並根據Order屬性升序處理路由。默認是0,設置爲-1時會在沒有設置的路由以前執行。

 

7.路由模板中的標記替換( [controller] , [action] , [area])

  爲了方便,特性路由支持標記替換,即經過在在方括號中封閉一個標記([, ])來替換對應的名稱。標記[action],[area],[controller]會被替換成操做所對應的操做名,區域名,控制器名。

    [Route("[controller]/[action]")]
    public class BlogController : Controller
    {
        [HttpGet]//匹配Blog/GetAll
        public ActionResult GetAll()
        {
            return View();
        }
}

  標記替換髮生在構建特性路由的最後一步。與上面結果相同的寫法:

    public class BlogController : Controller
    {
        [HttpGet("[controller]/[action]")]//匹配Blog/GetAll
        public ActionResult GetAll()
        {
            return View();
        }
}

  特性路由也能夠與繼承相結合,即繼承父類的路由標記。

  特性路由支持單個操做定義路由。若是用IActionConstarint實現的多個路由特性定義在一個操做上時,每一個操做約束與特性定義的路由相結合:

    [Route("Store")]
    [Route("[controller]")]
    public class BlogController : Controller
    {
        [HttpGet("GetAll")]//匹配 Get Blog/GetAll和 Store/GetAll
        [HttpPost("Set")]//匹配 Post Blog/Set和 Store/Set
        public ActionResult GetAll()
        {
            return View();
        }
}

  雖然使用多個路由到一個操做看起來很強大,但最好仍是保持URL的空間簡單和定義明確。使用多個路由到操做上僅僅在特殊須要的時候,好比支持多個客戶端。

 

8.使用IRouteTemplateProvider自定義路由特性

  全部框架提供的路由特性([Route(...) ] ,[HttpGet(...)]等)都實現了 IRouteTemplateProvider 接口。當程序啓動時,MVC查找控制器類和操做方法上都實現 IRouteTemplateProvider 接口的特性來構建儲時路由集合。

  能夠經過實現 IRouteTemplateProvider 來定義本身的路由特性。每一個 IRouteTemplateProvider 都容許定義使用自定義路由模板,順序以及名稱的單一路由:

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

  當 [MyApiController] 特性被應用時,會自動設置Template 爲 api/[controller] 。

 

9.使用應用程序模型來自定義特性路由

  應用程序模型時啓動時建立的對象模型,其中包含MVC用於路由和執行操做的全部元數據。應用程序模型包括從路由特性收集的全部數據(經過 IRouteTemplateProvider)。咱們能夠編寫約定以在啓動時修改應用程序模型爲自定義路由行爲。

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)
            {
                //此控制器自定義了一些路由,所以將其視爲覆蓋
                return;
            }


            // 使用命名空間和控制器來推斷控制器的路由
            //
            // Example:
            //
            //  controller.ControllerTypeInfo ->    "My.Application.Admin.UsersController"
            //  baseNamespace ->                    "My.Application"
            //
            //  template =>                         "Admin/[controller]"
            //
            // 這使得你的路由大體與你的項目結構一致
            //
            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()
                };
            }
        }
    }

  這部分怎麼使用,我的仍是不是很清楚,這裏只是記錄了官方文檔,有哪位知道能夠告訴如下小弟。

 

10.URL生成

  MVC應用程序可使用路由URL的生成特性來生成URL連接到操做。生成URL能夠消除硬編碼URL,使代碼更加健壯和易維護。IUrlHelper 接口是MVC與生成URL路由之間基礎設施的基本塊。能夠經過控制器,視圖以及視圖組件中的URL屬性找到一個可用的IUrlHelper實例:

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //生成/Home/Contact
            var url = Url.Action("Contact");
            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }
}

  這個URL路徑是由路由值與當前請求相結合而成的路由建立,並將值傳遞給Url.Action,替換路由模板中對應的值。

  上面Url.Action(的例子是常規路由,可是URL的生成工做與特性路由相似,儘管概念不一樣。在常規路由中,路由值被用來擴展模板,而且關於controller和action的路由值一般出如今那個模板,由於路由匹配的URL堅持了一個約定。在特性路由中,關於controller和action的路由值不容許出如今模板中--它們用來查找應該使用哪一個模板,例如:

//修改Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();

        }



    public class HomeController : Controller
    {
        [HttpGet("/")]
        public IActionResult Index()
        {
            //生成/Home/To/About
            var url = Url.Action("About");
            return View();
        }

        [HttpGet("Home/To/About")]
        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }
}

  MVC構建了一個全部特性路由操做的查找表,而且會匹配controller和action值來選擇路由模板用於生成URL。

 

11.經過操做名生成URL

     Url.Action(this IUrlHelper helper, string action) 以及全部相關的重載都是基於指定控制器名稱和操做名來指定要連接的內容。

  當使用Url.Action時,controller 和 action 的當前路由值是指定的 -- controller 和 action 的值同時是環境值和值的一部分。Url.Action 方法老是使用 controller 和 action 的當前值,而且生成路由到當前操做的URL路徑。

  路由嘗試使用環境值中的值來填充信息,同時咱們也能夠指定路由參數:

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //生成/Blog/Edit/1
            var url = Url.Action("Edit", "Blog",new { id=1});
            //生成/Blog/Edit/1?color=red
            var url1 = Url.Action("Edit", "Blog", new { id = 1 ,color="red"});
            return View();
        }
    }

  若是像建立一個絕對URL,可使用一個接受protocol的重載: Url.Action("Edit", "Blog",new { id=1},protocol:Request.Scheme);

  

12.經過路由名生成URL

  IUrlHelper也提供了 Url.RouteUrl 的系列方法,最多見的是指定一個路由名來使用具體的路由生成URL,一般沒有指定控制器名或操做名:

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //生成customer/to/url
            var url = Url.RouteUrl("AboutRoute");
            return View();
        }

        [HttpGet("customer/to/url",Name = "AboutRoute")]
        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }
    }

  在HTML 中生成的URLHtmlHelper,提供了 HtmlHelper 方法 Html.BeginForm 和 Html.ActionLink 來分別生成<form>和<a>元素。這些方法使用Url.Action方法來生成一個URL,而且它們接受相似的參數。Url.RouteLink ,它們有相似的功能。TagHelper經過form和<a> TagHelper生成URL。這些都使用了IUrlHelper 做爲它們的實現。在內部視圖中,IUrlHelper 經過Url 屬性生成任何不包含上述的特定URL。

 

13.在操做結果中生成URL

  在控制器中常見的一個用法是生成一個URL做爲操做結果的一部分。Controller和ControllerBase 基類爲引用其餘操做的操做結果提供了簡單方法。一個典型的方法:

public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //生成customer/to/url
            var url = Url.RouteUrl("AboutRoute");
            return Redirect(url);
            //或者
            //return RedirectToAction("Contact");
        }

        [HttpGet("customer/to/url",Name = "AboutRoute")]
        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";

            return View();
        }
    }

  RedirectToAction方法有多個重載可使用。

 

14.專用常規路由的特殊狀況

  有一種特殊的路由被稱爲專用常規路由,下面被命名爲blog的路由就是:

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

  Url.Action("Index", "Home") 會使用默認路由生成URL。

  專用常規路由是依靠默認路由的一個特殊行爲,沒有相應的路由參數,以防止路由生成URL「太貪婪」。當路由執行URL生成時,提供的值必須與默認值匹配:不然使用blog的URL生成失敗,由於值 {controller=Home,action=Index}不匹配{controller=Blog,action=Index}。而後路由回退嘗試default,併成功。

 

15.區域

   Areas 是一種MVC功能,用來將相關功能組織爲一個組,做爲單獨的路由命名空間(用於控制器操做)和文件夾結構(用於視圖)。使用區域容許應用程序擁有多個相同名稱的控制器——只要它們具備不一樣的區域。使用區域經過向控制器和操做添加另外一個路由參數,區域可建立用於路由目的的層次結構。

  使用默認常規路由配置MVC,命名一個OMS區域的路由:

app.UseMvc(routes =>
            {
                routes.MapAreaRoute("oms", "OMS", "OManage/{controller}/{action}/{id?}",
                    defaults: new { Controller = "Order", Action = "Index" });
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
namespace Blog.Areas.OMS.Controllers
{
    [Area("OMS")]
    public class OrderController : Controller
    {
        // GET: Order
        public ActionResult Index()
        {
            return View();
        }
}

  當URL爲 /OManage/Order/Edit 時,會匹配路由值 {area = OMS,controller = Order , action = Edit}。area路由值是經過area默認值產生的。使用MapRoute方法也能夠實現:

routes.MapRoute("oms", "OManage/{controller}/{action}/{id?}",
                    defaults:new {area="OMS" },constraints:new { area = "OMS" });

  MapAreaRoute建立一個路由,同時使用默認路由和area 約束,約束使用提供的區域名 OMS。默認值保證路由老是處理 {area = OMS},約束要求值{area = OMS} 來進行URL生成。

  常規路由是順序依賴。通常區域路由放置在前面,由於區域路由更具體。

  AreaAttribute表示控制器屬於一個區域的一部分,即這個控制器是在 OMS 區域。控制器不帶[Area] 特性則不屬於任何區域。

 

  當在區域內執行操做時,區域的路由值將做爲環境值以用於URL生成,這意味着,在默認狀況下,區域對URL生成具備黏性:

namespace Blog.Areas.OMS.Controllers
{
    [Area("OMS")]
    public class OrderController : Controller
    {
        // GET: Order
        public ActionResult Index()
        {
            //生成/OManage/Home/Create
            var url = Url.Action("Create","Home");
            //生成/Home/Create
            var url1 = Url.Action("Create", "Home",new { area=""});
            return View();
        }
    }
}

 

16.IActionConstraint

  一般應用程序不須要自定義 IActionConstraint,[HttpGet] 特性以及相似的特性實現 IActionConstraint 接口,以限制方法的執行。

  當兩個操做如出一轍,其中一個操做使用 IActionConstraint,老是認爲比沒有使用的操做更好,由於它被視爲更加具體,而且兩個操做均可以在匹配是被選中。(沒有使用的操做會匹配任何HTTP謂詞)

  概念上,IActionConstraint 是重載的一種形式,但不是使用相同名稱的重載,它是匹配相同URL操做的重載。特性路由也使用 IActionConstraint ,而且能夠致使不一樣控制器的操做都被視爲候選操做。

  實現 IActionConstraint 最簡單的方式是建立一個類派生自 System.Attribute ,而且將它放置到操做和控制器上。MVC會自動發現任何做爲特性被應用的 IActionConstraint。你可使用程序模型來應用約束,多是最靈活的方法,由於它容許對它們如何被應用進行元編程。

  一個例子,一個約束選擇一個基於來自路由數據的 country code 操做:

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);
        }
    }

  Accept 方法返回true,表示當country路由值匹配時操做時匹配的。這個和 RouteValueAttribute 不一樣,由於它容許回退到一個非特性操做。這個例子展現了若是定義一個 en-US 操做,而後國家代碼是 fr-FR,則會回退到一個更通用的控制器,這個控制器沒有應用 [CountrySpecific(...)] 。

  Order屬性和 [HttpGet] 特性中的Order屬性同樣,用來決定運行順序。

相關文章
相關標籤/搜索