ASP.NET Web API路由,简单来说,就是把客户端请求映射到对应的Action上的过程。在"ASP.NET Web API实践系列03,路由模版, 路由惯例, 路由设置"一文中,体验了通过模版、惯例、HTTP方法来设置路由,这种做法的好处是把路由模版统一放在了App_Start文件夹下的WebApiConfig类中,方便管理,但缺点是不够灵活。 REST把一切都看成资源,有时候,一个资源连带子资源,比如Customer和Orders密切关联,我们可能希望输入这样的请求:customers/1/orders,但仅仅凭借惯例,很难实现这种路由。而实际上,ASP.NET Web API为我们准备了Route特性,该特性可以直接打到Action上,使用非常灵活、直观。 下面就在ASP.NET MVC4下来体验Route特性的使用方法。 允许Route特性 首先需要在WebApiConfig中设置。using System.Web.Http;namespace MyRoutingAttributes4{ public static class WebApiConfig { public static void Register(HttpConfiguration config) { //设置特性路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 取消注释下面的代码行可对具有 IQueryable 或 IQueryable返回类型的操作启用查询支持。 // 若要避免处理意外查询或恶意查询,请使用 QueryableAttribute 上的验证设置来验证传入查询。 // 有关详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=279712。 //config.EnableQuerySupport(); // 若要在应用程序中禁用跟踪,请注释掉或删除以下代码行 // 有关详细信息,请参阅: http://www.asp.net/web-api config.EnableSystemDiagnosticsTracing(); } }} 以上的MapHttpAttributeRoutes方法只在ASP.NET Web API较新的版本中才有,如果你的版本比较低,可以通过"NuGet程序包管理器控制台"卸载旧版本,安装最新版本。Uninstall-Package microsoft.aspnet.webapi –Forceinstall-package microsoft.aspnet.webapi 接下来,在Global.asax中,需要把原先注册WebApiConfig的方式注释掉,采纳新的方式,如下: public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); //WebApiConfig.Register(GlobalConfiguration.Configuration); //Web API,启动特性路由 GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } } 这时候运行项目可能会报如下错误:1 这是因为在下载使用ASP.NET Web API最新版本的时候,顺带下载了一个最新版本的icrosoft.AspNet.WebApi.HelpPage。可以把最新版的HelpPage卸载掉,再下载相对老的版本。Uninstall-Package Microsoft.AspNet.WebApi.HelpPage –ForceInstall-Package Microsoft.AspNet.WebApi.HelpPage -Pre 使用Route特性 创建一个Cusomter类。namespace MyRoutingAttributes4.Models{ public class Customer { public int Id { get; set; } public string Name { get; set; } }} 创建一个Order类。namespace MyRoutingAttributes4.Models{ public class Order { public int Id { get; set; } public decimal Total { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } }} 创建一个Database类,用来获取Order集合。using System.Collections.Generic;using System.Linq;using MyRoutingAttributes4.Models;namespace MyRoutingAttributes4{ public class Database { public static IEnumerable GetOrdersByCustomerId(int customerId) { return GetOrders().Where(o => o.CustomerId == customerId); } private static IEnumerable GetOrders() { Customer cus1 = new Customer() { Id = 1, Name = "张三" }; Customer cus2 = new Customer() { Id = 2, Name = "李四" }; List orders = new List () { new Order(){Id = 1, Total = 80M, CustomerId = 1, Customer = cus1}, new Order(){Id = 2, Total = 100M, CustomerId = 1, Customer = cus1}, new Order(){Id = 3, Total = 120M, CustomerId = 2, Customer = cus2} }; return orders; } }} 创建一个空的API控制器,编写如下:using System.Collections.Generic;using System.Web.Http;using MyRoutingAttributes4.Models;namespace MyRoutingAttributes4.Controllers{ public class OrdersController : ApiController { [Route("customers/{customerId}/orders")] [HttpGet] public IEnumerable FindOrdersByCustomer(int customerId) { return Database.GetOrdersByCustomerId(customerId); } }}在浏览器中输入如下:2 如果你使用的是ASP.NET MVC4进行开发,在程序第一次运行的时候,可能会报如下错误:[A]System.Web.WebPages.Razor.Configuration.HostSection 无法强制转换为 [B]System.Web.WebPages.Razor.Configuration.HostSection。类型 A 源自“System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”(在上下文“Default”中的“C:/Windows/Microsoft.Net/assembly/GAC_MSIL/System.Web.WebPages.Razor/v4.0_2.0.0.0__31bf3856ad364e35/System.Web.WebPages.Razor.dll”位置处)。类型 B 源自“System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”(在上下文“Default”中的“C:/Windows/Microsoft.NET/Framework/v4.0.30319/Temporary ASP.NET Files/vs/feb7ce97/a525d58a/asse 这是因为,在下载最新版本的ASP.NET Web API的时候,用到了Razor的最新版本。需要在根目录下的Web.config中作如下配置: 使用RoutePrefix特性 如果想给某个API控制器中的所有Action加上一个前缀,可把RoutePrefix特性打在API控制器上。比如我们希望是这样的格式:http://localhost/api/customers/1/orders 这样来修改OrdersController。using System.Collections.Generic;using System.Web.Http;using MyRoutingAttributes4.Models;namespace MyRoutingAttributes4.Controllers{ [RoutePrefix("api")] public class OrdersController : ApiController { [Route("customers/{customerId}/orders")] [HttpGet] public IEnumerable ...... FindOrdersByCustomer(int customerId) { return Database.GetOrdersByCustomerId(customerId); } }}3 还可以在Route特性中使用~来重写Action的前缀规则。using System.Collections.Generic;using System.Web.Http;using MyRoutingAttributes4.Models;namespace MyRoutingAttributes4.Controllers{ [RoutePrefix("api")] public class OrdersController : ApiController { [Route("~/myapi/customers/{customerId:int}/orders")] [HttpGet] public IEnumerable FindOrdersByCustomer(int customerId) { return Database.GetOrdersByCustomerId(customerId); } }}4 RoutePrefix特性定义的前缀还可以带参数变量: [RoutePrefix("api/{customerId}")] public class OrdersController : ApiController 路由约束 可以通过"{参数变量名称:约束}"来约束路由中的参数变量。[Route("users/{id:int}"]public User GetUserById(int id) { ... }[Route("users/{name}"]public User GetUserByName(string name) { ... }以上,如果片段变量id为int类型,就路由到第一个Action,如果不是,路由到第二个Action。 ASP.NET Web API内置约束包括:{x:alpha} 约束大小写英文字母{x:bool}{x:datetime}{x:decimal}{x:double}{x:float}{x:guid}{x:int}{x:length(6)}{x:length(1,20)} 约束长度范围{x:long}{x:maxlength(10)}{x:min(10)}{x:range(10,50)}{x:regex(正则表达式)} 可以为一个参数变量同时设置多个约束:[Route("api/{id:int:min(1)}")] 实现IHttpRouteConstraint接口,可自定义约束规则。实现一个不能为0的约束。public class NonZeroConstraint : IHttpRouteConstraint{ public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary 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; }} 在App_Start文件夹中的WebApiConfig中注册自定义约束。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}")] 可选参数及其默认值 如果一个路由参数变量是可选的,同时必须给该参数一个默认值。[Route("api/{id:int?}")]public IEnumerable Get(int id = 8){}在约束后面加?,表示可选,在方法参数中给id设置默认值。 给路由设置名称 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; }} 路由优先顺序 Route特性设置的路由优先顺序是根据惯例和RouteOrder属性来确定的。 惯例是:1、静态片段变量2、带约束的片段变量3、不带约束的片段变量4、带约束的通配符片段变量5、不带约束的通配符片段变量 RouteOrder属性的默认值是0,属性值越小,排在越前面。 [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) { ... }}以上,路由的优先顺序是:orders/details 静态片段变量,RouteOrder属性值为0orders/{id} 带约束的片段变量,RouteOrder属性值为0orders/{customerName} 不带约束的片段变量,RouteOrder属性值为0orders/{*date} 带约束的通配符片段变量,RouteOrder属性值为0orders/pending RouteOrder属性值为1 装载 仅供保存学习