我越是想到它越多我认为可以编写一个使用这些URL定义的自定义路由:
- {var1}/{var2}/{var3}
- Const/{var1}/{var2}
- Const1/{var1}/Const2/{var2}
- {var1}/{var2}/Const
以及在任何上层网址中的任何位置上最多只有一个贪婪参数
- {*var1}/{var2}/{var3}
- {var1}/{*var2}/{var3}
- {var1}/{var2}/{*var3}
有一个重要的约束.具有贪婪段的路由不能包含任何可选部分.所有这些都是强制性的.
例
这是一个示例性请求
- http://localhost/Show/Topic/SubTopic/SubSubTopic/123/This-is-an-example
这是URL路由定义
- {action}/{*topicTree}/{id}/{title}
算法
在GetRouteData()中解析请求路由应该像这样工作:
>将请求拆分为段:
>显示
>主题
> SubTopic
> SubSubTopic
> 123
>这是一个例子
>从左侧开始处理路径URL定义并将单个段值分配给参数(或将请求段值匹配到静态路由常量段).
>当路径段定义为贪婪时,反向解析并转到最后一段.
>向后逐个解析路径段(分配它们的请求值),直到再次进入贪婪的捕获全部.
>当您再次到达贪婪的一个时,加入所有剩余的请求段(按原始顺序)并将它们分配给贪婪的catch-all路由参数.
问题
据我所知,它可以奏效.但我想知道:
>有没有人已经写过这样我没必要(因为还有其他方面要解析我没有提到(约束,默认等)
>你看到这个算法有什么缺陷吗,因为如果到目前为止还没有人做过,我将不得不自己编写.
我根本没有想过GetVirtuaPath()方法.
解决方法
最近我急需提问,所以我通常会自己解决问题.对不起,但这是我对那条路线的看法.任何人都发现任何问题:让我知道.
在URL中的任何位置使用catch-all段进行路由
- /// <summary>
- /// This route is used for cases where we want greedy route segments anywhere in the route URL definition
- /// </summary>
- public class GreedyRoute : Route
- {
- #region Properties
- public new string Url { get; private set; }
- private LinkedList<GreedyRouteSegment> urlSegments = new LinkedList<GreedyRouteSegment>();
- private bool hasGreedySegment = false;
- public int MinrequiredSegments { get; private set; }
- #endregion
- #region Constructors
- /// <summary>
- /// Initializes a new instance of the <see cref="VivaRoute"/> class,using the specified URL pattern and handler class.
- /// </summary>
- /// <param name="url">The URL pattern for the route.</param>
- /// <param name="routeHandler">The object that processes requests for the route.</param>
- public GreedyRoute(string url,IRouteHandler routeHandler)
- : this(url,null,routeHandler)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="VivaRoute"/> class,using the specified URL pattern,handler class,and default parameter values.
- /// </summary>
- /// <param name="url">The URL pattern for the route.</param>
- /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
- /// <param name="routeHandler">The object that processes requests for the route.</param>
- public GreedyRoute(string url,RouteValueDictionary defaults,defaults,default parameter values,and constraints.
- /// </summary>
- /// <param name="url">The URL pattern for the route.</param>
- /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
- /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
- /// <param name="routeHandler">The object that processes requests for the route.</param>
- public GreedyRoute(string url,RouteValueDictionary constraints,constraints,and custom values.
- /// </summary>
- /// <param name="url">The URL pattern for the route.</param>
- /// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
- /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
- /// <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>
- /// <param name="routeHandler">The object that processes requests for the route.</param>
- public GreedyRoute(string url,RouteValueDictionary dataTokens,IRouteHandler routeHandler)
- : base(url.Replace("*",""),dataTokens,routeHandler)
- {
- this.Defaults = defaults ?? new RouteValueDictionary();
- this.Constraints = constraints;
- this.DataTokens = dataTokens;
- this.RouteHandler = routeHandler;
- this.Url = url;
- this.MinrequiredSegments = 0;
- // URL must be defined
- if (string.IsNullOrEmpty(url))
- {
- throw new ArgumentException("Route URL must be defined.","url");
- }
- // correct URL definition can have AT MOST ONE greedy segment
- if (url.Split('*').Length > 2)
- {
- throw new ArgumentException("Route URL can have at most one greedy segment,but not more.","url");
- }
- Regex rx = new Regex(@"^(?<isToken>{)?(?(isToken)(?<isGreedy>\*?))(?<name>[a-zA-Z0-9-_]+)(?(isToken)})$",RegexOptions.Compiled | RegexOptions.Singleline);
- foreach (string segment in url.Split('/'))
- {
- // segment must not be empty
- if (string.IsNullOrEmpty(segment))
- {
- throw new ArgumentException("Route URL is invalid. Sequence \"//\" is not allowed.","url");
- }
- if (rx.IsMatch(segment))
- {
- Match m = rx.Match(segment);
- GreedyRouteSegment s = new GreedyRouteSegment {
- IsToken = m.Groups["isToken"].Value.Length.Equals(1),IsGreedy = m.Groups["isGreedy"].Value.Length.Equals(1),Name = m.Groups["name"].Value
- };
- this.urlSegments.AddLast(s);
- this.hasGreedySegment |= s.IsGreedy;
- continue;
- }
- throw new ArgumentException("Route URL is invalid.","url");
- }
- // get minimum required segments for this route
- LinkedListNode<GreedyRouteSegment> seg = this.urlSegments.Last;
- int sIndex = this.urlSegments.Count;
- while(seg != null && this.MinrequiredSegments.Equals(0))
- {
- if (!seg.Value.IsToken || !this.Defaults.ContainsKey(seg.Value.Name))
- {
- this.MinrequiredSegments = Math.Max(this.MinrequiredSegments,sIndex);
- }
- sIndex--;
- seg = seg.PrevIoUs;
- }
- // check that segments after greedy segment don't define a default
- if (this.hasGreedySegment)
- {
- LinkedListNode<GreedyRouteSegment> s = this.urlSegments.Last;
- while (s != null && !s.Value.IsGreedy)
- {
- if (s.Value.IsToken && this.Defaults.ContainsKey(s.Value.Name))
- {
- 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");
- }
- s = s.PrevIoUs;
- }
- }
- }
- #endregion
- #region GetRouteData
- /// <summary>
- /// Returns information about the requested route.
- /// </summary>
- /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
- /// <returns>
- /// An object that contains the values from the route definition.
- /// </returns>
- public override RouteData GetRouteData(HttpContextBase httpContext)
- {
- string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
- RouteValueDictionary values = this.ParseRoute(virtualPath);
- if (values == null)
- {
- return null;
- }
- RouteData result = new RouteData(this,this.RouteHandler);
- if (!this.ProcessConstraints(httpContext,values,RouteDirection.IncomingRequest))
- {
- return null;
- }
- // everything's fine,fill route data
- foreach (KeyValuePair<string,object> value in values)
- {
- result.Values.Add(value.Key,value.Value);
- }
- if (this.DataTokens != null)
- {
- foreach (KeyValuePair<string,object> token in this.DataTokens)
- {
- result.DataTokens.Add(token.Key,token.Value);
- }
- }
- return result;
- }
- #endregion
- #region GetVirtualPath
- /// <summary>
- /// Returns information about the URL that is associated with the route.
- /// </summary>
- /// <param name="requestContext">An object that encapsulates information about the requested route.</param>
- /// <param name="values">An object that contains the parameters for a route.</param>
- /// <returns>
- /// An object that contains information about the URL that is associated with the route.
- /// </returns>
- public override VirtualPathData GetVirtualPath(RequestContext requestContext,RouteValueDictionary values)
- {
- RouteUrl url = this.Bind(requestContext.RouteData.Values,values);
- if (url == null)
- {
- return null;
- }
- if (!this.ProcessConstraints(requestContext.HttpContext,url.Values,RouteDirection.UrlGeneration))
- {
- return null;
- }
- VirtualPathData data = new VirtualPathData(this,url.Url);
- if (this.DataTokens != null)
- {
- foreach (KeyValuePair<string,object> pair in this.DataTokens)
- {
- data.DataTokens[pair.Key] = pair.Value;
- }
- }
- return data;
- }
- #endregion
- #region Private methods
- #region ProcessConstraints
- /// <summary>
- /// Processes constraints.
- /// </summary>
- /// <param name="httpContext">The HTTP context.</param>
- /// <param name="values">Route values.</param>
- /// <param name="direction">Route direction.</param>
- /// <returns><c>true</c> if constraints are satisfied; otherwise,<c>false</c>.</returns>
- private bool ProcessConstraints(HttpContextBase httpContext,RouteValueDictionary values,RouteDirection direction)
- {
- if (this.Constraints != null)
- {
- foreach (KeyValuePair<string,object> constraint in this.Constraints)
- {
- if (!this.ProcessConstraint(httpContext,constraint.Value,constraint.Key,direction))
- {
- return false;
- }
- }
- }
- return true;
- }
- #endregion
- #region ParseRoute
- /// <summary>
- /// Parses the route into segment data as defined by this route.
- /// </summary>
- /// <param name="virtualPath">Virtual path.</param>
- /// <returns>Returns <see cref="System.Web.Routing.RouteValueDictionary"/> dictionary of route values.</returns>
- private RouteValueDictionary ParseRoute(string virtualPath)
- {
- Stack<string> parts = new Stack<string>(virtualPath.Split(new char[] {'/'},StringSplitOptions.RemoveEmptyEntries));
- // number of request route parts must match route URL definition
- if (parts.Count < this.MinrequiredSegments)
- {
- return null;
- }
- RouteValueDictionary result = new RouteValueDictionary();
- // start parsing from the beginning
- bool finished = false;
- LinkedListNode<GreedyRouteSegment> currentSegment = this.urlSegments.First;
- while (!finished && !currentSegment.Value.IsGreedy)
- {
- object p = parts.Pop();
- if (currentSegment.Value.IsToken)
- {
- p = p ?? this.Defaults[currentSegment.Value.Name];
- result.Add(currentSegment.Value.Name,p);
- currentSegment = currentSegment.Next;
- finished = currentSegment == null;
- continue;
- }
- if (!currentSegment.Value.Equals(p))
- {
- return null;
- }
- }
- // continue from the end if needed
- parts = new Stack<string>(parts.Reverse());
- currentSegment = this.urlSegments.Last;
- while (!finished && !currentSegment.Value.IsGreedy)
- {
- object p = parts.Pop();
- if (currentSegment.Value.IsToken)
- {
- p = p ?? this.Defaults[currentSegment.Value.Name];
- result.Add(currentSegment.Value.Name,p);
- currentSegment = currentSegment.PrevIoUs;
- finished = currentSegment == null;
- continue;
- }
- if (!currentSegment.Value.Equals(p))
- {
- return null;
- }
- }
- // fill in the greedy catch-all segment
- if (!finished)
- {
- object remaining = string.Join("/",parts.Reverse().ToArray()) ?? this.Defaults[currentSegment.Value.Name];
- result.Add(currentSegment.Value.Name,remaining);
- }
- // add remaining default values
- foreach (KeyValuePair<string,object> def in this.Defaults)
- {
- if (!result.ContainsKey(def.Key))
- {
- result.Add(def.Key,def.Value);
- }
- }
- return result;
- }
- #endregion
- #region Bind
- /// <summary>
- /// Binds the specified current values and values into a URL.
- /// </summary>
- /// <param name="currentValues">Current route data values.</param>
- /// <param name="values">Additional route values that can be used to generate the URL.</param>
- /// <returns>Returns a URL route string.</returns>
- private RouteUrl Bind(RouteValueDictionary currentValues,RouteValueDictionary values)
- {
- currentValues = currentValues ?? new RouteValueDictionary();
- values = values ?? new RouteValueDictionary();
- HashSet<string> required = new HashSet<string>(this.urlSegments.Where(seg => seg.IsToken).ToList().ConvertAll(seg => seg.Name),StringComparer.OrdinalIgnoreCase);
- RouteValueDictionary routeValues = new RouteValueDictionary();
- object dataValue = null;
- foreach (string token in new List<string>(required))
- {
- dataValue = values[token] ?? currentValues[token] ?? this.Defaults[token];
- if (this.IsUsable(dataValue))
- {
- string val = dataValue as string;
- if (val != null)
- {
- val = val.StartsWith("/") ? val.Substring(1) : val;
- val = val.EndsWith("/") ? val.Substring(0,val.Length - 1) : val;
- }
- routeValues.Add(token,val ?? dataValue);
- required.Remove(token);
- }
- }
- // this route data is not related to this route
- if (required.Count > 0)
- {
- return null;
- }
- // add all remaining values
- foreach (KeyValuePair<string,object> pair1 in values)
- {
- if (this.IsUsable(pair1.Value) && !routeValues.ContainsKey(pair1.Key))
- {
- routeValues.Add(pair1.Key,pair1.Value);
- }
- }
- // add remaining defaults
- foreach (KeyValuePair<string,object> pair2 in this.Defaults)
- {
- if (this.IsUsable(pair2.Value) && !routeValues.ContainsKey(pair2.Key))
- {
- routeValues.Add(pair2.Key,pair2.Value);
- }
- }
- // check that non-segment defaults are the same as those provided
- RouteValueDictionary nonRouteDefaults = new RouteValueDictionary(this.Defaults);
- foreach (GreedyRouteSegment seg in this.urlSegments.Where(ss => ss.IsToken))
- {
- nonRouteDefaults.Remove(seg.Name);
- }
- foreach (KeyValuePair<string,object> pair3 in nonRouteDefaults)
- {
- if (!routeValues.ContainsKey(pair3.Key) || !this.RoutePartsEqual(pair3.Value,routeValues[pair3.Key]))
- {
- // route data is not related to this route
- return null;
- }
- }
- StringBuilder sb = new StringBuilder();
- RouteValueDictionary valuesToUse = new RouteValueDictionary(routeValues);
- bool mustAdd = this.hasGreedySegment;
- // build URL string
- LinkedListNode<GreedyRouteSegment> s = this.urlSegments.Last;
- object segmentValue = null;
- while (s != null)
- {
- if (s.Value.IsToken)
- {
- segmentValue = valuesToUse[s.Value.Name];
- mustAdd = mustAdd || !this.RoutePartsEqual(segmentValue,this.Defaults[s.Value.Name]);
- valuesToUse.Remove(s.Value.Name);
- }
- else
- {
- segmentValue = s.Value.Name;
- mustAdd = true;
- }
- if (mustAdd)
- {
- sb.Insert(0,sb.Length > 0 ? "/" : string.Empty);
- sb.Insert(0,Uri.EscapeUriString(Convert.ToString(segmentValue,CultureInfo.InvariantCulture)));
- }
- s = s.PrevIoUs;
- }
- // add remaining values
- if (valuesToUse.Count > 0)
- {
- bool first = true;
- foreach (KeyValuePair<string,object> pair3 in valuesToUse)
- {
- // only add when different from defaults
- if (!this.RoutePartsEqual(pair3.Value,this.Defaults[pair3.Key]))
- {
- sb.Append(first ? "?" : "&");
- sb.Append(Uri.EscapeDataString(pair3.Key));
- sb.Append("=");
- sb.Append(Uri.EscapeDataString(Convert.ToString(pair3.Value,CultureInfo.InvariantCulture)));
- first = false;
- }
- }
- }
- return new RouteUrl {
- Url = sb.ToString(),Values = routeValues
- };
- }
- #endregion
- #region IsUsable
- /// <summary>
- /// Determines whether an object actually is instantiated or has a value.
- /// </summary>
- /// <param name="value">Object value to check.</param>
- /// <returns>
- /// <c>true</c> if an object is instantiated or has a value; otherwise,<c>false</c>.
- /// </returns>
- private bool IsUsable(object value)
- {
- string val = value as string;
- if (val != null)
- {
- return val.Length > 0;
- }
- return value != null;
- }
- #endregion
- #region RoutePartsEqual
- /// <summary>
- /// Checks if two route parts are equal
- /// </summary>
- /// <param name="firstValue">The first value.</param>
- /// <param name="secondValue">The second value.</param>
- /// <returns><c>true</c> if both values are equal; otherwise,<c>false</c>.</returns>
- private bool RoutePartsEqual(object firstValue,object secondValue)
- {
- string sFirst = firstValue as string;
- string sSecond = secondValue as string;
- if ((sFirst != null) && (sSecond != null))
- {
- return string.Equals(sFirst,sSecond,StringComparison.OrdinalIgnoreCase);
- }
- if ((sFirst != null) && (sSecond != null))
- {
- return sFirst.Equals(sSecond);
- }
- return (sFirst == sSecond);
- }
- #endregion
- #endregion
- }
在上层代码中使用的另外两个类:
- /// <summary>
- /// Represents a route segment
- /// </summary>
- public class RouteSegment
- {
- /// <summary>
- /// Gets or sets segment path or token name.
- /// </summary>
- /// <value>Route segment path or token name.</value>
- public string Name { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether this segment is greedy.
- /// </summary>
- /// <value><c>true</c> if this segment is greedy; otherwise,<c>false</c>.</value>
- public bool IsGreedy { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether this segment is a token.
- /// </summary>
- /// <value><c>true</c> if this segment is a token; otherwise,<c>false</c>.</value>
- public bool IsToken { get; set; }
- }
和
- /// <summary>
- /// Represents a generated route url with route data
- /// </summary>
- public class RouteUrl
- {
- /// <summary>
- /// Gets or sets the route URL.
- /// </summary>
- /// <value>Route URL.</value>
- public string Url { get; set; }
- /// <summary>
- /// Gets or sets route values.
- /// </summary>
- /// <value>Route values.</value>
- public RouteValueDictionary Values { get; set; }
- }
这就是所有人.让我知道任何问题.