1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
|
namespace System.Web.Routing {
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Web.Hosting;
[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public class RouteCollection : Collection<RouteBase> {
private Dictionary<string, RouteBase> _namedMap = new Dictionary<string, RouteBase>(StringComparer.OrdinalIgnoreCase);
private VirtualPathProvider _vpp;
private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public RouteCollection() {
}
public RouteCollection(VirtualPathProvider virtualPathProvider) {
VPP = virtualPathProvider;
}
public bool AppendTrailingSlash {
get;
set;
}
public bool LowercaseUrls {
get;
set;
}
public bool RouteExistingFiles {
get;
set;
}
private VirtualPathProvider VPP {
get {
if (_vpp == null) {
return HostingEnvironment.VirtualPathProvider;
}
return _vpp;
}
set {
_vpp = value;
}
}
public RouteBase this[string name] {
get {
if (String.IsNullOrEmpty(name)) {
return null;
}
RouteBase route;
if (_namedMap.TryGetValue(name, out route)) {
return route;
}
return null;
}
}
public void Add(string name, RouteBase item) {
if (item == null) {
throw new ArgumentNullException("item");
}
if (!String.IsNullOrEmpty(name)) {
if (_namedMap.ContainsKey(name)) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentUICulture,
SR.GetString(SR.RouteCollection_DuplicateName),
name),
"name");
}
}
Add(item);
if (!String.IsNullOrEmpty(name)) {
_namedMap[name] = item;
}
// RouteBase doesn't have handler info, so we only log Route.RouteHandler
var route = item as Route;
if (route != null && route.RouteHandler != null) {
TelemetryLogger.LogHttpHandler(route.RouteHandler.GetType());
}
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar routing API")]
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile) {
return MapPageRoute(routeName, routeUrl, physicalFile, true /* checkPhysicalUrlAccess */, null, null, null);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar routing API")]
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess) {
return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, null, null, null);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar routing API")]
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults) {
return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, null, null);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar routing API")]
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints) {
return MapPageRoute(routeName, routeUrl, physicalFile, checkPhysicalUrlAccess, defaults, constraints, null);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Warning was suppressed for consistency with existing similar routing API")]
public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens) {
if (routeUrl == null) {
throw new ArgumentNullException("routeUrl");
}
Route route = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));
Add(routeName, route);
return route;
}
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
protected override void ClearItems() {
_namedMap.Clear();
base.ClearItems();
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not worth a breaking change.")]
public IDisposable GetReadLock() {
_rwLock.EnterReadLock();
return new ReadLockDisposable(_rwLock);
}
private RequestContext GetRequestContext(RequestContext requestContext) {
if (requestContext != null) {
return requestContext;
}
HttpContext httpContext = HttpContext.Current;
if (httpContext == null) {
throw new InvalidOperationException(SR.GetString(SR.RouteCollection_RequiresContext));
}
return new RequestContext(new HttpContextWrapper(httpContext), new RouteData());
}
// Returns true if this is a request to an existing file
private bool IsRouteToExistingFile(HttpContextBase httpContext) {
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
return ((requestPath != "~/") &&
(VPP != null) &&
(VPP.FileExists(requestPath) ||
VPP.DirectoryExists(requestPath)));
}
public RouteData GetRouteData(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
if (httpContext.Request == null) {
throw new ArgumentException(SR.GetString(SR.RouteTable_ContextMissingRequest), "httpContext");
}
// Optimize performance when the route collection is empty. The main improvement is that we avoid taking
// a read lock when the collection is empty. Without this check, the UrlRoutingModule causes a 25%-50%
// regression in HelloWorld RPS due to lock contention. The UrlRoutingModule is now in the root web.config,
// so we need to ensure the module is performant, especially when you are not using routing.
// This check does introduce a slight bug, in that if a writer clears the collection as part of a write
// transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent.
// We will investigate a better fix in Dev10 Beta2. The Beta1 bug is Dev10 652986.
if (Count == 0) {
return null;
}
bool isRouteToExistingFile = false;
bool doneRouteCheck = false; // We only want to do the route check once
if (!RouteExistingFiles) {
isRouteToExistingFile = IsRouteToExistingFile(httpContext);
doneRouteCheck = true;
if (isRouteToExistingFile) {
// If we're not routing existing files and the file exists, we stop processing routes
return null;
}
}
// Go through all the configured routes and find the first one that returns a match
using (GetReadLock()) {
foreach (RouteBase route in this) {
RouteData routeData = route.GetRouteData(httpContext);
if (routeData != null) {
// If we're not routing existing files on this route and the file exists, we also stop processing routes
if (!route.RouteExistingFiles) {
if (!doneRouteCheck) {
isRouteToExistingFile = IsRouteToExistingFile(httpContext);
doneRouteCheck = true;
}
if (isRouteToExistingFile) {
return null;
}
}
return routeData;
}
}
}
return null;
}
[SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)", Justification = @"okay")]
private string NormalizeVirtualPath(RequestContext requestContext, string virtualPath) {
string url = System.Web.UI.Util.GetUrlWithApplicationPath(requestContext.HttpContext, virtualPath);
if (LowercaseUrls || AppendTrailingSlash) {
int iqs = url.IndexOfAny(new char[] { '?', '#' });
string urlWithoutQs;
string qs;
if (iqs >= 0) {
urlWithoutQs = url.Substring(0, iqs);
qs = url.Substring(iqs);
}
else {
urlWithoutQs = url;
qs = "";
}
// Don't lowercase the query string
if (LowercaseUrls) {
urlWithoutQs = urlWithoutQs.ToLowerInvariant();
}
if (AppendTrailingSlash && !urlWithoutQs.EndsWith("/")) {
urlWithoutQs += "/";
}
url = urlWithoutQs + qs;
}
return url;
}
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {
requestContext = GetRequestContext(requestContext);
// Go through all the configured routes and find the first one that returns a match
using (GetReadLock()) {
foreach (RouteBase route in this) {
VirtualPathData vpd = route.GetVirtualPath(requestContext, values);
if (vpd != null) {
vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
return vpd;
}
}
}
return null;
}
public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) {
requestContext = GetRequestContext(requestContext);
if (!String.IsNullOrEmpty(name)) {
RouteBase namedRoute;
bool routeFound;
using (GetReadLock()) {
routeFound = _namedMap.TryGetValue(name, out namedRoute);
}
if (routeFound) {
VirtualPathData vpd = namedRoute.GetVirtualPath(requestContext, values);
if (vpd != null) {
vpd.VirtualPath = NormalizeVirtualPath(requestContext, vpd.VirtualPath);
return vpd;
}
return null;
}
else {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentUICulture,
SR.GetString(SR.RouteCollection_NameNotFound),
name),
"name");
}
}
else {
return GetVirtualPath(requestContext, values);
}
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not worth a breaking change.")]
public IDisposable GetWriteLock() {
_rwLock.EnterWriteLock();
return new WriteLockDisposable(_rwLock);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "This is not a regular URL as it may contain special routing characters.")]
public void Ignore(string url) {
Ignore(url, null /* constraints */);
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "This is not a regular URL as it may contain special routing characters.")]
public void Ignore(string url, object constraints) {
if (url == null) {
throw new ArgumentNullException("url");
}
IgnoreRouteInternal route = new IgnoreRouteInternal(url) {
Constraints = new RouteValueDictionary(constraints)
};
Add(route);
}
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
protected override void InsertItem(int index, RouteBase item) {
if (item == null) {
throw new ArgumentNullException("item");
}
if (Contains(item)) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
SR.GetString(SR.RouteCollection_DuplicateEntry)),
"item");
}
base.InsertItem(index, item);
}
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
protected override void RemoveItem(int index) {
RemoveRouteName(index);
base.RemoveItem(index);
}
private void RemoveRouteName(int index) {
// Search for the specified route and clear out its name if we have one
RouteBase route = this[index];
foreach (KeyValuePair<string, RouteBase> namedRoute in _namedMap) {
if (namedRoute.Value == route) {
_namedMap.Remove(namedRoute.Key);
break;
}
}
}
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
protected override void SetItem(int index, RouteBase item) {
if (item == null) {
throw new ArgumentNullException("item");
}
if (Contains(item)) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
SR.GetString(SR.RouteCollection_DuplicateEntry)),
"item");
}
RemoveRouteName(index);
base.SetItem(index, item);
}
private class ReadLockDisposable : IDisposable {
private ReaderWriterLockSlim _rwLock;
public ReadLockDisposable(ReaderWriterLockSlim rwLock) {
_rwLock = rwLock;
}
[SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
Justification = "Type does not have a finalizer.")]
void IDisposable.Dispose() {
_rwLock.ExitReadLock();
}
}
private class WriteLockDisposable : IDisposable {
private ReaderWriterLockSlim _rwLock;
public WriteLockDisposable(ReaderWriterLockSlim rwLock) {
_rwLock = rwLock;
}
[SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly",
Justification = "Type does not have a finalizer.")]
void IDisposable.Dispose() {
_rwLock.ExitWriteLock();
}
}
private sealed class IgnoreRouteInternal : Route {
public IgnoreRouteInternal(string url)
: base(url, new StopRoutingHandler()) {
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues) {
// Never match during route generation. This avoids the scenario where an IgnoreRoute with
// fairly relaxed constraints ends up eagerly matching all generated URLs.
return null;
}
}
}
}
|