asp.net-mvc – 支持URL中任何位置的catch-all参数的Asp.net MVC Route类

前端之家收集整理的这篇文章主要介绍了asp.net-mvc – 支持URL中任何位置的catch-all参数的Asp.net MVC Route类前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我越是想到它越多我认为可以编写一个使用这些URL定义的自定义路由:
  1. {var1}/{var2}/{var3}
  2. Const/{var1}/{var2}
  3. Const1/{var1}/Const2/{var2}
  4. {var1}/{var2}/Const

以及在任何上层网址中的任何位置上最多只有一个贪婪参数

  1. {*var1}/{var2}/{var3}
  2. {var1}/{*var2}/{var3}
  3. {var1}/{var2}/{*var3}

有一个重要的约束.具有贪婪段的路由不能包含任何可选部分.所有这些都是强制性的.

这是一个示例性请求

  1. http://localhost/Show/Topic/SubTopic/SubSubTopic/123/This-is-an-example

这是URL路由定义

  1. {action}/{*topicTree}/{id}/{title}

算法

在GetRouteData()中解析请求路由应该像这样工作:

>将请求拆分为段:

>显示
>主题
> SubTopic
> SubSubTopic
> 123
>这是一个例子

>从左侧开始处理路径URL定义并将单个段值分配给参数(或将请求段值匹配到静态路由常量段).
>当路径段定义为贪婪时,反向解析并转到最后一段.
>向后逐个解析路径段(分配它们的请求值),直到再次进入贪婪的捕获全部.
>当您再次到达贪婪的一个时,加入所有剩余的请求段(按原始顺序)并将它们分配给贪婪的catch-all路由参数.

问题

据我所知,它可以奏效.但我想知道:

>有没有人已经写过这样我没必要(因为还有其他方面要解析我没有提到(约束,默认等)
>你看到这个算法有什么缺陷吗,因为如果到目前为止还没有人做过,我将不得不自己编写.

我根本没有想过GetVirtuaPath()方法.

解决方法

最近我急需提问,所以我通常会自己解决问题.对不起,但这是我对那条路线的看法.任何人都发现任何问题:让我知道.

在URL中的任何位置使用catch-all段进行路由

  1. /// <summary>
  2. /// This route is used for cases where we want greedy route segments anywhere in the route URL definition
  3. /// </summary>
  4. public class GreedyRoute : Route
  5. {
  6. #region Properties
  7.  
  8. public new string Url { get; private set; }
  9.  
  10. private LinkedList<GreedyRouteSegment> urlSegments = new LinkedList<GreedyRouteSegment>();
  11.  
  12. private bool hasGreedySegment = false;
  13.  
  14. public int MinrequiredSegments { get; private set; }
  15.  
  16. #endregion
  17.  
  18. #region Constructors
  19.  
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="VivaRoute"/> class,using the specified URL pattern and handler class.
  22. /// </summary>
  23. /// <param name="url">The URL pattern for the route.</param>
  24. /// <param name="routeHandler">The object that processes requests for the route.</param>
  25. public GreedyRoute(string url,IRouteHandler routeHandler)
  26. : this(url,null,routeHandler)
  27. {
  28. }
  29.  
  30. /// <summary>
  31. /// Initializes a new instance of the <see cref="VivaRoute"/> class,using the specified URL pattern,handler class,and default parameter values.
  32. /// </summary>
  33. /// <param name="url">The URL pattern for the route.</param>
  34. /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
  35. /// <param name="routeHandler">The object that processes requests for the route.</param>
  36. public GreedyRoute(string url,RouteValueDictionary defaults,defaults,default parameter values,and constraints.
  37. /// </summary>
  38. /// <param name="url">The URL pattern for the route.</param>
  39. /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
  40. /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
  41. /// <param name="routeHandler">The object that processes requests for the route.</param>
  42. public GreedyRoute(string url,RouteValueDictionary constraints,constraints,and custom values.
  43. /// </summary>
  44. /// <param name="url">The URL pattern for the route.</param>
  45. /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
  46. /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
  47. /// <param name="dataTokens">Custom values that are passed to the route handler,but which are not used to determine whether the route matches a specific URL pattern. The route handler might need these values to process the request.</param>
  48. /// <param name="routeHandler">The object that processes requests for the route.</param>
  49. public GreedyRoute(string url,RouteValueDictionary dataTokens,IRouteHandler routeHandler)
  50. : base(url.Replace("*",""),dataTokens,routeHandler)
  51. {
  52. this.Defaults = defaults ?? new RouteValueDictionary();
  53. this.Constraints = constraints;
  54. this.DataTokens = dataTokens;
  55. this.RouteHandler = routeHandler;
  56. this.Url = url;
  57. this.MinrequiredSegments = 0;
  58.  
  59. // URL must be defined
  60. if (string.IsNullOrEmpty(url))
  61. {
  62. throw new ArgumentException("Route URL must be defined.","url");
  63. }
  64.  
  65. // correct URL definition can have AT MOST ONE greedy segment
  66. if (url.Split('*').Length > 2)
  67. {
  68. throw new ArgumentException("Route URL can have at most one greedy segment,but not more.","url");
  69. }
  70.  
  71. Regex rx = new Regex(@"^(?<isToken>{)?(?(isToken)(?<isGreedy>\*?))(?<name>[a-zA-Z0-9-_]+)(?(isToken)})$",RegexOptions.Compiled | RegexOptions.Singleline);
  72. foreach (string segment in url.Split('/'))
  73. {
  74. // segment must not be empty
  75. if (string.IsNullOrEmpty(segment))
  76. {
  77. throw new ArgumentException("Route URL is invalid. Sequence \"//\" is not allowed.","url");
  78. }
  79.  
  80. if (rx.IsMatch(segment))
  81. {
  82. Match m = rx.Match(segment);
  83. GreedyRouteSegment s = new GreedyRouteSegment {
  84. IsToken = m.Groups["isToken"].Value.Length.Equals(1),IsGreedy = m.Groups["isGreedy"].Value.Length.Equals(1),Name = m.Groups["name"].Value
  85. };
  86. this.urlSegments.AddLast(s);
  87. this.hasGreedySegment |= s.IsGreedy;
  88.  
  89. continue;
  90. }
  91. throw new ArgumentException("Route URL is invalid.","url");
  92. }
  93.  
  94. // get minimum required segments for this route
  95. LinkedListNode<GreedyRouteSegment> seg = this.urlSegments.Last;
  96. int sIndex = this.urlSegments.Count;
  97. while(seg != null && this.MinrequiredSegments.Equals(0))
  98. {
  99. if (!seg.Value.IsToken || !this.Defaults.ContainsKey(seg.Value.Name))
  100. {
  101. this.MinrequiredSegments = Math.Max(this.MinrequiredSegments,sIndex);
  102. }
  103. sIndex--;
  104. seg = seg.PrevIoUs;
  105. }
  106.  
  107. // check that segments after greedy segment don't define a default
  108. if (this.hasGreedySegment)
  109. {
  110. LinkedListNode<GreedyRouteSegment> s = this.urlSegments.Last;
  111. while (s != null && !s.Value.IsGreedy)
  112. {
  113. if (s.Value.IsToken && this.Defaults.ContainsKey(s.Value.Name))
  114. {
  115. throw new ArgumentException(string.Format("Defaults for route segment \"{0}\" is not allowed,because it's specified after greedy catch-all segment.",s.Value.Name),"defaults");
  116. }
  117. s = s.PrevIoUs;
  118. }
  119. }
  120. }
  121.  
  122. #endregion
  123.  
  124. #region GetRouteData
  125. /// <summary>
  126. /// Returns information about the requested route.
  127. /// </summary>
  128. /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
  129. /// <returns>
  130. /// An object that contains the values from the route definition.
  131. /// </returns>
  132. public override RouteData GetRouteData(HttpContextBase httpContext)
  133. {
  134. string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
  135.  
  136. RouteValueDictionary values = this.ParseRoute(virtualPath);
  137. if (values == null)
  138. {
  139. return null;
  140. }
  141.  
  142. RouteData result = new RouteData(this,this.RouteHandler);
  143. if (!this.ProcessConstraints(httpContext,values,RouteDirection.IncomingRequest))
  144. {
  145. return null;
  146. }
  147.  
  148. // everything's fine,fill route data
  149. foreach (KeyValuePair<string,object> value in values)
  150. {
  151. result.Values.Add(value.Key,value.Value);
  152. }
  153. if (this.DataTokens != null)
  154. {
  155. foreach (KeyValuePair<string,object> token in this.DataTokens)
  156. {
  157. result.DataTokens.Add(token.Key,token.Value);
  158. }
  159. }
  160. return result;
  161. }
  162. #endregion
  163.  
  164. #region GetVirtualPath
  165. /// <summary>
  166. /// Returns information about the URL that is associated with the route.
  167. /// </summary>
  168. /// <param name="requestContext">An object that encapsulates information about the requested route.</param>
  169. /// <param name="values">An object that contains the parameters for a route.</param>
  170. /// <returns>
  171. /// An object that contains information about the URL that is associated with the route.
  172. /// </returns>
  173. public override VirtualPathData GetVirtualPath(RequestContext requestContext,RouteValueDictionary values)
  174. {
  175. RouteUrl url = this.Bind(requestContext.RouteData.Values,values);
  176. if (url == null)
  177. {
  178. return null;
  179. }
  180. if (!this.ProcessConstraints(requestContext.HttpContext,url.Values,RouteDirection.UrlGeneration))
  181. {
  182. return null;
  183. }
  184.  
  185. VirtualPathData data = new VirtualPathData(this,url.Url);
  186. if (this.DataTokens != null)
  187. {
  188. foreach (KeyValuePair<string,object> pair in this.DataTokens)
  189. {
  190. data.DataTokens[pair.Key] = pair.Value;
  191. }
  192. }
  193. return data;
  194. }
  195. #endregion
  196.  
  197. #region Private methods
  198.  
  199. #region ProcessConstraints
  200. /// <summary>
  201. /// Processes constraints.
  202. /// </summary>
  203. /// <param name="httpContext">The HTTP context.</param>
  204. /// <param name="values">Route values.</param>
  205. /// <param name="direction">Route direction.</param>
  206. /// <returns><c>true</c> if constraints are satisfied; otherwise,<c>false</c>.</returns>
  207. private bool ProcessConstraints(HttpContextBase httpContext,RouteValueDictionary values,RouteDirection direction)
  208. {
  209. if (this.Constraints != null)
  210. {
  211. foreach (KeyValuePair<string,object> constraint in this.Constraints)
  212. {
  213. if (!this.ProcessConstraint(httpContext,constraint.Value,constraint.Key,direction))
  214. {
  215. return false;
  216. }
  217. }
  218. }
  219. return true;
  220. }
  221. #endregion
  222.  
  223. #region ParseRoute
  224. /// <summary>
  225. /// Parses the route into segment data as defined by this route.
  226. /// </summary>
  227. /// <param name="virtualPath">Virtual path.</param>
  228. /// <returns>Returns <see cref="System.Web.Routing.RouteValueDictionary"/> dictionary of route values.</returns>
  229. private RouteValueDictionary ParseRoute(string virtualPath)
  230. {
  231. Stack<string> parts = new Stack<string>(virtualPath.Split(new char[] {'/'},StringSplitOptions.RemoveEmptyEntries));
  232.  
  233. // number of request route parts must match route URL definition
  234. if (parts.Count < this.MinrequiredSegments)
  235. {
  236. return null;
  237. }
  238.  
  239. RouteValueDictionary result = new RouteValueDictionary();
  240.  
  241. // start parsing from the beginning
  242. bool finished = false;
  243. LinkedListNode<GreedyRouteSegment> currentSegment = this.urlSegments.First;
  244. while (!finished && !currentSegment.Value.IsGreedy)
  245. {
  246. object p = parts.Pop();
  247. if (currentSegment.Value.IsToken)
  248. {
  249. p = p ?? this.Defaults[currentSegment.Value.Name];
  250. result.Add(currentSegment.Value.Name,p);
  251. currentSegment = currentSegment.Next;
  252. finished = currentSegment == null;
  253. continue;
  254. }
  255. if (!currentSegment.Value.Equals(p))
  256. {
  257. return null;
  258. }
  259. }
  260.  
  261. // continue from the end if needed
  262. parts = new Stack<string>(parts.Reverse());
  263. currentSegment = this.urlSegments.Last;
  264. while (!finished && !currentSegment.Value.IsGreedy)
  265. {
  266. object p = parts.Pop();
  267. if (currentSegment.Value.IsToken)
  268. {
  269. p = p ?? this.Defaults[currentSegment.Value.Name];
  270. result.Add(currentSegment.Value.Name,p);
  271. currentSegment = currentSegment.PrevIoUs;
  272. finished = currentSegment == null;
  273. continue;
  274. }
  275. if (!currentSegment.Value.Equals(p))
  276. {
  277. return null;
  278. }
  279. }
  280.  
  281. // fill in the greedy catch-all segment
  282. if (!finished)
  283. {
  284. object remaining = string.Join("/",parts.Reverse().ToArray()) ?? this.Defaults[currentSegment.Value.Name];
  285. result.Add(currentSegment.Value.Name,remaining);
  286. }
  287.  
  288. // add remaining default values
  289. foreach (KeyValuePair<string,object> def in this.Defaults)
  290. {
  291. if (!result.ContainsKey(def.Key))
  292. {
  293. result.Add(def.Key,def.Value);
  294. }
  295. }
  296.  
  297. return result;
  298. }
  299. #endregion
  300.  
  301. #region Bind
  302. /// <summary>
  303. /// Binds the specified current values and values into a URL.
  304. /// </summary>
  305. /// <param name="currentValues">Current route data values.</param>
  306. /// <param name="values">Additional route values that can be used to generate the URL.</param>
  307. /// <returns>Returns a URL route string.</returns>
  308. private RouteUrl Bind(RouteValueDictionary currentValues,RouteValueDictionary values)
  309. {
  310. currentValues = currentValues ?? new RouteValueDictionary();
  311. values = values ?? new RouteValueDictionary();
  312.  
  313. HashSet<string> required = new HashSet<string>(this.urlSegments.Where(seg => seg.IsToken).ToList().ConvertAll(seg => seg.Name),StringComparer.OrdinalIgnoreCase);
  314. RouteValueDictionary routeValues = new RouteValueDictionary();
  315.  
  316. object dataValue = null;
  317. foreach (string token in new List<string>(required))
  318. {
  319. dataValue = values[token] ?? currentValues[token] ?? this.Defaults[token];
  320. if (this.IsUsable(dataValue))
  321. {
  322. string val = dataValue as string;
  323. if (val != null)
  324. {
  325. val = val.StartsWith("/") ? val.Substring(1) : val;
  326. val = val.EndsWith("/") ? val.Substring(0,val.Length - 1) : val;
  327. }
  328. routeValues.Add(token,val ?? dataValue);
  329. required.Remove(token);
  330. }
  331. }
  332.  
  333. // this route data is not related to this route
  334. if (required.Count > 0)
  335. {
  336. return null;
  337. }
  338.  
  339. // add all remaining values
  340. foreach (KeyValuePair<string,object> pair1 in values)
  341. {
  342. if (this.IsUsable(pair1.Value) && !routeValues.ContainsKey(pair1.Key))
  343. {
  344. routeValues.Add(pair1.Key,pair1.Value);
  345. }
  346. }
  347.  
  348. // add remaining defaults
  349. foreach (KeyValuePair<string,object> pair2 in this.Defaults)
  350. {
  351. if (this.IsUsable(pair2.Value) && !routeValues.ContainsKey(pair2.Key))
  352. {
  353. routeValues.Add(pair2.Key,pair2.Value);
  354. }
  355. }
  356.  
  357. // check that non-segment defaults are the same as those provided
  358. RouteValueDictionary nonRouteDefaults = new RouteValueDictionary(this.Defaults);
  359. foreach (GreedyRouteSegment seg in this.urlSegments.Where(ss => ss.IsToken))
  360. {
  361. nonRouteDefaults.Remove(seg.Name);
  362. }
  363. foreach (KeyValuePair<string,object> pair3 in nonRouteDefaults)
  364. {
  365. if (!routeValues.ContainsKey(pair3.Key) || !this.RoutePartsEqual(pair3.Value,routeValues[pair3.Key]))
  366. {
  367. // route data is not related to this route
  368. return null;
  369. }
  370. }
  371.  
  372. StringBuilder sb = new StringBuilder();
  373. RouteValueDictionary valuesToUse = new RouteValueDictionary(routeValues);
  374. bool mustAdd = this.hasGreedySegment;
  375.  
  376. // build URL string
  377. LinkedListNode<GreedyRouteSegment> s = this.urlSegments.Last;
  378. object segmentValue = null;
  379. while (s != null)
  380. {
  381. if (s.Value.IsToken)
  382. {
  383. segmentValue = valuesToUse[s.Value.Name];
  384. mustAdd = mustAdd || !this.RoutePartsEqual(segmentValue,this.Defaults[s.Value.Name]);
  385. valuesToUse.Remove(s.Value.Name);
  386. }
  387. else
  388. {
  389. segmentValue = s.Value.Name;
  390. mustAdd = true;
  391. }
  392.  
  393. if (mustAdd)
  394. {
  395. sb.Insert(0,sb.Length > 0 ? "/" : string.Empty);
  396. sb.Insert(0,Uri.EscapeUriString(Convert.ToString(segmentValue,CultureInfo.InvariantCulture)));
  397. }
  398.  
  399. s = s.PrevIoUs;
  400. }
  401.  
  402. // add remaining values
  403. if (valuesToUse.Count > 0)
  404. {
  405. bool first = true;
  406. foreach (KeyValuePair<string,object> pair3 in valuesToUse)
  407. {
  408. // only add when different from defaults
  409. if (!this.RoutePartsEqual(pair3.Value,this.Defaults[pair3.Key]))
  410. {
  411. sb.Append(first ? "?" : "&");
  412. sb.Append(Uri.EscapeDataString(pair3.Key));
  413. sb.Append("=");
  414. sb.Append(Uri.EscapeDataString(Convert.ToString(pair3.Value,CultureInfo.InvariantCulture)));
  415. first = false;
  416. }
  417. }
  418. }
  419.  
  420. return new RouteUrl {
  421. Url = sb.ToString(),Values = routeValues
  422. };
  423. }
  424. #endregion
  425.  
  426. #region IsUsable
  427. /// <summary>
  428. /// Determines whether an object actually is instantiated or has a value.
  429. /// </summary>
  430. /// <param name="value">Object value to check.</param>
  431. /// <returns>
  432. /// <c>true</c> if an object is instantiated or has a value; otherwise,<c>false</c>.
  433. /// </returns>
  434. private bool IsUsable(object value)
  435. {
  436. string val = value as string;
  437. if (val != null)
  438. {
  439. return val.Length > 0;
  440. }
  441. return value != null;
  442. }
  443. #endregion
  444.  
  445. #region RoutePartsEqual
  446. /// <summary>
  447. /// Checks if two route parts are equal
  448. /// </summary>
  449. /// <param name="firstValue">The first value.</param>
  450. /// <param name="secondValue">The second value.</param>
  451. /// <returns><c>true</c> if both values are equal; otherwise,<c>false</c>.</returns>
  452. private bool RoutePartsEqual(object firstValue,object secondValue)
  453. {
  454. string sFirst = firstValue as string;
  455. string sSecond = secondValue as string;
  456. if ((sFirst != null) && (sSecond != null))
  457. {
  458. return string.Equals(sFirst,sSecond,StringComparison.OrdinalIgnoreCase);
  459. }
  460. if ((sFirst != null) && (sSecond != null))
  461. {
  462. return sFirst.Equals(sSecond);
  463. }
  464. return (sFirst == sSecond);
  465. }
  466. #endregion
  467.  
  468. #endregion
  469. }

在上层代码中使用的另外两个类:

  1. /// <summary>
  2. /// Represents a route segment
  3. /// </summary>
  4. public class RouteSegment
  5. {
  6. /// <summary>
  7. /// Gets or sets segment path or token name.
  8. /// </summary>
  9. /// <value>Route segment path or token name.</value>
  10. public string Name { get; set; }
  11.  
  12. /// <summary>
  13. /// Gets or sets a value indicating whether this segment is greedy.
  14. /// </summary>
  15. /// <value><c>true</c> if this segment is greedy; otherwise,<c>false</c>.</value>
  16. public bool IsGreedy { get; set; }
  17.  
  18. /// <summary>
  19. /// Gets or sets a value indicating whether this segment is a token.
  20. /// </summary>
  21. /// <value><c>true</c> if this segment is a token; otherwise,<c>false</c>.</value>
  22. public bool IsToken { get; set; }
  23. }

  1. /// <summary>
  2. /// Represents a generated route url with route data
  3. /// </summary>
  4. public class RouteUrl
  5. {
  6. /// <summary>
  7. /// Gets or sets the route URL.
  8. /// </summary>
  9. /// <value>Route URL.</value>
  10. public string Url { get; set; }
  11.  
  12. /// <summary>
  13. /// Gets or sets route values.
  14. /// </summary>
  15. /// <value>Route values.</value>
  16. public RouteValueDictionary Values { get; set; }
  17. }

这就是所有人.让我知道任何问题.

我还写了一个与这个自定义路由类相关的blog post.它详细解释了所有内容.

猜你在找的asp.Net相关文章