我越是想到它越多我认为可以编写一个使用这些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; } }
这就是所有人.让我知道任何问题.