1 namespace System.Web.Routing {
2 using System.Collections.Generic;
3 using System.Collections.ObjectModel;
4 using System.Diagnostics.CodeAnalysis;
5 using System.Globalization;
6 using System.Runtime.CompilerServices;
7 using System.Threading;
8 using System.Web.Hosting;
10 [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
11 public class RouteCollection : Collection<RouteBase> {
12 private Dictionary<string, RouteBase> _namedMap = new Dictionary<string, RouteBase>(StringComparer.OrdinalIgnoreCase);
13 private VirtualPathProvider _vpp;
15 private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
17 public RouteCollection() {
20 public RouteCollection(VirtualPathProvider virtualPathProvider) {
21 VPP = virtualPathProvider;
24 public bool AppendTrailingSlash {
29 public bool LowercaseUrls {
34 public bool RouteExistingFiles {
39 private VirtualPathProvider VPP {
42 return HostingEnvironment.VirtualPathProvider;
51 public RouteBase this[string name] {
53 if (String.IsNullOrEmpty(name)) {
57 if (_namedMap.TryGetValue(name, out route)) {
64 public void Add(string name, RouteBase item) {
66 throw new ArgumentNullException("item");
69 if (!String.IsNullOrEmpty(name)) {
70 if (_namedMap.ContainsKey(name)) {
71 throw new ArgumentException(
73 CultureInfo.CurrentUICulture,
74 SR.GetString(SR.RouteCollection_DuplicateName),
81 if (!String.IsNullOrEmpty(name)) {
82 _namedMap[name] = item;
86 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
87 Justification = "Warning was suppressed for consistency with existing similar routing API")]
88 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile) {
89 return MapPageRoute(routeName, routeUrl, physicalFile, true /* checkPhysicalUrlAccess */, null, null, null);
92 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
93 Justification = "Warning was suppressed for consistency with existing similar routing API")]
94 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess) {
95 return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, null, null, null);
98 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
99 Justification = "Warning was suppressed for consistency with existing similar routing API")]
100 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults) {
101 return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, null, null);
104 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
105 Justification = "Warning was suppressed for consistency with existing similar routing API")]
106 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints) {
107 return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, constraints, null);
110 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
111 Justification = "Warning was suppressed for consistency with existing similar routing API")]
112 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) {
113 if (routeUrl == null) {
114 throw new ArgumentNullException("routeUrl");
116 Route route = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));
117 Add(routeName, route);
121 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
122 protected override void ClearItems() {
127 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not worth a breaking change.")]
128 public IDisposable GetReadLock() {
129 _rwLock.EnterReadLock();
130 return new ReadLockDisposable(_rwLock);
133 private RequestContext GetRequestContext(RequestContext requestContext) {
134 if (requestContext != null) {
135 return requestContext;
137 HttpContext httpContext = HttpContext.Current;
138 if (httpContext == null) {
139 throw new InvalidOperationException(SR.GetString(SR.RouteCollection_RequiresContext));
141 return new RequestContext(new HttpContextWrapper(httpContext), new RouteData());
144 // Returns true if this is a request to an existing file
145 private bool IsRouteToExistingFile(HttpContextBase httpContext) {
146 string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
147 return ((requestPath != "~/") &&
149 (VPP.FileExists(requestPath) ||
150 VPP.DirectoryExists(requestPath)));
153 public RouteData GetRouteData(HttpContextBase httpContext) {
154 if (httpContext == null) {
155 throw new ArgumentNullException("httpContext");
157 if (httpContext.Request == null) {
158 throw new ArgumentException(SR.GetString(SR.RouteTable_ContextMissingRequest), "httpContext");
161 // Optimize performance when the route collection is empty. The main improvement is that we avoid taking
162 // a read lock when the collection is empty. Without this check, the UrlRoutingModule causes a 25%-50%
163 // regression in HelloWorld RPS due to lock contention. The UrlRoutingModule is now in the root web.config,
164 // so we need to ensure the module is performant, especially when you are not using routing.
165 // This check does introduce a slight bug, in that if a writer clears the collection as part of a write
166 // transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent.
167 // We will investigate a better fix in Dev10 Beta2. The Beta1 bug is Dev10 652986.
172 bool isRouteToExistingFile = false;
173 bool doneRouteCheck = false; // We only want to do the route check once
174 if (!RouteExistingFiles) {
175 isRouteToExistingFile = IsRouteToExistingFile(httpContext);
176 doneRouteCheck = true;
177 if (isRouteToExistingFile) {
178 // If we're not routing existing files and the file exists, we stop processing routes
183 // Go through all the configured routes and find the first one that returns a match
184 using (GetReadLock()) {
185 foreach (RouteBase route in this) {
186 RouteData routeData = route.GetRouteData(httpContext);
187 if (routeData != null) {
188 // If we're not routing existing files on this route and the file exists, we also stop processing routes
189 if (!route.RouteExistingFiles) {
190 if (!doneRouteCheck) {
191 isRouteToExistingFile = IsRouteToExistingFile(httpContext);
192 doneRouteCheck = true;
194 if (isRouteToExistingFile) {
207 [SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)", Justification = @"okay")]
208 private string NormalizeVirtualPath(RequestContext requestContext, string virtualPath) {
209 string url = System.Web.UI.Util.GetUrlWithApplicationPath(requestContext.HttpContext, virtualPath);
211 if (LowercaseUrls || AppendTrailingSlash) {
212 int iqs = url.IndexOfAny(new char[] { '?', '#' });
216 urlWithoutQs = url.Substring(0, iqs);
217 qs = url.Substring(iqs);
224 // Don't lowercase the query string
226 urlWithoutQs = urlWithoutQs.ToLowerInvariant();
229 if (AppendTrailingSlash && !urlWithoutQs.EndsWith("/")) {
233 url = urlWithoutQs + qs;
239 public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {
240 requestContext = GetRequestContext(requestContext);
242 // Go through all the configured routes and find the first one that returns a match
243 using (GetReadLock()) {
244 foreach (RouteBase route in this) {
245 VirtualPathData vpd = route.GetVirtualPath(requestContext, values);
247 vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
256 public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) {
257 requestContext = GetRequestContext(requestContext);
259 if (!String.IsNullOrEmpty(name)) {
260 RouteBase namedRoute;
262 using (GetReadLock()) {
263 routeFound = _namedMap.TryGetValue(name, out namedRoute);
266 VirtualPathData vpd = namedRoute.GetVirtualPath(requestContext, values);
268 vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
274 throw new ArgumentException(
276 CultureInfo.CurrentUICulture,
277 SR.GetString(SR.RouteCollection_NameNotFound),
283 return GetVirtualPath(requestContext, values);
287 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not worth a breaking change.")]
288 public IDisposable GetWriteLock() {
289 _rwLock.EnterWriteLock();
290 return new WriteLockDisposable(_rwLock);
293 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
294 Justification = "This is not a regular URL as it may contain special routing characters.")]
295 public void Ignore(string url) {
296 Ignore(url, null /* constraints */);
299 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
300 Justification = "This is not a regular URL as it may contain special routing characters.")]
301 public void Ignore(string url, object constraints) {
303 throw new ArgumentNullException("url");
306 IgnoreRouteInternal route = new IgnoreRouteInternal(url) {
307 Constraints = new RouteValueDictionary(constraints)
313 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
314 protected override void InsertItem(int index, RouteBase item) {
316 throw new ArgumentNullException("item");
319 if (Contains(item)) {
320 throw new ArgumentException(
322 CultureInfo.CurrentCulture,
323 SR.GetString(SR.RouteCollection_DuplicateEntry)),
326 base.InsertItem(index, item);
329 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
330 protected override void RemoveItem(int index) {
331 RemoveRouteName(index);
332 base.RemoveItem(index);
335 private void RemoveRouteName(int index) {
336 // Search for the specified route and clear out its name if we have one
337 RouteBase route = this[index];
338 foreach (KeyValuePair<string, RouteBase> namedRoute in _namedMap) {
339 if (namedRoute.Value == route) {
340 _namedMap.Remove(namedRoute.Key);
346 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
347 protected override void SetItem(int index, RouteBase item) {
349 throw new ArgumentNullException("item");
352 if (Contains(item)) {
353 throw new ArgumentException(
355 CultureInfo.CurrentCulture,
356 SR.GetString(SR.RouteCollection_DuplicateEntry)),
359 RemoveRouteName(index);
360 base.SetItem(index, item);
363 private class ReadLockDisposable : IDisposable {
365 private ReaderWriterLockSlim _rwLock;
367 public ReadLockDisposable(ReaderWriterLockSlim rwLock) {
371 [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
372 Justification = "Type does not have a finalizer.")]
373 void IDisposable.Dispose() {
374 _rwLock.ExitReadLock();
378 private class WriteLockDisposable : IDisposable {
380 private ReaderWriterLockSlim _rwLock;
382 public WriteLockDisposable(ReaderWriterLockSlim rwLock) {
386 [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
387 Justification = "Type does not have a finalizer.")]
388 void IDisposable.Dispose() {
389 _rwLock.ExitWriteLock();
393 private sealed class IgnoreRouteInternal : Route {
394 public IgnoreRouteInternal(string url)
395 : base(url, new StopRoutingHandler()) {
398 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues) {
399 // Never match during route generation. This avoids the scenario where an IgnoreRoute with
400 // fairly relaxed constraints ends up eagerly matching all generated URLs.