問題
怎么樣限制路由中參數的值。
解決方案
ASP.NET WEB API 允許我們通過 IHttpRouteConstraint 接口設置路由約束。集中式路由和直接式路由都可以使用 IHttpRouteConstraint。
框架提供了 18 個接口,他提供了大部分類型的約束,例如,路由參數長度相關的約束,可以確保值都在定義范圍內,或者限制數據類型。當然也可以通過實現接口 IHttpRouteConstraint 來自定義約束邏輯。
工作原理
IHttpRoutConstraint 是一個 HTTP 路由約束接口(如代碼片段 3-11),并公開了一個簡單的方法 Match,這個方法需要五個參數,HttpRequestMessage 實例,IHttpRoute 實例,string 類型的 parameterName,Idictionary<string,object> 類型的路由 value,HttpRouteDirection 類型的 routeDirection,也是為了保證路由可以基于應用程序的邏輯被匹配到。
代碼片段 3-11 IHttpRouteConstraint 定義
public interface IHttpRouteConstraint { bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection); }
也可以通過使用 CompoundRouteConstraint 進行復合約束,需要通過構造函數添加到 IHttpRouteConstraint 集合中,如表格 3-1 所示,展示內建的約束和基本用法
表格 3-1. ASP.NET WEB API 中 IHttpRouteConstraint 可以用的約束
屬性路由,是通過 DefaultInlineConstraintResolver 來映射嵌入約束的版本和實際類型。當調用 MapHttpAtrributeRoute 的時候,ASP.NET WEB API 會使用解析器轉換嵌入約束為相關 IHttpRouteConstraint 實例。為了采用某些約束處理自定義約束,也可以修改 DefaultInlineConstraintResolver 或者自己實現全部的 IInineConstraintReslover 接口。不管怎么樣,都需要傳一個他的實例給方法 MapHttpAttributeRoute。
OptionalRouteConstraint 是被用來提供可選參數功能的,如上一篇 3-4 介紹的,還提供了常見的約束功能。如果路由參數不是 RouteParameter.Optional 的,OptionalRouteConstraint 就只會計算約束。
代碼演示
對于集中式路由,約束是作為 MapHttpRoute 方法的第三個參數傳進來的。與默認值類似,已經在 3-3 部分介紹過,這個參數的類型是 Idictionary<string,object>,但是,框架的設計也是可以傳匿名對象,這個方法簽名的實際類型就是一個簡單對象。約束參數的名稱必須和路由模板以及 Action 的簽名一致。
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "orders/{text}", constraints: new {text = new AlphaRouteConstraint()}, defaults: null );
使用集中式路由,也可以定義一個字符串的嵌入正則表達式,不必使用任何 IHttpRouteConstarint 接口。在下面的李子中,“id”就是一個數字約束嵌入正則表達式。
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "size/{id}", constraints: new {id = "\d+"}, defaults: null );
集中式路由也可以約束 HTTP 方法,只要通過一個預定義的 httpMethod 鍵并賦值 HttpMethodConstrtin 就可以。
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "size/{id}", constraints: new {httpMethod = new HttpMethodConstraint(HttpMethod.Get)}, defaults: null );
對于直接式路由,可以通過在參數加上冒號再加上約束條件。
[Route("orders/{text:alpha}")] public HttpResponseMessage Get(string text){}
對于復合路由的定義,集中式路由需要通過 CompoundRouteConstraint 轉換。
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "orders/{text}", constraints: new { text = new CompoundRouteConstraint(new List<IHttpRouteConstraint> {new AlphaRouteConstraint(), new MaxLengthRouteConstraint(5)} }, defaults: null );
對于屬性路由,可以通過冒號將不同的約束鏈起來;框架會在內部使用 CompoundRouteConstraint 構建復合約束。
[Route("orders/{text:alpha:maxlength(5)}")] public HttpResponseMessage Get(string text){}
一個簡單的自定義路由約束,確保參數是一個合法的 email 格式,如代碼片段 3-12 所示。因為路由值是 IDictionary<string.object>,再驗證約束之前需要轉換成期望的類型(這里就是 string)。
public class EmailRouteConstraint : 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) { var stringValue = value as string; if (stringValue == null) return false; try { var email = new MailAddress(stringValue); return true; } catch (FormatException) { return false; } } return false; } }
直接在集中式路由中,像使用內置約束一樣使用這個約束。
config.Routes.MapHttpRoute( name: "Email", routeTemplate: "{controller}/email/{text}", constraints: new {text = new EmailRouteConstraint()}, defaults: null );
然而,使用屬性路由的時候,可以使用別名來代替,但是在 EmailRouteConstraint 類沒有這樣的定義。因為,別名和約束類型是通過 DefaultInlineConstraintReslover 來完成映射的,ASP.NET WEB API 使用這個來解析這個約束,我們需要執行如下步驟
var constraintResolver = new DefaultInlineConstraintResolver(); constraintResolver.ConstraintMap.Add("email", typeof (EmailRouteConstraint)); config.MapHttpAttributeRoutes(constraintResolver);
這樣,就可以像使用框架提供的約束一樣,使用上面定義的 email 約束。
[Route("orders/client/{text:email}")] public HttpResponseMessage GetByClient(string text) { }
注意 如果沒有額外的映射步驟,約束就不會有任何反應,但是,ASP.NET WEB API 的整個屬性路由可能有問題。
文章列表