问题
We're using RavenDB on the backend and so all the DB keys are strings that contain forward slashes e.g. users/1
.
AFAIK there's no way to use the default ASP NET Web API route matching engine to match a parameter like this, without using a catch-all which means the key must be the last thing on the URL. I tried adding a regex constraint of users/d+
but it didn't seem to make a difference, the route wasn't matched.
What bits would I have to replace to do just enough custom route matching to allow this, preferably without crippling the rest of the route matching. For example, using url: "{*route}"
and a custom constraint that did full regex matching would work but may work unexpectedly with other route configurations.
If your answer comes with some sample code, so much the better.
回答1:
It seems that it is possible to do this by defining a custom route. In MVC4 (last stable released 4.0.30506.0), it is not possible to do by implementing IHttpRoute
as per specification but by defining a custom MVC-level Route
and adding it directly to the RouteTable
. For details see 1, 2. The RegexRoute
implementation below is based on the implementation here with mods from the answer here.
Define RegexRoute
:
public class RegexRoute : Route
{
private readonly Regex urlRegex;
public RegexRoute(string urlPattern, string routeTemplate, object defaults, object constraints = null)
: base(routeTemplate, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(), HttpControllerRouteHandler.Instance)
{
urlRegex = new Regex(urlPattern, RegexOptions.Compiled);
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string requestUrl = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
Match match = urlRegex.Match(requestUrl);
RouteData data = null;
if (match.Success)
{
data = new RouteData(this, RouteHandler);
// add defaults first
if (null != Defaults)
{
foreach (var def in Defaults)
{
data.Values[def.Key] = def.Value;
}
}
// iterate matching groups
for (int i = 1; i < match.Groups.Count; i++)
{
Group group = match.Groups[i];
if (group.Success)
{
string key = urlRegex.GroupNameFromNumber(i);
if (!String.IsNullOrEmpty(key) && !Char.IsNumber(key, 0)) // only consider named groups
{
data.Values[key] = group.Value;
}
}
}
}
return data;
}
}
Add this DelegatingHandler
to avoid a NullReference
due to some other bug:
public class RouteByPassingHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
HttpMessageInvoker invoker = new HttpMessageInvoker(new HttpControllerDispatcher(request.GetConfiguration()));
return invoker.SendAsync(request, cancellationToken);
}
}
Add handler and route directly to the RouteTable
:
RouteTable.Routes.Add(new RegexRoute(@"^api/home/index/(?<id>\d+)$", "test", new { controller = "Home", action = "Index" }));
config.MessageHandlers.Add(new RouteByPassingHandler());
Et voila!
EDIT: This solution has problems when the API is self-hosted (instead of using a WebHost) and requires further work to make it work with both. If this is interesting to anyone, please leave a comment and I'll post my solution.
来源:https://stackoverflow.com/questions/17856926/how-to-modify-asp-net-web-api-route-matching-to-allow-parameters-with-slashes-in