PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Ivony.Html.Web.Mvc/SimpleRouteRule.cs

#
C# | 528 lines | 274 code | 160 blank | 94 comment | 45 complexity | ae1e10b7a42470f99c7946063916533f MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Web;
  8. using System.Web.Routing;
  9. using Ivony.Fluent;
  10. namespace Ivony.Html.Web.Mvc
  11. {
  12. /// <summary>
  13. /// 简单路由规则,定义简单路由表的路由规则
  14. /// </summary>
  15. public sealed class SimpleRouteRule
  16. {
  17. /// <summary>定义匹配静态路径段的正则表达式</summary>
  18. public const string staticParagraphPattern = @"(?<paragraph>[\p{Lu}\p{Ll}\p{Nd}-.]+)";
  19. /// <summary>定义匹配动态路径段的正则表达式</summary>
  20. public const string dynamicParagraphPattern = @"(?<paragraph>\{[\p{Lu}\p{Ll}\p{Nd}]+\})";
  21. /// <summary>定义匹配 URL 模式的正则表达式</summary>
  22. public static readonly string urlPattern = @"(^~/$)|(^~((/{static}(/{static})*(/{dynamic})*)|((/{dynamic})+))$)".Replace( "{static}", staticParagraphPattern ).Replace( "{dynamic}", dynamicParagraphPattern );
  23. private static readonly Regex urlPatternRegex = new Regex( urlPattern, RegexOptions.Compiled );
  24. /// <summary>
  25. /// 创建一个简单路由规则
  26. /// </summary>
  27. /// <param name="name">规则名称</param>
  28. /// <param name="urlPattern">URL 模式</param>
  29. /// <param name="routeValues">静态/默认路由值</param>
  30. /// <param name="queryKeys">可用于 QueryString 的参数</param>
  31. internal SimpleRouteRule( string name, string urlPattern, IDictionary<string, string> routeValues, string[] queryKeys )
  32. {
  33. Name = name;
  34. if ( queryKeys == null )
  35. {
  36. LimitedQueries = false;
  37. queryKeys = new string[0];
  38. }
  39. else
  40. LimitedQueries = true;
  41. DataTokens = new RouteValueDictionary();
  42. var match = urlPatternRegex.Match( urlPattern );
  43. if ( !match.Success )
  44. throw new FormatException( "URL模式格式不正确" );
  45. _paragraphes = match.Groups["paragraph"].Captures.Cast<Capture>().Select( c => c.Value ).ToArray();
  46. _urlPattern = urlPattern;
  47. _staticValues = new Dictionary<string, string>( routeValues, StringComparer.OrdinalIgnoreCase );
  48. _routeKeys = new HashSet<string>( _staticValues.Keys, StringComparer.OrdinalIgnoreCase );
  49. _dynamics = new HashSet<string>( _paragraphes.Where( p => p.StartsWith( "{" ) && p.EndsWith( "}" ) ).Select( p => p.Substring( 1, p.Length - 2 ) ), StringComparer.OrdinalIgnoreCase );
  50. foreach ( var key in _dynamics )
  51. {
  52. if ( _routeKeys.Contains( key ) )
  53. throw new FormatException( "URL模式格式不正确,包含重复的动态参数名或动态参数名与预设路由键重复" );
  54. _routeKeys.Add( key );
  55. }
  56. if ( _routeKeys.Intersect( queryKeys, StringComparer.OrdinalIgnoreCase ).Any() )
  57. throw new FormatException( "URL模式格式不正确,动态参数或预设路由键与可选查询字符串名重复" );
  58. _queryKeys = new HashSet<string>( queryKeys, StringComparer.OrdinalIgnoreCase );
  59. _allKeys = new HashSet<string>( _routeKeys, StringComparer.OrdinalIgnoreCase );
  60. _allKeys.UnionWith( _queryKeys );
  61. }
  62. /// <summary>
  63. /// 路由规则的名称
  64. /// </summary>
  65. public string Name
  66. {
  67. get;
  68. private set;
  69. }
  70. /// <summary>
  71. /// 是否限制产生的 QueryString 不超过指定范围(查询键)
  72. /// </summary>
  73. public bool LimitedQueries
  74. {
  75. get;
  76. private set;
  77. }
  78. /// <summary>
  79. /// 指示路由规则是否为单向的,单向路由只路由请求,不产生虚拟路径。
  80. /// </summary>
  81. public bool Oneway
  82. {
  83. get;
  84. private set;
  85. }
  86. private string[] _paragraphes;
  87. /// <summary>
  88. /// 获取所有路径段
  89. /// </summary>
  90. public string[] Paragraphes
  91. {
  92. get { return _paragraphes.Copy(); }
  93. }
  94. private HashSet<string> _routeKeys;
  95. /// <summary>
  96. /// 获取所有路由键(包括静态和动态的)
  97. /// </summary>
  98. /// <remarks>
  99. /// 路由键的值会作为虚拟路径的一部分
  100. /// </remarks>
  101. public string[] RouteKeys
  102. {
  103. get { return _routeKeys.ToArray(); }
  104. }
  105. private HashSet<string> _queryKeys;
  106. /// <summary>
  107. /// 获取所有查询键
  108. /// </summary>
  109. /// <remarks>
  110. /// 构造虚拟路径时,查询键都是可选的。
  111. /// 查询键的值会被产生为查询字符串。
  112. /// </remarks>
  113. public string[] QueryKeys
  114. {
  115. get { return _queryKeys.ToArray(); }
  116. }
  117. private HashSet<string> _allKeys;
  118. /// <summary>
  119. /// 获取所有键(包括路由键和查询键)
  120. /// </summary>
  121. public string[] AllKeys
  122. {
  123. get
  124. {
  125. return _allKeys.ToArray();
  126. }
  127. }
  128. private HashSet<string> _dynamics;
  129. /// <summary>
  130. /// 获取所有动态路由键
  131. /// </summary>
  132. /// <remarks>
  133. /// 动态路由键的值不能包含特殊字符
  134. /// </remarks>
  135. public string[] DynamicRouteKyes
  136. {
  137. get { return _dynamics.ToArray(); }
  138. }
  139. private string _prefix;
  140. /// <summary>
  141. /// 获取URL模式的静态前缀
  142. /// </summary>
  143. public string StaticPrefix
  144. {
  145. get
  146. {
  147. if ( _prefix == null )
  148. {
  149. var dynamicStarts = _urlPattern.IndexOf( '{' );
  150. if ( dynamicStarts < 0 )
  151. _prefix = _urlPattern;
  152. else
  153. _prefix = _urlPattern.Substring( 0, dynamicStarts - 1 );
  154. }
  155. return _prefix;
  156. }
  157. }
  158. private string _urlPattern;
  159. /// <summary>
  160. /// 获取整个URL模式
  161. /// </summary>
  162. public string UrlPattern
  163. {
  164. get { return _urlPattern; }
  165. }
  166. private IDictionary<string, string> _staticValues;
  167. /// <summary>
  168. /// 获取所有的静态值
  169. /// </summary>
  170. public IDictionary<string, string> StaticRouteValues
  171. {
  172. get { return new Dictionary<string, string>( _staticValues ); }
  173. }
  174. /// <summary>
  175. /// 根据路由值创建虚拟路径
  176. /// </summary>
  177. /// <param name="routeValues">路由值</param>
  178. /// <returns>创建的虚拟路径</returns>
  179. public string CreateVirtualPath( IDictionary<string, string> routeValues )
  180. {
  181. var builder = new StringBuilder( StaticPrefix );
  182. foreach ( var key in DynamicRouteKyes )
  183. {
  184. var value = routeValues[key];
  185. if ( string.IsNullOrEmpty( value ) )
  186. throw new ArgumentException( "作为动态路径的路由值不能包含空引用或空字符串", "routeValues" );
  187. if ( value.Contains( '/' ) )
  188. throw new ArgumentException( "作为动态路径的路由值不能包含路径分隔符 '/'", "routeValues" );
  189. //value = HttpUtility.UrlEncode( value, RoutingTable.UrlEncoding );
  190. builder.Append( "/" + value );
  191. }
  192. bool isAppendedQueryStartSymbol = false;
  193. var unallocatedKeys = routeValues.Keys.Except( RouteKeys, StringComparer.OrdinalIgnoreCase );
  194. foreach ( var key in unallocatedKeys )
  195. {
  196. if ( QueryKeys.Contains( key, StringComparer.OrdinalIgnoreCase ) || !LimitedQueries )
  197. {
  198. var value = routeValues[key];
  199. if ( !isAppendedQueryStartSymbol )
  200. builder.Append( '?' );
  201. else
  202. builder.Append( '&' );
  203. isAppendedQueryStartSymbol = true;
  204. builder.Append( HttpUtility.UrlEncode( key ) );
  205. builder.Append( '=' );
  206. builder.Append( HttpUtility.UrlEncode( routeValues[key] ) );
  207. }
  208. }
  209. return builder.ToString();
  210. }
  211. /// <summary>
  212. /// 规则所属的简单路由表实例
  213. /// </summary>
  214. public SimpleRouteTable SimpleRouteTable
  215. {
  216. get;
  217. internal set;
  218. }
  219. /// <summary>
  220. /// 检查指定的路由值是否满足约束
  221. /// </summary>
  222. /// <param name="values">路由值</param>
  223. /// <returns>是否满足路由规则的约束</returns>
  224. public bool IsMatch( IDictionary<string, string> values )
  225. {
  226. if ( values == null )
  227. throw new ArgumentNullException( "values" );
  228. foreach ( var key in _staticValues.Keys )
  229. {
  230. string val;
  231. if ( !values.TryGetValue( key, out val ) )
  232. return false;
  233. if ( !_staticValues[key].EqualsIgnoreCase( val ) )
  234. return false;
  235. }
  236. return true;
  237. }
  238. private object _sync = new object();
  239. private string _virtualPathDescriptor;
  240. internal string GetVirtualPathDescriptor()
  241. {
  242. lock ( _sync )
  243. {
  244. if ( _virtualPathDescriptor != null )
  245. return _virtualPathDescriptor;
  246. return _virtualPathDescriptor = StaticPrefix + string.Join( "", Enumerable.Repeat( "/{dynamic}", DynamicRouteKyes.Length ) );
  247. }
  248. }
  249. private string _routeValuesDescriptor;
  250. internal string GetRouteValuesDescriptor()
  251. {
  252. lock ( _sync )
  253. {
  254. if ( _routeValuesDescriptor != null )
  255. return _routeValuesDescriptor;
  256. List<string> list = new List<string>();
  257. foreach ( var key in RouteKeys.OrderBy( k => k, StringComparer.OrdinalIgnoreCase ) )
  258. {
  259. string value;
  260. if ( StaticRouteValues.TryGetValue( key, out value ) )
  261. list.Add( string.Format( "<\"{0}\",\"{1}\">", key.Replace( "\"", "\\\"" ), value.Replace( "\"", "\\\"" ) ) );
  262. else
  263. list.Add( string.Format( "<\"{0}\",dynamic>", key.Replace( "\"", "\\\"" ) ) );
  264. }
  265. return _routeValuesDescriptor = string.Join( ",", list );
  266. }
  267. }
  268. /// <summary>
  269. /// 检查两个路由规则是否互斥。
  270. /// </summary>
  271. /// <param name="rule1">路由规则1</param>
  272. /// <param name="rule2">路由规则2</param>
  273. /// <returns></returns>
  274. public static bool Mutex( SimpleRouteRule rule1, SimpleRouteRule rule2 )
  275. {
  276. var KeySet = new HashSet<string>( rule1.StaticRouteValues.Keys, StringComparer.OrdinalIgnoreCase );
  277. var KeySet2 = new HashSet<string>( rule2.StaticRouteValues.Keys, StringComparer.OrdinalIgnoreCase );
  278. KeySet.IntersectWith( KeySet2 );
  279. return KeySet.Any( key => !rule1.StaticRouteValues[key].EqualsIgnoreCase( rule2.StaticRouteValues[key] ) );
  280. }
  281. /// <summary>
  282. /// 比较两个路由规则约束是否一致
  283. /// </summary>
  284. /// <param name="rule">要比较的路由规则</param>
  285. /// <returns>两个规则的约束是否一致</returns>
  286. public bool EqualsConstraints( SimpleRouteRule rule )
  287. {
  288. if ( rule == null )
  289. throw new ArgumentNullException( "rule" );
  290. if ( rule.StaticRouteValues.Count != StaticRouteValues.Count )
  291. return false;
  292. return IsMatch( rule.StaticRouteValues );
  293. }
  294. private static Regex multipleSlashRegex = new Regex( "/+", RegexOptions.Compiled );
  295. /// <summary>
  296. /// 获取路由值
  297. /// </summary>
  298. /// <param name="virtualPath">当前请求的虚拟路径</param>
  299. /// <param name="queryString">当前请求的查询数据</param>
  300. /// <returns></returns>
  301. public IDictionary<string, string> GetRouteValues( string virtualPath, NameValueCollection queryString )
  302. {
  303. if ( virtualPath == null )
  304. throw new ArgumentNullException( "virtualPath" );
  305. if ( !VirtualPathUtility.IsAppRelative( virtualPath ) )
  306. throw new ArgumentException( "virtualPath 只能使用应用程序根相对路径,即以 \"~/\" 开头的路径,调用 VirtualPathUtility.ToAppRelative 方法或使用 HttpRequest.AppRelativeCurrentExecutionFilePath 属性获取", "virtualPath" );
  307. var queryKeySet = new HashSet<string>( _queryKeys, StringComparer.OrdinalIgnoreCase );
  308. var requestQueryKeySet = new HashSet<string>( queryString.AllKeys, StringComparer.OrdinalIgnoreCase );
  309. if ( LimitedQueries && !queryKeySet.IsSupersetOf( requestQueryKeySet ) )//如果限制了查询键并且查询键集合没有完全涵盖所有传进来的QueryString键的话,即存在有一个QueryString键不在查询键集合中,则这条规则不适用。
  310. return null;
  311. virtualPath = multipleSlashRegex.Replace( virtualPath, "/" );//将连续的/替换成单独的/
  312. virtualPath = virtualPath.Substring( 2 );
  313. var pathParagraphs = virtualPath.Split( '/' );
  314. if ( virtualPath == "" )
  315. pathParagraphs = new string[0];
  316. if ( pathParagraphs.Length != Paragraphes.Length )//路径段长度不一致,规则不适用
  317. return null;
  318. var values = new Dictionary<string, string>( StringComparer.OrdinalIgnoreCase );
  319. foreach ( var pair in _staticValues )
  320. values.Add( pair.Key, pair.Value );
  321. for ( int i = 0; i < pathParagraphs.Length; i++ )
  322. {
  323. var paragraph = Paragraphes[i];
  324. if ( !paragraph.StartsWith( "{" ) )
  325. {
  326. if ( !pathParagraphs[i].EqualsIgnoreCase( paragraph ) )//静态路径段不符,规则不适用
  327. return null;
  328. }
  329. else
  330. {
  331. var name = paragraph.Substring( 1, paragraph.Length - 2 );
  332. values.Add( name, pathParagraphs[i] );
  333. }
  334. }
  335. if ( !LimitedQueries )//如果没有限制查询键,但传进来的查询键与现有路由键有任何冲突,则这条规则不适用。
  336. { //因为如果限制了查询键,则上面会确保查询键不超出限制的范围,且查询键的范围与路由键范围不可能重合(构造函数限定),也就不可能存在冲突。
  337. requestQueryKeySet.IntersectWith( _routeKeys );
  338. if ( requestQueryKeySet.Any() )
  339. return null;
  340. }
  341. foreach ( var key in queryString.AllKeys )
  342. {
  343. if ( key == null )
  344. continue;//因某些未知原因会导致 AllKeys 包含空键值。
  345. var v = queryString[key];
  346. if ( v != null )
  347. values.Add( key, v );
  348. }
  349. return values;
  350. }
  351. /// <summary>
  352. /// 获取一个字符串,其描述了这个简单路由规则。
  353. /// </summary>
  354. /// <returns></returns>
  355. public override string ToString()
  356. {
  357. return UrlPattern + " : " + "{" + GetRouteValuesDescriptor() + "}";
  358. }
  359. /// <summary>
  360. /// 获取简单路由规则的扩展数据标记
  361. /// </summary>
  362. public RouteValueDictionary DataTokens
  363. {
  364. get;
  365. private set;
  366. }
  367. }
  368. }