Web API 2 中的特性路由
Web API 2 中的特性路由
灵儿灵 发表于6个月前
Web API 2 中的特性路由
  • 发表于 6个月前
  • 阅读 29
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

摘要: 这是一篇翻译文。原文地址为 https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-constraints

Atrtribute Routing in ASP.NET Web API 2

> ASP.NET Web API 2 中的特性路由 (作者:Mike Wasson)

路由是指Web API怎么根据URI匹配到一个操作。Web API 2 支持一个新的路由类型,叫做特性路由。顾名思义,特性路由使用特性来定义路由。特性路由使你能够更好地控制你的Web API中的URI。例如,你可以轻松地创建描述资源结构的URI。

更早的路由方式(成为约定式路由)仍然完全受支持。实际上,你可以在同一个项目中组合这两种技术。

本主题演示如何启用特性路由并描述特性路由的各种选项。有关使用特性路由的端到端教程(end-to-end 也许是手把手的意思),请参阅Create a REST API with Attribute Routing in Web API 2

先决条件

Visual Studio 2013 或者 Visual Studio Express 2013

使用 NuGet 包管理工具安装必要的包。从 visual studio 的“工具”菜单中选择“NuGet 包管理器”,然后选择“程序包管理器控制台”。在“包管理器”控制台窗口中输入以下命令:

Install-Package Microsoft.AspNet.WebApi.WebHost

为什么使用特性路由?

Web API 的第一个版本使用约定式路由。在这种类型的路由中,你定义了一个或者多个路由模版,他们基本上是参数化的字符串。当框架接收一个请求,它将 URI 和路由模板匹配。(有关约定式路由的详细信息,请参阅Routing in ASP.NET Web API

约定式路由的一个优点是可以集中定义模版,并且路由规则可以应用来所有控制器上。不幸的是,约定式路由很难支持REST风格的api中常见的某些uri模式。例如,资源通常包含子资源:客户有订单,电影有演员,书籍有作者,等等。创建反映这些关系的URI是很自然的。

/customers/1/orders

这种类型的URI很难使用约定式路由来实现。尽管可以做到,但是如果你有许多控制器或者资源类型,结果就是不易扩展。

使用特性路由,定义这个URI的路由就变得很简单。你只需要给控制器操作添加一个特性:

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

这里还有一些特性路由容易实现的部分:

API 版本

在这个例子里,"/api/v1/products" 将会被路由到一个和 "/api/v2/products" 不同的控制器。

/api/v1/products

/api/v2/pruducts

重载 URI 段

在这个例子里,"1" 是一个排序数字,但是 "pending" 映射到了一个控制器。

/orders/1

/orders/pending

多参数类型

在这个例子里,“1”是一个排序数字,但是“2013/06/16”是一个日期。

/orders/1

/orders/2013/06/16

启用特性路由

启用特性路由,需要在配置阶段调用MapHttpAttributeRoutes。这个扩展方法定义在System.Web.Http.httpConfigurationExtensions类中。

using System.Web.Http;
namespace WebApplication
{
  public static class WebApiConfig
  {
    public  static void Register(HttpConfiguration config)

    {
      config.MapHttpAttributeRoute();	//Web API routes
    }
  }
}

特性路由可以和约定式路由一起使用。要定义约定式路由,调用MapHttpRoute方法。

public static class WebApiConfig
{
  public static void Register(HttpCopnfiguration config)
  {
    // Attribute routing
    config.MapHttpAttributeRoute();
    // Convent-based routing
    config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{id}",
      defaults: new { id = RouteParameter.Optional }
    );
  }
}

关于配置Web API的更多信息,请参照 Configuring ASP.NET Web API 2

注意:从Web API 1迁移

在 Web API 2 之前,Web API 项目模板会生成如下代码:

protected void Application_Start()
{
    // WARNING - Not compatible with attribute routing.
    WebApiConfig.Register(GlobalConfiguration.Configuration);
}

如果已经启用了特性路由,这段代码会抛出异常。如果你准备升级当前的 Web API 项目以便使用特性路由,请确保将此配置代码更新为以下内容:

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

> 更多信息,查看 Configuring Web API with ASP.NET Hostion

添加路由特性

这里是一个使用特性定义路由的例子:

public class OrdersController : ApiController
{
	[Route("customers/{customerId}/orders")]
  	[HttpGet]
  	public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }
}

字符串 “customers/{customerId}/orders” 是一个路由的URI模板。Web API 会尝试为请求的URI匹配模板。在这个例子里,“customers” 和 “orders” 是文本段,而 “{customerId}” 是一个变量参数。下面这些 URI 将匹配到这个模板:

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

你可以通过使用约束来限制匹配,本文后面将对此进行说明。

注意,路由模板中的 "{customerId}" 参数与方法中的 customerId 参数的名称相匹配。当 Web API 调用控制器操作时, 它会试图绑定路由参数。例如,如果 URI 是 http://example.com/cuestomers/1/orders , Web API 尝试给操作的 customerId 参数绑定值 “1”。

一个 URI 模板可以有多个参数:

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

任何没有路由特性的控制器方法都使用约定式路由,这样你就可以在同一项目中组合使用两种类型的路由。

HTTP 方法

Web API 选择操作时,也要基于请求的 Http 方法(Get,Post 等等)。默认情况下,Web API 不分区大小写地查找名称开头匹配的控制器方法。例如,一个名为 PutCustomers 的控制器方法与一个 Http Put 请求匹配。你可以使用下面任意一个特性修饰方法以覆盖此约定:

  • [HttpDelete]
  • [HttpGet]
  • [HttpHead]
  • [HttpOptions]
  • [HttpPatch]
  • [HttpPost]
  • [HttpPut]

接下来这个例子,将 CreateBook 方法映射到了 HTTP POST 请求。

[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }

对于其他的 HTTP 方法(包括非标准方法),使用 AcceptVerbs 特性,该特性采用 HTTP 方法列表。

// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() {}

路由前缀

通常,一个控制器的所有路由都有着相同的前缀。例如:

public class BooksController : ApiController
{
	[Route("api/books")]
  	public IEnumerable<Book> GetBook() { ... }
  
  	[Route("api/books/{id:int}")]
  	public Book GetBook(int id) { ... }
  
  	[Route("api/books")]
  	[HttpPost]
  	public HttpResponseMessage CreateBook(Book book) { ... }
}

你可以使用 [RoutePrefix] 特性给一个控制器设置一个公共前缀:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
  	// GET api/books
  	[Route("")]
  	public IEnumerable<Book> 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")]
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) { ... }
}

路由约束

路由约束使你能够约束模板中的参数如何进行匹配。一般语法是 “{parameter:constraint}”。例如:

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

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

这里,如果 URI 的“id”段是个整数就会选择第一个路由。否则,就会选择第二个路由。

下面的表格列举了被支持的约束:

ConstraintDescriptionExample
alpha匹配大写或小写拉丁字母{x:alpha}
bool匹配Boolean值{x:bool}
datetime匹配DateTime值{x:datetime}
decimal匹配decimal值{x:decimal}
double匹配64位浮点值{x:double}
float匹配32位浮点值{x:float}
guid匹配GUID值{x:guid}
int匹配32位整数{x:int}
length匹配指定长度或长度区间的字符串{x:length(6)} {x:length(1,20)}
long匹配64位整数{x:long}
max匹配有上限的整数{x:max(10)}
maxlength匹配有最大长度的字符串{x:maxlength(10)}
min匹配有下限的整数{x:min(10)}
minlength匹配有最小长度的字符串{x:minlength(10)}
rang匹配一个区间的整数{x:range(10,50)}
regex正则匹配{x:regax(^\d{3}-\d{3}-\d{4}$)}

注意有些约束(例如 "min" )从圆括号里取得实参。你可以对一个形式参数应用多个约束,需要使用冒号进行分割。

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

自定义路由约束

你可以通过实现 IHttpRouteConstraint 接口来创建自定义的路由约束。例如,下面的约束就限制参数只能接收非零整数。

public class NonZeroConstraint : IHttpRouteConstraint
{
  	public bool Match(HttpRequestMessage request, IHttpRoute rout, 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);
    }
}

现在可以在路由中应用这个约束:

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

还可以通过实现 IInlineConstraintResolver 接口来替换整个 DefaultInlineConstraintResolver 类。这样做将会替换所有的内置约束,除非你的 IInlineContraintResolver 专门添加了它们。

可选的 URI 参数 与默认值

可以通过向路由参数添加一个问号来使 URI 参数可选。如果路由参数是可选的,你就必须为方法参数提供默认值。

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?}" ,默认值1033被直接赋给了方法参数,所以参数的值将是确定的。
  • 第二个例子里使用了 "{lcid=1033}" ,默认值“1033”是在模型绑定过程中设置。默认的绑定器将会把字符串“1033”转换为数值1033。然而,你可以插入自定义模型绑定器,这可能会做一些不同的事情。

(大多数情况下,这两种方式都是等效的。除非,你在管线中使用了自定义的模型绑定器。)

路由名称

在 Web API 里,每个路由都有一个名字。路由名称对于生成链接很有用,所以可以在 HTTP 响应中包含链接。

若要指定路由名称,请在特性上设置 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;
    }
}

路由顺序

当框架试图匹配 URI 和路由时,它将按特定顺序计算路由。若要指定顺序,请在路由特性上设置 RouteOrder 属性。较低的值会被优先考虑。 默认顺序值是零。

下面是确定整体顺序的算法:

  1. 比较路由特性的 RouteOrder 属性
  2. 查看路由模板中的每个 URI 段。对于每个部分,顺序如下:
    1. 文本段
    2. 带约束的路由参数
    3. 无约束的路由参数
    4. 带约束的通配符参数段
    5. 无约束的通配符参数段
  3. 平局的情况下,会对路由模板进行不区分大小写的比较,进而决定顺序

这里是一个例子,假设你定义了以下控制器:

[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

注意 “details” 是文本段,排在 “{id}” 之前。但 “pending” 的 RouteOrder 属性值为1,所以排在最后。(本例假定没有客户的名字是 “details” 或者 “pending”。通常,尽量避免歧义的路由。在本例中, GetByCustomer 的更好的路由模板是 "customers/{customerName}”)

共有 人打赏支持
粉丝 0
博文 10
码字总数 9249
×
灵儿灵
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: