// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Hosting; using System.Web.Http.Routing; using System.Web.Http.WebHost.Properties; using System.Web.Routing; namespace System.Web.Http.WebHost.Routing { internal class HostedHttpRouteCollection : HttpRouteCollection { private readonly RouteCollection _routeCollection; private readonly string _virtualPathRoot; public HostedHttpRouteCollection(RouteCollection routeCollection) : this(routeCollection, virtualPathRoot: null) { } public HostedHttpRouteCollection(RouteCollection routeCollection, string virtualPathRoot) { if (routeCollection == null) { throw Error.ArgumentNull("routeCollection"); } _routeCollection = routeCollection; _virtualPathRoot = virtualPathRoot; } /// public override string VirtualPathRoot { get { if (_virtualPathRoot == null) { return HostingEnvironment.ApplicationVirtualPath; } else { return _virtualPathRoot; } } } /// public override int Count { get { return _routeCollection.Count; } } /// public override IHttpRoute this[string name] { get { HttpWebRoute route = _routeCollection[name] as HttpWebRoute; if (route != null) { return route.HttpRoute; } throw Error.KeyNotFound(); } } /// public override IHttpRoute this[int index] { get { HttpWebRoute route = _routeCollection[index] as HttpWebRoute; if (route != null) { return route.HttpRoute; } throw Error.ArgumentOutOfRange("index", index, SRResources.RouteCollectionOutOfRange); } } /// public override IHttpRouteData GetRouteData(HttpRequestMessage request) { if (request == null) { throw Error.ArgumentNull("request"); } HttpContextBase httpContextBase = request.GetHttpContext(); if (httpContextBase == null) { httpContextBase = new HttpRequestMessageContextWrapper(VirtualPathRoot, request); } if (httpContextBase.GetHttpRequestMessage() == null) { httpContextBase.SetHttpRequestMessage(request); } RouteData routeData = _routeCollection.GetRouteData(httpContextBase); // If the match is from an IgnoreRoute, do not return a RouteData but return a null, which will be treated as a 404 NoRouteMatched. if (routeData != null && !(routeData.RouteHandler is System.Web.Routing.StopRoutingHandler)) { return new HostedHttpRouteData(routeData); } return null; } /// public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, string name, IDictionary values) { if (request == null) { throw Error.ArgumentNull("request"); } HttpContextBase httpContextBase = request.GetHttpContext(); if (httpContextBase == null) { httpContextBase = new HttpRequestMessageContextWrapper(VirtualPathRoot, request); } if (httpContextBase.GetHttpRequestMessage() == null) { httpContextBase.SetHttpRequestMessage(request); } IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { return null; } RequestContext requestContext = new RequestContext(httpContextBase, routeData.ToRouteData()); RouteValueDictionary routeValues = values != null ? new RouteValueDictionary(values) : new RouteValueDictionary(); VirtualPathData virtualPathData = _routeCollection.GetVirtualPath(requestContext, name, routeValues); if (virtualPathData != null) { // If the route is not an HttpWebRoute, try getting a virtual path without the httproute key in the route value dictionary // This ensures that httproute isn't picked up by non-WebAPI routes that might pollute the virtual path with httproute if (!(virtualPathData.Route is HttpWebRoute)) { if (routeValues.Remove(HttpWebRoute.HttpRouteKey)) { VirtualPathData virtualPathDataWithoutHttpRouteValue = _routeCollection.GetVirtualPath(requestContext, name, routeValues); if (virtualPathDataWithoutHttpRouteValue != null) { virtualPathData = virtualPathDataWithoutHttpRouteValue; } } } return new HostedHttpVirtualPathData(virtualPathData, routeData.Route); } return null; } /// public override IHttpRoute CreateRoute(string uriTemplate, IDictionary defaults, IDictionary constraints, IDictionary dataTokens, HttpMessageHandler handler) { if (constraints != null) { foreach (var constraint in constraints) { ValidateConstraint(uriTemplate, constraint.Key, constraint.Value); } } return new HostedHttpRoute(uriTemplate, defaults, constraints, dataTokens, handler); } /// protected override void ValidateConstraint(string routeTemplate, string name, object constraint) { // In WebHost the constraint might be IHttpRouteConstraint or IRouteConstraint (System.Web) or a string HttpWebRoute.ValidateConstraint(routeTemplate, name, constraint); } /// public override void Add(string name, IHttpRoute route) { _routeCollection.Add(name, route.ToRoute()); } /// public override void Clear() { _routeCollection.Clear(); } /// public override bool Contains(IHttpRoute item) { foreach (RouteBase route in _routeCollection) { HttpWebRoute webRoute = route as HttpWebRoute; if (webRoute != null && webRoute.HttpRoute == item) { return true; } } return false; } /// public override bool ContainsKey(string name) { return _routeCollection[name] != null; } /// public override void CopyTo(IHttpRoute[] array, int arrayIndex) { throw NotSupportedByHostedRouteCollection(); } /// public override void CopyTo(KeyValuePair[] array, int arrayIndex) { throw NotSupportedByRouteCollection(); } /// public override void Insert(int index, string name, IHttpRoute value) { throw NotSupportedByRouteCollection(); } /// public override bool Remove(string name) { throw NotSupportedByRouteCollection(); } /// public override IEnumerator GetEnumerator() { // Here we only care about Web API routes. return _routeCollection .OfType() .Select(httpWebRoute => httpWebRoute.HttpRoute) .GetEnumerator(); } /// public override bool TryGetValue(string name, out IHttpRoute route) { HttpWebRoute rt = _routeCollection[name] as HttpWebRoute; if (rt != null) { route = rt.HttpRoute; return true; } route = null; return false; } private static NotSupportedException NotSupportedByRouteCollection() { return Error.NotSupported(SRResources.RouteCollectionNotSupported, typeof(HostedHttpRouteCollection).Name); } private static NotSupportedException NotSupportedByHostedRouteCollection() { return Error.NotSupported(SRResources.RouteCollectionUseDirectly, typeof(RouteCollection).Name); } } }