Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Web / Routing / RouteCollection.cs
blob0b6bf9cfd0e59bfc20b16789342e4573c924ec63
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 {
25 get;
26 set;
29 public bool LowercaseUrls {
30 get;
31 set;
34 public bool RouteExistingFiles {
35 get;
36 set;
39 private VirtualPathProvider VPP {
40 get {
41 if (_vpp == null) {
42 return HostingEnvironment.VirtualPathProvider;
44 return _vpp;
46 set {
47 _vpp = value;
51 public RouteBase this[string name] {
52 get {
53 if (String.IsNullOrEmpty(name)) {
54 return null;
56 RouteBase route;
57 if (_namedMap.TryGetValue(name, out route)) {
58 return route;
60 return null;
64 public void Add(string name, RouteBase item) {
65 if (item == null) {
66 throw new ArgumentNullException("item");
69 if (!String.IsNullOrEmpty(name)) {
70 if (_namedMap.ContainsKey(name)) {
71 throw new ArgumentException(
72 String.Format(
73 CultureInfo.CurrentUICulture,
74 SR.GetString(SR.RouteCollection_DuplicateName),
75 name),
76 "name");
80 Add(item);
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);
124 return route;
127 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
128 protected override void ClearItems() {
129 _namedMap.Clear();
130 base.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 != "~/") &&
154 (VPP != null) &&
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.
174 if (Count == 0) {
175 return null;
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
185 return null;
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) {
201 return null;
204 return routeData;
209 return null;
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[] { '?', '#' });
219 string urlWithoutQs;
220 string qs;
221 if (iqs >= 0) {
222 urlWithoutQs = url.Substring(0, iqs);
223 qs = url.Substring(iqs);
225 else {
226 urlWithoutQs = url;
227 qs = "";
230 // Don't lowercase the query string
231 if (LowercaseUrls) {
232 urlWithoutQs = urlWithoutQs.ToLowerInvariant();
235 if (AppendTrailingSlash && !urlWithoutQs.EndsWith("/")) {
236 urlWithoutQs += "/";
239 url = urlWithoutQs + qs;
242 return url;
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);
252 if (vpd != null) {
253 vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
254 return vpd;
259 return null;
262 public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) {
263 requestContext = GetRequestContext(requestContext);
265 if (!String.IsNullOrEmpty(name)) {
266 RouteBase namedRoute;
267 bool routeFound;
268 using (GetReadLock()) {
269 routeFound = _namedMap.TryGetValue(name, out namedRoute);
271 if (routeFound) {
272 VirtualPathData vpd = namedRoute.GetVirtualPath(requestContext, values);
273 if (vpd != null) {
274 vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
275 return vpd;
277 return null;
279 else {
280 throw new ArgumentException(
281 String.Format(
282 CultureInfo.CurrentUICulture,
283 SR.GetString(SR.RouteCollection_NameNotFound),
284 name),
285 "name");
288 else {
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) {
308 if (url == null) {
309 throw new ArgumentNullException("url");
312 IgnoreRouteInternal route = new IgnoreRouteInternal(url) {
313 Constraints = new RouteValueDictionary(constraints)
316 Add(route);
319 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
320 protected override void InsertItem(int index, RouteBase item) {
321 if (item == null) {
322 throw new ArgumentNullException("item");
325 if (Contains(item)) {
326 throw new ArgumentException(
327 String.Format(
328 CultureInfo.CurrentCulture,
329 SR.GetString(SR.RouteCollection_DuplicateEntry)),
330 "item");
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);
347 break;
352 [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
353 protected override void SetItem(int index, RouteBase item) {
354 if (item == null) {
355 throw new ArgumentNullException("item");
358 if (Contains(item)) {
359 throw new ArgumentException(
360 String.Format(
361 CultureInfo.CurrentCulture,
362 SR.GetString(SR.RouteCollection_DuplicateEntry)),
363 "item");
365 RemoveRouteName(index);
366 base.SetItem(index, item);
369 private class ReadLockDisposable : IDisposable {
371 private ReaderWriterLockSlim _rwLock;
373 public ReadLockDisposable(ReaderWriterLockSlim rwLock) {
374 _rwLock = 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) {
389 _rwLock = 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.
407 return null;