Routing & message handlers: request processing ord

2019-05-09 11:32发布

I'm facing an issue with the execution order of the ASP.NET Web API request pipeline.

According to the ASP.NET Web API documentation (available here), global message handlers are supposed to be executed before the routing mechanism.

Request pipeline

On this image, MessageHandler1 is a global message handler whereas MessageHandler2 is specific to Route 2.


I created a very simple example to show that there seems to be an issue in the execution order… or I'm really missing something important.

I have this controller

public class FooController : ApiController {
    [HttpPut]
    public string PutMe() {
        return Request.Method.Method;
    }
}

It only accepts PUT requests.

The application is configured as such:

protected void Application_Start() {
    var configuration = GlobalConfiguration.Configuration;

    configuration.MessageHandlers.Add( new SimpleMethodOverrideHandler() );
    configuration.Configuration.Routes.MapHttpRoute(
        name: "Foo",
        routeTemplate: "api/foo",
        defaults: new { controller = "foo", action = "putme" },
        constraints: new { put = new HttpPutOnlyConstraint() }
    );
}

SimpleMethodOverrideHandler is a very simple DelegatingHandler that just changed the request's method according to a "method" parameter in the query string.

public class SimpleMethodOverrideHandler : DelegatingHandler {
    protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) {
        var method = request.RequestUri.ParseQueryString()["method"];
        if( !string.IsNullOrEmpty( method ) ) {
            request.Method = new HttpMethod( method );
        }
        return base.SendAsync( request, cancellationToken );
    }
}

So basically, requesting /api/foo?method=put in my browser would fire up FooController's PutMe method.
Indeed, as seen earlier, the message handler treats the requests before it gets passed to the HttpRoutingDispatched.

Finally, here's how the the constaint HttpPutOnlyConstraint is defined:

public class HttpPutOnlyConstraint : IHttpRouteConstraint {
    public bool Match( HttpRequestMessage request,
                       IHttpRoute route,
                       string parameterName,
                       IDictionary<string, object> values,
                       HttpRouteDirection routeDirection ) {
        return request.Method == HttpMethod.Put;
    }
}

Well the problem is that when I request /api/foo?method=put within my browser, the program first enters HttpPutOnlyConstraint's Match method, which is wrong.

If we refer to the previously linked image, the message handler is supposed to be executed first, unfortunately it is not.

So, of course, Match returns false and no controller/action is found for the request, 404 happens.

If I remove the constraint from the route definition, the program enters SimpleMethodOverrideHandler, the request's method gets changed successfully and it is able to match and execute my controller's method.

Am I doing something wrong? Is there a secret configuration parameter to know in order to do such things? :-)

If anyone needs the whole project, it's available here [7KB zip file].

Thank you.

1条回答
萌系小妹纸
2楼-- · 2019-05-09 11:55

You are confusing routing engine with Web API pipeline. HttpRoutingDispatcher is not a routing engine concept. The route constraints will be processed first because your underlying host needs to build a route table and match the route for your request.

HttpRoutingDispatcher is simply another implementation of HttpMessageHandler and all it does is it examines the IHttpRoute of the route that has been matched, and chooses which message handler to call next. If there is no per-route handler present, it delegates the processing to HttpControllerDispatcher.

查看更多
登录 后发表回答