Loading

Asp.Net Web API(五)

         路由就是Web API如何把URI匹配到一个Action的描述。Web API支持一种新的路由类型,叫做属性路由,顾名思义,属性路由是用属性来创建路由。在Web API中属性路由可以更好的控制URI。能更容易的创建资源阶层的URIs

         较早的基于公约的路由风格是全面被支持的,事实上,你能够在项目中联合使用这两种技术

为什么使用属性路由

         以前的Web  API版本使用是基于公约的路由。在该类型的路由中,你可以一个或多个被参数化字符串的模板。当Web APi框架接收到一个请求时,它匹配一个URI到路由模板

         基于公约的路由的一个优势就是,这个模板被定义在一个单独的地方。这个路由规则一致的被应用于所有的Controller。不幸的是,基于公约的路由是很难支持确切的URI模式,而这个确切的URI模式在Restful APIs中时很普遍的。例如,资源经常包含子资源:客户下了订单,电影有演员,书有作者等。它是很自然的创建这些URI来反应这些关系:

/customers/1/orders

         这种类型的URI时基于公约的路由下时比较难实现的。尽管它能做到,但是如果你有很多控制器或很多资源类型这种结果不是很好的被扩展。

          对于属性路由,它时很容易的为这个URI定义一个路由。你可以简单的添加一个属性到控制器的Action上:

[Route("customers/{customerId}/orders")]
  public IEnumerable<Order> GetOrdersByCustomer(int customerId)
   {
            return Orders.Where(y => y.CustomerID == customerId);
  }

      这个属性路由使它更容易的其它URI模式。

  API版本控制

       在下面例子中,“api/v1/product”相对于“api/v2/product”将被路由到不同的控制器

/api/v1/product
/api/v2/product

   重载URI片段

          在下面例子,“1”是一个阶数,而“pending”被映射到集合

/orders/1
/orders/pending

  多个参数类型

            在下面例子中,“1”是一个阶数。而“2017/07/07”被指定为一个日期

 

/orders/1
/orders/2017/07/07

启用属性路由

    要启动属性路由,在配置期间需要调用MapHttpAttributeRoutes。这个扩展方法被定义在System.Web.HttpConfigurationExtensions类中

 1 public static class WebApiConfig
 2 {
 3         public static void Register(HttpConfiguration config)
 4         {
 5             // Web API 配置和服务
 6 
 7             //启用属性路由
 8             config.MapHttpAttributeRoutes();
 9 
10         }
11 }

    你可以将属性与基于公约的路由一起使用。为了定义基于公约的路由,需要调用MapHttpRoute的方法

 

 public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            //启用属性路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }

添加路由属性

     下面一个是使用属性定义的路由示例

 1 namespace WebAPI.Demo.Controllers
 2 {
 3     public class CustomersController : ApiController
 4     {
 5         IList<Order> Orders = new List<Order>
 6         {
 7             new Order{ ID=1,OrderName="S1001",CustomerID=1},
 8               new Order{ ID=2,OrderName="S1002",CustomerID=2},
 9                 new Order{ ID=3,OrderName="S1003",CustomerID=1}
10         };
11         [Route("customers/{customerId}/orders")]
12         public IEnumerable<Order> GetOrdersByCustomer(int customerId)
13         {
14             return Orders.Where(y => y.CustomerID == customerId);
15         }
16     }
17 }

这个[Route]属性定义了一个HTTP Get方法,这个字符串“customers/{customerId}/orders”是路由的URI模板。在路由模板中的“{customerId}”参数匹配了在方法中的customerId参数的名称。

这个路由可以有多个参数

[Route("customers/{customerId}/orders/{orderId}")]
 public IEnumerable<Order> GetOrders(int customerId,int orderId)
 {
            return Orders.Where(y => y.CustomerID == customerId&&y.ID==orderId);
 }

 

 

   任何没有路由属性的控制器方法将使用基于公约的路由。这种方式,你可以结合两种方式在同一个项目中。

路由前缀

通常情况下,在同一个控制器中的所有路由以相同的前缀开头。例如刚才那两个方法

 [Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId)
{
            return Orders.Where(y => y.CustomerID == customerId);
}
[Route("customers/{customerId}/orders/{orderId}")]
public IEnumerable<Order> GetOrders(int customerId,int orderId)
{
            return Orders.Where(y => y.CustomerID == customerId&&y.ID==orderId);
}

对于整个控制器你能通过一个[RoutePerfix]属性来设置一个公共的前缀:

namespace WebAPI.Demo.Controllers
{
    [RoutePrefix("customers")]
    public class CustomersController : ApiController
    {
        IList<Order> Orders = new List<Order>
        {
            new Order{ ID=1,OrderName="S1001",CustomerID=1},
              new Order{ ID=2,OrderName="S1002",CustomerID=2},
                new Order{ ID=3,OrderName="S1003",CustomerID=1}
        };
        [Route("{customerId}/orders")]
        public IEnumerable<Order> GetOrdersByCustomer(int customerId)
        {
            return Orders.Where(y => y.CustomerID == customerId);
        }
        [Route("{customerId}/orders/{orderId}")]
        public IEnumerable<Order> GetOrders(int customerId,int orderId)
        {
            return Orders.Where(y => y.CustomerID == customerId&&y.ID==orderId);
        }
    }
}

  在方法属性上可以用一个“~”符号重写路由前缀

[Route("~/api/customers/{customerId}/orders/{orderId}")]
public IEnumerable<Order> GetOrders(int customerId,int orderId)
{
            return Orders.Where(y => y.CustomerID == customerId&&y.ID==orderId);
}

路由前缀可以包含参数:

[RoutePrefix("customers/{customerId}")]
public class CustomersController : ApiController
{
  //====
}

路由约束

  路由约束可以让你在路由模板中限制参数被匹配。常规的语法是:“{parameter:constraint}”,例如:

[Route("{customerId:int}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId)
{
            return Orders.Where(y => y.CustomerID == customerId);
}
[Route("{name}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(String name)
{
     return Orders.Where(y => y.OrderName == name);
}

  如果URI的“id”片段是一个Integer类型,那么第一个路由就会被匹配,否则第二个路由就被匹配

  下面是被支持的约束列表

 ConStraint Description Example
alpha 匹配大小写拉丁字母(a-z,A-Z) {x:alpha}
bool 匹配布尔值 {x:bool}
datetime 匹配时间值 {x:datetime}
decimal 匹配decimal值 {x:decimal}
double 匹配一个double值 {x:double}
float 匹配一个float值 {x:float}
guid 匹配一个GUID值 {x:guid}
int 匹配一个int值 {x:int}
length 在指定长度范围内或在指定范围内的字符串宽度

{x:length(6)}

{x:length(1,7)}

long 匹配一个long类型值 {x:long}
max 匹配一个具有最大值的整数 {x:max(10)}
min 匹配一个具有最小值的整数 {x:min(10)}
maxlength 匹配一个最大长度的字符串 {x:maxlength(10)}
minlength 匹配一个最小长度的字符串 {x:minlength(10)}
range 匹配一个范围之间的整数 {x:range(10,50)}
regex 匹配正则表达式 {x:(^\d{3}-\d{3}$)}

 

   注意一些限制,例如“min”,带参数在括号里。可以应用多个约束参数,用冒号分割

[Route("{customerId:int:min(1)}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId)
{
            return Orders.Where(y => y.CustomerID == customerId);
}

 可选的URI参数和默认值

    可以通过添加一个问号标记路由参数使成为一个可选的URI参数。如果一个路由参数是可选的,那么就必须为这个参数定义一个默认值

[Route("{customers/customerId?}")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId = 1)
{
            return Orders.Where(y => y.CustomerID == customerId);
}

 这个Action可以使用“customers/1”和“customers”同时访问

   也可以在路由模板定义默认值

 [Route("{customerId=1}")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId)
{
            return Orders.Where(y => y.CustomerID == customerId);
 }

路由名称

     在Web API中,每个路由都有名称.路由名称被用于生成连接。能在HTTP响应中包含一个连接。

    指定这个路由名称,在这个属性上设置Name属性。

[Route("{customerId}",Name ="GetOrder")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId=2)
{
            return Orders.Where(y => y.CustomerID == customerId);
}

如果不设置Name属性,Web API产生这个名字。这个默认的路由名称是ControllerName.ActionName。在前面例子,对于这个GetOrdersByCustomer方法的默认路由名称将是“Customers.GetOrdersByCustomer”。对于同一个动作名称如果控制器有多重的属性路由,一个后缀将被添加,例如:“Customers/GetOrder”

路由顺序

    当一个框架试图将一个URI匹配到路由的时候。它会在特定的顺序下评估这些路由,为了指定这个顺序,在路由属性上设置Order属性,降低的值将首先被评估,默认顺序为0

         文本字段

         带有约束的路由参数

        不带有约束的路由参数

        带有约束的通配符路由参数

         不带有约束的通配符路由参数

[RoutePrefix("orders")]
public class OrdersController : ApiController
{
    [Route("{id:int}")] // constrained parameterpublic HttpResponseMessage Get(int id) { ... }

    [Route("details")]  // literalpublic HttpResponseMessage GetDetails() { ... }

    [Route("pending", RouteOrder = 1)]
    public HttpResponseMessage GetPending() { ... }

    [Route("{customerName}")]  // unconstrained parameterpublic HttpResponseMessage GetByCustomer(string customerName) { ... }

    [Route("{*date:datetime}")]  // wildcardpublic HttpResponseMessage Get(DateTime date) { ... }
}

这些路由进行排序,如下所示

1.orders/details
2.orders/{id}
3.orders/{customerName}
4.orders/{*date}
5.orders/pending

注意"details"是一个文本片段出现在"{id}"之前,但是"pending"将出现在最后因为这个RouteOrder 属性是1。(这个例子假定没有客户命名为"details"或者"pending")。总之,试图去避免模糊不清的路由。在这个例子中,GetByCustomer 的一个较好的路由模版是"customers/{customerName}")。

posted @ 2017-11-04 15:06  莫问今朝乄  阅读(242)  评论(0)    收藏  举报