// 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.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Routing;
using System.Web.Http.WebHost.Properties;
using System.Web.Routing;
namespace System.Web.Http.WebHost.Routing
{
///
/// Mimics the System.Web.Routing.Route class to work better for Web API scenarios. The only
/// difference between the base class and this class is that this one will match only when
/// a special "httproute" key is specified when generating URLs. There is no special behavior
/// for incoming URLs.
///
internal class HttpWebRoute : Route
{
///
/// Key used to signify that a route URL generation request should include HTTP routes (e.g. Web API).
/// If this key is not specified then no HTTP routes will match.
///
internal const string HttpRouteKey = "httproute";
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Matches the base class's parameter names.")]
public HttpWebRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler, IHttpRoute httpRoute)
: base(url, defaults, constraints, dataTokens, routeHandler)
{
if (httpRoute == null)
{
throw Error.ArgumentNull("httpRoute");
}
HttpRoute = httpRoute;
}
///
/// Gets the associated with this .
///
public IHttpRoute HttpRoute { get; private set; }
protected override bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// The base class will validate that a constraint is either a string or IRoutingConstraint inside its
// ProcessConstraint method. We're doing the validation up front here because we also support
// IHttpRouteConstraint and we want the error message to reflect all three valid possibilities.
ValidateConstraint(HttpRoute.RouteTemplate, parameterName, constraint);
IHttpRouteConstraint httpRouteConstraint = constraint as IHttpRouteConstraint;
if (httpRouteConstraint != null)
{
HttpRequestMessage request = httpContext.GetOrCreateHttpRequestMessage();
return httpRouteConstraint.Match(request, HttpRoute, parameterName, values, ConvertRouteDirection(routeDirection));
}
return base.ProcessConstraint(httpContext, constraint, parameterName, values, routeDirection);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Top-level catch block for unhandled routing exceptions.")]
public override RouteData GetRouteData(HttpContextBase httpContext)
{
try
{
if (HttpRoute is HostedHttpRoute)
{
return base.GetRouteData(httpContext);
}
else
{
// if user passed us a custom IHttpRoute, then we should invoke their function instead of the base
HttpRequestMessage request = httpContext.GetOrCreateHttpRequestMessage();
IHttpRouteData data = HttpRoute.GetRouteData(httpContext.Request.ApplicationPath, request);
return data == null ? null : data.ToRouteData();
}
}
catch (Exception exception)
{
// Processing an exception involves async work, and this method is synchronous.
// Instead of waiting on the async work here, it's better to return a handler that will deal with the
// exception asynchronously during its request processing method.
ExceptionDispatchInfo exceptionInfo = ExceptionDispatchInfo.Capture(exception);
return new RouteData(this, new HttpRouteExceptionRouteHandler(exceptionInfo));
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
// Only perform URL generation if the "httproute" key was specified. This allows these
// routes to be ignored when a regular MVC app tries to generate URLs. Without this special
// key an HTTP route used for Web API would normally take over almost all the routes in a
// typical app.
if (!values.ContainsKey(HttpRouteKey))
{
return null;
}
// Remove the value from the collection so that it doesn't affect the generated URL
RouteValueDictionary newValues = GetRouteDictionaryWithoutHttpRouteKey(values);
if (HttpRoute is HostedHttpRoute)
{
return base.GetVirtualPath(requestContext, newValues);
}
else
{
// if user passed us a custom IHttpRoute, then we should invoke their function instead of the base
HttpRequestMessage request = requestContext.HttpContext.GetOrCreateHttpRequestMessage();
IHttpVirtualPathData virtualPathData = HttpRoute.GetVirtualPath(request, values);
return virtualPathData == null ? null : new VirtualPathData(this, virtualPathData.VirtualPath);
}
}
private static RouteValueDictionary GetRouteDictionaryWithoutHttpRouteKey(IDictionary routeValues)
{
var newRouteValues = new RouteValueDictionary();
foreach (var routeValue in routeValues)
{
if (!String.Equals(routeValue.Key, HttpRouteKey, StringComparison.OrdinalIgnoreCase))
{
newRouteValues.Add(routeValue.Key, routeValue.Value);
}
}
return newRouteValues;
}
private static HttpRouteDirection ConvertRouteDirection(RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest)
{
return HttpRouteDirection.UriResolution;
}
if (routeDirection == RouteDirection.UrlGeneration)
{
return HttpRouteDirection.UriGeneration;
}
throw Error.InvalidEnumArgument("routeDirection", (int)routeDirection, typeof(RouteDirection));
}
// Validates that this constraint is of a type that HttpWebRoute can process. This is not valid to
// call when a route inherits from HttpWebRoute - as the derived class can handle any types of
// constraints it wants to support.
internal static void ValidateConstraint(string routeTemplate, string name, object constraint)
{
if (constraint is IHttpRouteConstraint)
{
return;
}
// This validation is repeated in the call to base.ProcessConstraint, but if we do it here we can give a
// better error message. base.ProcessConstraint doesn't handle IHttpRouteConstraint, but this class does.
if (constraint is IRouteConstraint)
{
return;
}
if (constraint is string)
{
return;
}
throw CreateInvalidConstraintTypeException(routeTemplate, name);
}
private static Exception CreateInvalidConstraintTypeException(string routeTemplate, string name)
{
return Error.InvalidOperation(
SRResources.Route_ValidationMustBeStringOrCustomConstraint,
name,
routeTemplate,
typeof(IHttpRouteConstraint).FullName,
typeof(IRouteConstraint).FullName);
}
}
}