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属性同样,用来决定运行顺序。

相关文章
相关标签/搜索