gpt4 book ai didi

asp.net-mvc - Asp.net MVC Route 类,支持 URL 中任何位置的全部参数

转载 作者:行者123 更新时间:2023-12-04 05:59:55 26 4
gpt4 key购买 nike

我想得越多,我就越相信可以编写一个使用这些 URL 定义的自定义路由:

{var1}/{var2}/{var3}
Const/{var1}/{var2}
Const1/{var1}/Const2/{var2}
{var1}/{var2}/Const

以及 最多 在任何上面的 URL 中的任何位置上的一个贪婪参数,如
{*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()应该像这样工作:
  • 将请求拆分为段:
  • 显示
  • 话题
  • 副主题
  • 子主题
  • 123
  • 这是一个例子
  • 从左侧开始处理路由 URL 定义并将单个段值分配给参数(或将请求段值匹配到静态路由常量段)。
  • 当路由段定义为贪婪时,反向解析并转到最后一个段。
  • 一一向后解析路由段(为它们分配请求值),直到您再次获得贪婪的全能。
  • 当您再次到达 greedy 时,加入所有剩余的请求段(按原始顺序)并将它们分配给 greedy catch-all 路由参数。

  • 问题

    据我所知,它可以工作。但我想知道:
  • 有没有人已经写过这个,所以我不必(因为还有其他我没有提到的解析方面(约束、默认值等)
  • 你看到这个算法有什么缺陷吗,因为如果到目前为止没有人这样做,我将不得不自己编写它。

  • 我还没想过 GetVirtuaPath()方法。

    最佳答案

    最近问的比较急,一般都是自己解决问题。很抱歉,但这是我对我所询问的那种路线的看法。任何人发现它有任何问题:让我知道。

    在 URL 中的任何位置使用全能段路由

    /// <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, null, 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, IRouteHandler routeHandler)
    : this(url, defaults, null, null, routeHandler)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="VivaRoute"/> class, using the specified URL pattern, handler class, 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 defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
    : this(url, defaults, constraints, null, routeHandler)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="VivaRoute"/> class, using the specified URL pattern, handler class, default parameter values, 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 defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
    : base(url.Replace("*", ""), defaults, constraints, 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, values, 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; }
    }

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

    我还写了一个 blog post与此自定义路由类相关。它非常详细地解释了一切。

    关于asp.net-mvc - Asp.net MVC Route 类,支持 URL 中任何位置的全部参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2378222/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com