如何在MVC中定义自定义路由时构建路由表

时间:2016-07-19 16:50:27

标签: asp.net-mvc c#-4.0 asp.net-mvc-routing

使用RouteConfig类的RegisterRoutes方法中的routes.add定义自定义路由。

routes.Add("Default", new ABCRoute("{*url}",
                  new
                  {
                      languageCulture = "en",
                      pos = "uk",
                      area = defArea,
                      controller = "XXX",
                      action = "YYY",
                      id = UrlParameter.Optional
                  },
                 new { languageCulture = @Supportedlang , pos = @SupportedPos },
                  null, new MvcRouteHandler()));

ABCRoute是一个继承Route类的类。

ABCClass类似于以下链接中的GreedyClass,如下所示。 http://erraticdev.blogspot.com/2011/01/custom-aspnet-mvc-route-class-with.html

on对应用程序的第一个请求,调用带有参数的构造函数,它不返回任何内容。

当我们调用上面给出的Route.Add()方法时,如何定义路由并构建路由表。

路由表如何具有所有可能的组合,因为路由defied是动态的而不是静态的。

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

1 个答案:

答案 0 :(得分:0)

  

路由表如何具有所有可能的组合,因为路由defied是动态的而不是静态的。

路由在静态 RouteTable.Routes表中定义。因此,它们仅在应用程序初始化时实例化。您可以在构造函数中做的唯一事情是保存值和服务,可以在运行时用于评估。

在运行时,构造函数从不运行。路由表从第一条路径到最后一条路径进行评估。但是,当找到匹配的路由时,路由将使用该路由而不再继续。换句话说,第一场比赛获胜。

考虑这个例子:

routes.Add(new ABCRoute());
routes.Add(new XYZRoute());

MVC将总是首先尝试匹配ABCRoute,如果ABCRoute无法匹配,则XYZRoute接下来匹配。

如果路线返回非空值,则该路线被视为匹配。因此,如果路由中的逻辑确定它不应该处理请求,那么它必须始终返回null。这将使路由框架测试为匹配注册的下一个路由。

如果路由匹配,则应返回路由值,告诉MVC要查找哪个控制器和操作(以及任何参数)。

  

注意:基于ABCRoute继承GreedyRoute的事实,您不能假设任何 ABCRoute,因为//method to generate signature //$this->method = "GET" //QBO_SANDBOX_URL = 'https://some_url.com/' //$this->_query = 'something=something' public function generate_signature() { $base = $this->_method.'&'.rawurlencode($this->_url.QBO_SANDBOX_URL.'v3/company/'.$this->_realm_id).'&' .rawurlencode("oauth_consumer_key=".rawurlencode($this->_consumer_key).'&' .'&oauth_nonce='.rawurlencode('34604g54654y456546') .'&oauth_signature_method='.rawurlencode('HMAC-SHA1') .'&oauth_timestamp='.rawurlencode(time()) .'&oauth_token='.rawurlencode($this->_auth_token) .'&oauth_version='.rawurlencode('1.0') .'&'.rawurlencode($this->_query)); $key = rawurlencode($this->_consumer_secret.'&'.$this->_token_secret); $this->_signature = base64_encode(hash_hmac("sha1", $base, $key, true)); } }可以完全重新定义它的超类行为。