zoukankan      html  css  js  c++  java
  • Web API-属性路由

            路由(Routing)就是Web API如何将一个URI匹配到一个action的过程。Web API 2 支持一个新的路由方式-属性路由(attribute routing)。顾名思义,属性路由使用标识属性去定义路由,属性路由可以使你在Web API中更方便的定制你的URIs。例如,你可以很容易的创建描述不同层次资源的URIs。

            前面将的路由风格,叫做基于约定的路由(convention-based)在Web API中也完全支持,实际上,你能够在项目中同时使用两种路由技术。

            这篇文章主要演示如何在项目中启用属性路由(attribute routing)和描述各种属性路由的使用方式,主要内容:

        --1、为什么需要属性路由

        --2、允许属性路由

        --3、添加路由属性

        --4、路由前缀

        --5、路由约束

        --6、可选择的URI参数以及默认值

        --7、路由名称

        --8、路由顺序

    1、为什么需要属性路由

            第一个发布版的Web API使用 convention-based routing(基于约定的)。在这种风格的路由中,你定义一个或者多个路由模版,基本上是参数化的字符串。当框架接收到一个请求,框架将URI与路由模版进行匹配。

            convention-based路由的一个优势是:路由模版定义在一个文件中,路由规则被应用到所以的控制器上。但是convention-based方式的路由风格,要实现支持像RESTful APIs中常见的特定的URI模式比较麻烦。例如,资源经常包含子资源:Customers have orders, movies have actors, books have authors,等等。创建能够反映这种关系的URI是必须的:/customers/1/orders

    使用convention-based 路由很难去实现这种风格的URI(This type of URI is difficult to create using convention-based routing),尽管可以实现,但结果不佳,如果你有很多控制器和资源类型。

            使用属性路由,你可以很容易的为这样的URI定义一个路由,只需要给一个控制器的action添加一个标识属性:

    [Route("customers/{customerId}/orders")]
    public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

    还有一些情况下使用属性路由将非常方便:

    --API 版本(API versioning)

    /api/v1/products
    /api/v2/products

    解释:假设要控制请求访问不同版本的api,如果是convention-based风格的路由,意味着这里的v1 ,v2被定义为参数,那么必须在action中接收这个参数,然后在action中才能判断出版本(貌似这个时候知道版本用处不大了)我们要实现的是v1 ,v2 访问的是不同的控制器。那么使用属性路由很容易实现这一点,比如一个方法只在v2中有:

    [Route("/api/v2/products")]
    public Ienumerable<Product> GetAll(){}

    (如何实现版本控制,细节可能要在项目中去感受)

    --重载 URI片段(Overloaded URI segments)

    /orders/1
    /orders/pending

    这个例子中"1"是一个订单编号,但是"pending"对应了一个订单集合。

    --复杂的参数类型

    /orders/1
    /orders/2013/06/16

    这个例子中"1" 是一个订单编号,但是"2013/06/16"指定了一个日期。

    2、允许属性路由

    Global.asax文件

    protected void Application_Start()
    {
        // Pass a delegate to the Configure method.
        GlobalConfiguration.Configure(WebApiConfig.Register);
    }

    WebApiConfig类

    public static void Register(HttpConfiguration config)
    {
         // Web API routes
         config.MapHttpAttributeRoutes();
    
         // Other Web API configuration not shown.
    }

    config.MapHttpAttributeRoutes()方法启用属性路由。

    3、添加路由标识属性

    一个例子:

    public class OrdersController : ApiController
    {
        [Route("customers/{customerId}/orders")]
        [HttpGet]
        public IEnumerable&lt;Order&gt; FindOrdersByCustomer(int customerId) { ... }
    }

    字符串"customers/{customerId}/orders"是路由的URI模版,Web API尝试将请求URI与这个模版匹配,这个例子中"coustomers" 和 "orders" 是纯文本片段,{customerId}是占位符,下面的URI都会与这个模版匹配:

    • http://localhost/customers/1/orders
    • http://localhost/customers/bob/orders
    • http://localhost/customers/1234-5678/orders

    可以使用约束限制{customerId}匹配的范围,下面会讲到。

    注意在路由模版中的{customerId}参数,和action方法中的customerId参数匹配,当Web API调用控制器的action,将进行参数绑定,例如如果URI是: http://example.com/customers/1/orders  Web API会将值“1”传递的action方法的customerId参数。一个URI模版可以有多个占位符参数:

    [Route("customers/{customerId}/orders/{orderId}")]
    public Order GetOrderByCustomer(int customerId, int orderId) { ... }

    --HTTP请求方式

    默认情况下Action方法使用方法开头用请求方式名称的方式两匹配不同的HTTP请求(忽略大小写的),可以通过添加标识属性来指定某一个action方法匹配的HTTP请求方式:

    [HttpDelete]

    [HttpGet]

    [HttpHead]

    [HttpOptions]

    [HttpPatch]

    [HttpPost]

    [HttpPut]

    例如:

    [Route("api/books")]
    [HttpPost]
    public HttpResponseMessage CreateBook(Book book) { ... }// WebDAV method
    [Route("api/books")]
    [AcceptVerbs("MKCOL")]
    public void MakeCollection() { }
    
    

    4、路由前缀

            很多时候一个控制器下的action路由模版的前面部分都是相同的,为了不重复书写,可以这样:

    [RoutePrefix("api/books")]
    public class BooksController : ApiController
    {
        // GET api/books
        [Route("")]
        public IEnumerable&lt;Book&gt; Get() { ... }
    
        // GET api/books/5
        [Route("{id:int}")]
        public Book Get(int id) { ... }
    
        // POST api/books
        [Route("")]
        public HttpResponseMessage Post(Book book) { ... }
    }

    [RoutePrefix("api/books")] 给控制器下的所有action方法增加了路由模版前缀。

    如果有特殊情况,你可以在action方法的标识属性中使用浪符号(~)来覆盖指定的统一的路由前缀:

    [RoutePrefix("api/books")]
    public class BooksController : ApiController
    {
        // GET /api/authors/1/books
        [Route("~/api/authors/{authorId:int}/books")]
        public IEnumerable<Book> GetByAuthor(int authorId) { ... }
    
        // ...
    }

    路由前缀也可以包含占位符参数:

    [RoutePrefix("customers/{customerId}")]
    public class OrdersController : ApiController
    {
        // GET customers/1/orders
        [Route("orders")]
        public IEnumerable<Order> Get(int customerId) { ... }
    }

    5、路由约束

    路由约束允许你限制路由模版中占位符参数的匹配范围,基本语法是{parameter:constraint}。例如:

    [Route("users/{id:int}"]
    public User GetUserById(int id) { ... }
    
    [Route("users/{name}"]
    public User GetUserByName(string name) { ... }

    GetUserById 方法只匹配id参数为整型的URI

    支持的约束条件:

    Constraint

    Description

    Example

    alpha

    Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z)

    {x:alpha}

    bool

    Matches a Boolean value.

    {x:bool}

    datetime

    Matches a DateTime value.

    {x:datetime}

    decimal

    Matches a decimal value.

    {x:decimal}

    double

    Matches a 64-bit floating-point value.

    {x:double}

    float

    Matches a 32-bit floating-point value.

    {x:float}

    guid

    Matches a GUID value.

    {x:guid}

    int

    Matches a 32-bit integer value.

    {x:int}

    length

    Matches a string with the specified length or within a specified range of lengths.

    {x:length(6)}
    {x:length(1,20)}

    long

    Matches a 64-bit integer value.

    {x:long}

    max

    Matches an integer with a maximum value.

    {x:max(10)}

    maxlength

    Matches a string with a maximum length.

    {x:maxlength(10)}

    min

    Matches an integer with a minimum value.

    {x:min(10)}

    minlength

    Matches a string with a minimum length.

    {x:minlength(10)}

    range

    Matches an integer within a range of values.

    {x:range(10,50)}

    regex

    Matches a regular expression.

    {x:regex(^d{3}-d{3}-d{4}$)}

    有些约束条件可以组合使用,比如"min":必须为整型且大于或等于1

    [Route("users/{id:int:min(1)}")]
    public User GetUserById(int id) { ... }

    自定义路由约束

    通过实现IHttpRouteConstraint接口来创建自定义的路由约束,例如下面的代码定义了一个“不能为0”的整数约束。

    public class NonZeroConstraint : IHttpRouteConstraint
    {
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, 
            IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            object value;
            if (values.TryGetValue(parameterName, out value) && value != null)
            {
                long longValue;
                if (value is long)
                {
                    longValue = (long)value;
                    return longValue != 0;
                }
    
                string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
                if (Int64.TryParse(valueString, NumberStyles.Integer, 
                    CultureInfo.InvariantCulture, out longValue))
                {
                    return longValue != 0;
                }
            }
            return false;
        }
    }

    下面代码展示如果注册自定义的约束:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var constraintResolver = new DefaultInlineConstraintResolver();
            constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));
    
            config.MapHttpAttributeRoutes(constraintResolver);
        }
    }

    现在就可以在路由中使用这个自定义的约束条件了:id必须为非0整数

    [Route("{id:nonzero}")]
    public HttpResponseMessage GetNonZero(int id) { ... }

    还可以通过实现IInlineConstraintResolver 接口的方式来覆盖所有的内置的约束。

    6、可选择的URI参数和默认值

    你可以通过给路由参数添加 问号"?”的方式来标识这个参数是可选的,如果路由模版中定义了可选参数,那么必须为action方法参数指定一个默认值(可选参数)。

    public class BooksController : ApiController
    {
        [Route("api/books/locale/{lcid:int?}")]
        public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
    }

    上面这个例子,

    /api/books/locale/1033 和 /api/books/locale 将返回同样的资源

    还可以在路由模版中直接指定默认值:

    public class BooksController : ApiController
    {
        [Route("api/books/locale/{lcid:int=1033}")]
        public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
    }

    上面两个例子功能基本相同,但也有细微的差别:

    --在第一个例子中“{lcid:int?}”,默认值直接在action方法的参数位置指定,所以action方法有一个确定类型的默认值。

    --第二个例子中“{lcid=1033}”,因为在路由模版中指定的默认值,那么需要经过模型绑定的过程,模型绑定过程会将“1033”从字符类型转换成数字类型,如何自定义了模型绑定方式,可能还有其他的不同的地方。

    两个例子的功能一般情况下都是相同的,除非自定义了模型绑定。

    7、路由名称

    在Web API中每一个路由项都有一个名称,路由名称在生成链接的时候非常有用,隐藏你可以在返回消息中包含一个有效的链接。

    使用Name 属性指定路由名称,下面的例子展示了如何定义路由名称,以及如何使用路由名称生成链接:

    public class BooksController : ApiController
    {
        [Route("api/books/{id}", Name="GetBookById")]
        public BookDto GetBook(int id) 
        {
            // Implementation not shown...
        }
    
        [Route("api/books")]
        public HttpResponseMessage Post(Book book)
        {
            // Validate and add book to database (not shown)
    
            var response = Request.CreateResponse(HttpStatusCode.Created);
    
            // Generate a link to the new book and set the Location header in the response.
            string uri = Url.Link("GetBookById", new { id = book.BookId });
            response.Headers.Location = new Uri(uri);
            return response;
        }
    }

    8、路由顺序

    当框架尝试去将一个URI匹配到一个路由时,会给路由进行排序,如果需要自定义顺序,可以在路由标识属性中使用RouteOrder 属性,较小的值排在前面,默认的排序值是0。

    排序是如何确定的:

    1.比较路由标识属性的RouteOrder属性值。

    2.查看路由模版中的每一个URI片段,对于每一个片段,按照下面的方式排序

        1-纯文本片段

        2-带约束条件的路由参数

        3-不带约束条件的路由参数

        4-带约束条件的通配符路由参数

        5不带约束条件的通配符路由参数

    3.In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.

    看例子:

    [RoutePrefix("orders")]
    public class OrdersController : ApiController
    {
        [Route("{id:int}")] // constrained parameter
        public HttpResponseMessage Get(int id) { ... }
    
        [Route("details")]  // literal
        public HttpResponseMessage GetDetails() { ... }
    
        [Route("pending", RouteOrder = 1)]
        public HttpResponseMessage GetPending() { ... }
    
        [Route("{customerName}")]  // unconstrained parameter
        public HttpResponseMessage GetByCustomer(string customerName) { ... }
    
        [Route("{*date:datetime}")]  // wildcard
        public HttpResponseMessage Get(DateTime date) { ... }
    }

    那么这些路由的排序如下:

    1、orders/details

    2、orders/{id}

    3、orders/{customerName}

    4、orders/{*date}

    5、orders/pending

    前面有讲过,在URI匹配路由模版时是从路由的排列顺序开始匹配,一旦匹配成功则会忽略后面的路由模版了。

  • 相关阅读:
    KVM WEB管理工具——WebVirtMgr(二)日常配置
    在阿里云上遇见更好的Oracle(四)
    Django源码分析之权限系统_擒贼先擒王
    Django源码分析之server
    Django源码分析之执行入口
    HDFS常用文件操作
    排查实时tail功能cpu占用过高问题
    ZooKeeper完全分布式安装与配置
    Hadoop2.5.2集群部署(完全分布式)
    构造器
  • 原文地址:https://www.cnblogs.com/zkun/p/4169746.html
Copyright © 2011-2022 走看看