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
;
85 // RouteBase doesn't have handler info, so we only log Route.RouteHandler
86 var route
= item
as Route
;
87 if (route
!= null && route
.RouteHandler
!= null) {
88 TelemetryLogger
.LogHttpHandler(route
.RouteHandler
.GetType());
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
) {
95 return MapPageRoute(routeName
, routeUrl
, physicalFile
, true /* 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
) {
101 return MapPageRoute(routeName
, routeUrl
, physicalFile
, checkPhysicalUrlAccess
, null, 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
) {
107 return MapPageRoute(routeName
, routeUrl
, physicalFile
, checkPhysicalUrlAccess
, defaults
, null, 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
) {
113 return MapPageRoute(routeName
, routeUrl
, physicalFile
, checkPhysicalUrlAccess
, defaults
, constraints
, null);
116 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
117 Justification
= "Warning was suppressed for consistency with existing similar routing API")]
118 public Route
MapPageRoute(string routeName
, string routeUrl
, string physicalFile
, bool checkPhysicalUrlAccess
, RouteValueDictionary defaults
, RouteValueDictionary constraints
, RouteValueDictionary dataTokens
) {
119 if (routeUrl
== null) {
120 throw new ArgumentNullException("routeUrl");
122 Route route
= new Route(routeUrl
, defaults
, constraints
, dataTokens
, new PageRouteHandler(physicalFile
, checkPhysicalUrlAccess
));
123 Add(routeName
, route
);
127 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
128 protected override void ClearItems() {
133 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification
= "Not worth a breaking change.")]
134 public IDisposable
GetReadLock() {
135 _rwLock
.EnterReadLock();
136 return new ReadLockDisposable(_rwLock
);
139 private RequestContext
GetRequestContext(RequestContext requestContext
) {
140 if (requestContext
!= null) {
141 return requestContext
;
143 HttpContext httpContext
= HttpContext
.Current
;
144 if (httpContext
== null) {
145 throw new InvalidOperationException(SR
.GetString(SR
.RouteCollection_RequiresContext
));
147 return new RequestContext(new HttpContextWrapper(httpContext
), new RouteData());
150 // Returns true if this is a request to an existing file
151 private bool IsRouteToExistingFile(HttpContextBase httpContext
) {
152 string requestPath
= httpContext
.Request
.AppRelativeCurrentExecutionFilePath
;
153 return ((requestPath
!= "~/") &&
155 (VPP
.FileExists(requestPath
) ||
156 VPP
.DirectoryExists(requestPath
)));
159 public RouteData
GetRouteData(HttpContextBase httpContext
) {
160 if (httpContext
== null) {
161 throw new ArgumentNullException("httpContext");
163 if (httpContext
.Request
== null) {
164 throw new ArgumentException(SR
.GetString(SR
.RouteTable_ContextMissingRequest
), "httpContext");
167 // Optimize performance when the route collection is empty. The main improvement is that we avoid taking
168 // a read lock when the collection is empty. Without this check, the UrlRoutingModule causes a 25%-50%
169 // regression in HelloWorld RPS due to lock contention. The UrlRoutingModule is now in the root web.config,
170 // so we need to ensure the module is performant, especially when you are not using routing.
171 // This check does introduce a slight bug, in that if a writer clears the collection as part of a write
172 // transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent.
173 // We will investigate a better fix in Dev10 Beta2. The Beta1 bug is Dev10 652986.
178 bool isRouteToExistingFile
= false;
179 bool doneRouteCheck
= false; // We only want to do the route check once
180 if (!RouteExistingFiles
) {
181 isRouteToExistingFile
= IsRouteToExistingFile(httpContext
);
182 doneRouteCheck
= true;
183 if (isRouteToExistingFile
) {
184 // If we're not routing existing files and the file exists, we stop processing routes
189 // Go through all the configured routes and find the first one that returns a match
190 using (GetReadLock()) {
191 foreach (RouteBase route
in this) {
192 RouteData routeData
= route
.GetRouteData(httpContext
);
193 if (routeData
!= null) {
194 // If we're not routing existing files on this route and the file exists, we also stop processing routes
195 if (!route
.RouteExistingFiles
) {
196 if (!doneRouteCheck
) {
197 isRouteToExistingFile
= IsRouteToExistingFile(httpContext
);
198 doneRouteCheck
= true;
200 if (isRouteToExistingFile
) {
213 [SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId
= "System.String.EndsWith(System.String)", Justification
= @"okay")]
214 private string NormalizeVirtualPath(RequestContext requestContext
, string virtualPath
) {
215 string url
= System
.Web
.UI
.Util
.GetUrlWithApplicationPath(requestContext
.HttpContext
, virtualPath
);
217 if (LowercaseUrls
|| AppendTrailingSlash
) {
218 int iqs
= url
.IndexOfAny(new char[] { '?', '#' }
);
222 urlWithoutQs
= url
.Substring(0, iqs
);
223 qs
= url
.Substring(iqs
);
230 // Don't lowercase the query string
232 urlWithoutQs
= urlWithoutQs
.ToLowerInvariant();
235 if (AppendTrailingSlash
&& !urlWithoutQs
.EndsWith("/")) {
239 url
= urlWithoutQs
+ qs
;
245 public VirtualPathData
GetVirtualPath(RequestContext requestContext
, RouteValueDictionary values
) {
246 requestContext
= GetRequestContext(requestContext
);
248 // Go through all the configured routes and find the first one that returns a match
249 using (GetReadLock()) {
250 foreach (RouteBase route
in this) {
251 VirtualPathData vpd
= route
.GetVirtualPath(requestContext
, values
);
253 vpd
.VirtualPath
= NormalizeVirtualPath(requestContext
, vpd
.VirtualPath
);
262 public VirtualPathData
GetVirtualPath(RequestContext requestContext
, string name
, RouteValueDictionary values
) {
263 requestContext
= GetRequestContext(requestContext
);
265 if (!String
.IsNullOrEmpty(name
)) {
266 RouteBase namedRoute
;
268 using (GetReadLock()) {
269 routeFound
= _namedMap
.TryGetValue(name
, out namedRoute
);
272 VirtualPathData vpd
= namedRoute
.GetVirtualPath(requestContext
, values
);
274 vpd
.VirtualPath
= NormalizeVirtualPath(requestContext
, vpd
.VirtualPath
);
280 throw new ArgumentException(
282 CultureInfo
.CurrentUICulture
,
283 SR
.GetString(SR
.RouteCollection_NameNotFound
),
289 return GetVirtualPath(requestContext
, values
);
293 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification
= "Not worth a breaking change.")]
294 public IDisposable
GetWriteLock() {
295 _rwLock
.EnterWriteLock();
296 return new WriteLockDisposable(_rwLock
);
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
) {
302 Ignore(url
, null /* constraints */);
305 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
306 Justification
= "This is not a regular URL as it may contain special routing characters.")]
307 public void Ignore(string url
, object constraints
) {
309 throw new ArgumentNullException("url");
312 IgnoreRouteInternal route
= new IgnoreRouteInternal(url
) {
313 Constraints
= new RouteValueDictionary(constraints
)
319 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
320 protected override void InsertItem(int index
, RouteBase item
) {
322 throw new ArgumentNullException("item");
325 if (Contains(item
)) {
326 throw new ArgumentException(
328 CultureInfo
.CurrentCulture
,
329 SR
.GetString(SR
.RouteCollection_DuplicateEntry
)),
332 base.InsertItem(index
, item
);
335 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
336 protected override void RemoveItem(int index
) {
337 RemoveRouteName(index
);
338 base.RemoveItem(index
);
341 private void RemoveRouteName(int index
) {
342 // Search for the specified route and clear out its name if we have one
343 RouteBase route
= this[index
];
344 foreach (KeyValuePair
<string, RouteBase
> namedRoute
in _namedMap
) {
345 if (namedRoute
.Value
== route
) {
346 _namedMap
.Remove(namedRoute
.Key
);
352 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
353 protected override void SetItem(int index
, RouteBase item
) {
355 throw new ArgumentNullException("item");
358 if (Contains(item
)) {
359 throw new ArgumentException(
361 CultureInfo
.CurrentCulture
,
362 SR
.GetString(SR
.RouteCollection_DuplicateEntry
)),
365 RemoveRouteName(index
);
366 base.SetItem(index
, item
);
369 private class ReadLockDisposable
: IDisposable
{
371 private ReaderWriterLockSlim _rwLock
;
373 public ReadLockDisposable(ReaderWriterLockSlim rwLock
) {
377 [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
378 Justification
= "Type does not have a finalizer.")]
379 void IDisposable
.Dispose() {
380 _rwLock
.ExitReadLock();
384 private class WriteLockDisposable
: IDisposable
{
386 private ReaderWriterLockSlim _rwLock
;
388 public WriteLockDisposable(ReaderWriterLockSlim rwLock
) {
392 [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
393 Justification
= "Type does not have a finalizer.")]
394 void IDisposable
.Dispose() {
395 _rwLock
.ExitWriteLock();
399 private sealed class IgnoreRouteInternal
: Route
{
400 public IgnoreRouteInternal(string url
)
401 : base(url
, new StopRoutingHandler()) {
404 public override VirtualPathData
GetVirtualPath(RequestContext requestContext
, RouteValueDictionary routeValues
) {
405 // Never match during route generation. This avoids the scenario where an IgnoreRoute with
406 // fairly relaxed constraints ends up eagerly matching all generated URLs.