Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
667 views
in Technique[技术] by (71.8m points)

asp.net web api - webapi batching and delegating handlers

based on my last post I was able to get batching working... until a certain point. In addition to registering the route specific handler I also have 2 delegating handlers

  1. Authenticate the user
  2. logging

the batch handler goes through the delegating handlers authenticating the user and logging the request. when the messagehandlerinvoker starts to send the child/nested requests the following exception is thrown.

System.ArgumentException was unhandled by user code
  HResult=-2147024809
  Message=The 'DelegatingHandler' list is invalid because the property 'InnerHandler' of 'AuthenticationMessageHandler' is not null.
Parameter name: handlers
  Source=System.Net.Http.Formatting
  ParamName=handlers
  StackTrace:
       at System.Net.Http.HttpClientFactory.CreatePipeline(HttpMessageHandler innerHandler, IEnumerable`1 handlers)
       at System.Web.Http.HttpServer.Initialize()
       at System.Web.Http.HttpServer.<EnsureInitialized>b__3()
       at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
       at System.Threading.LazyInitializer.EnsureInitialized[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
       at System.Web.Http.HttpServer.EnsureInitialized()
       at System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
       at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
       at RoutingRequest.Service.Startup.BatchMessageHandler.<>c__DisplayClassd.<PrcoessRequest>b__b(Task`1 m) in C:CEIClientsFootlocker.comFL - Vendor Routing PortalsourceRoutingRequest.ServiceStartupBatchMessageHandler.cs:line 45
       at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException: 

is there a config option I am missing, or do I need to bypass the delegating handlers?

edit here is my authentication handler.

public class AuthenticationMessageHandler
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        SetCurrentUser(request);
        return base.SendAsync(request, cancellationToken);
    }

    private void SetCurrentUser(HttpRequestMessage request)
    {
        var values = new List<string>().AsEnumerable();
        if (request.Headers.TryGetValues("routingrequest-username", out values) == false) return;

        var username = values.First();

        var user = Membership.GetUser(username, true);
        if (user == null)
        {
            var message = string.Format("membership information for '{0}' could not be found.", username);
            throw new HttpRequestException(message);
        }

        var roles = Roles.GetRolesForUser(username);

        Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(user.UserName), roles);
    }
}

based on Kiran's answer a subclassed httpserver fixes one issue and introduces another. My roles provider is getting a null reference exception. looking into that now.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

That blog post correctly identifies the problem, but there is a simpler solution if you are configuring OWIN using a Startup or OwinStartup class:

Change the OWIN configuration call from UseWebApi(this IAppBuilder builder, HttpConfiguration configuration); to UseWebApi(this IAppBuilder builder, HttpServer httpServer); so that your batch handler and the OWIN pipeline are using the same HttpServer instance.

The root cause of this is that many of the batching articles/examples (eg http://bradwilson.typepad.com/blog/2012/06/batching-handler-for-web-api.html ) create a new HttpServer for batching in addition to the main HttpServer that is handling HTTP requests; and both HttpServers are using the same HttpConfiguration.

When each HttpServer is initialized the first time it receives requests, it creates a pipeline of handlers (in HttpClientFactory.CreatePipeline) by reversing all the configured delegating handlers (eg tracing handlers, or other proxy-type handlers), and terminating the pipeline with the Web API dispatcher.

If you don't have any delegating handlers configured, then this problem won't bite you - you can have 2 HttpServer objects that use the same HttpConfiguration.

But if you have any delegating handlers explicitly or implicitly configured (eg by enabling Web API Tracing), then Web API can't build the 2nd pipeline - the delegating handlers are already linked in the first pipeline - and this exception is thrown on the first request to the 2nd HttpServer.

This exception should absolutely be more clear about what is going on. Better yet, this problem shouldn't even be possible - configuration should be configuration, not individual handlers. The configuration could be a factory for delegating handlers. But I digress...

While the issue is kinda hard to figure out, there's a pretty easy fix:

  1. If you're using OWIN, pass the same HttpServer as you use in the batch handler to the OWIN pipeline via UseWebApi(this IAppBuilder builder, HttpServer httpServer);
  2. If you're using IIS + Web API (no OWIN Startup class), pass GlobalConfiguration.DefaultServer to your batch handler, to avoid creating a new HttpServer

Here's an example OWIN startup class that creates a single HttpServer and passes it to both the batch handler, and Web API. This example uses to OData batch handler:

[assembly: OwinStartup(typeof(My.Web.OwinStartup))]
namespace My.Web
{

    /// <summary>
    /// OWIN webapp configuration.
    /// </summary>
    public sealed class OwinStartup
    {

        /// <summary>
        /// Configure all the OWIN modules that participate in each request.
        /// </summary>
        /// <param name="app">The OWIN appBuilder</param>
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration webApiConfig = new HttpConfiguration();
            webApiConfig.MapHttpAttributeRoutes();

            HttpServer webApiServer = new HttpServer(webApiConfig);

            // Configure batch handler
            var batchHandler = new DefaultODataBatchHandler(webApiServer);
            webApiConfig.Routes.MapODataServiceRoute("ODataRoute",
                                                     "odata",
                                                     BuildEdmModel(),
                                                     new DefaultODataPathHandler(),
                                                     ODataRoutingConventions.CreateDefault(),
                                                     batchHandler);

            app.UseWebApi(webApiServer);
        }

        private EdmModel BuildEdmModel()
        {
            // ...
        }
    }

}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...