WebAPI 批处理和委托处理程序

9

根据我的上一篇文章,我已经成功实现了批处理...直到某个点。除了注册路由特定的处理程序外,我还有两个委托处理程序:

  1. 验证用户
  2. 记录日志

批处理程序通过委派处理程序对用户进行身份验证并记录请求。当messagehandlerinvoker开始发送子/嵌套请求时,会抛出以下异常。

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:\CEI\Clients\Footlocker.com\FL - Vendor Routing Portal\source\RoutingRequest.Service\Startup\BatchMessageHandler.cs:line 45
       at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException: 

我是否遗漏了某个配置选项,或者需要绕过委托处理程序?

编辑 这是我的身份验证处理程序。

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);
    }
}

根据Kiran的回答,子类化的httpserver解决了一个问题,但引入了另一个问题。我的角色提供程序出现了空引用异常。现在正在调查这个问题。


你能发布一下你的身份验证处理程序代码和初始化代码吗? - Filip W
2个回答

19

这篇博客正确地指出了问题,但如果您正在使用StartupOwinStartup类配置OWIN,则有一个更简单的解决方案:

将OWIN配置调用从 UseWebApi(this IAppBuilder builder, HttpConfiguration configuration); 改为 UseWebApi(this IAppBuilder builder, HttpServer httpServer); 以便您的批处理处理程序和OWIN管道使用相同的HttpServer实例。

根本原因是许多批处理文章/示例(例如http://bradwilson.typepad.com/blog/2012/06/batching-handler-for-web-api.html)为批处理创建了一个新的HttpServer,除了处理HTTP请求的主要HttpServer之外;并且两个HttpServer都在使用相同的HttpConfiguration

每个HttpServer在第一次接收请求时初始化,它通过反转所有配置的委托处理程序(例如跟踪处理程序或其他代理类型处理程序)来创建处理程序管道(在HttpClientFactory.CreatePipeline中),并使用Web API分发器终止管道。
如果您没有配置任何委托处理程序,则不会遇到此问题-您可以拥有使用相同HttpConfiguration的2个HttpServer对象。
但是,如果您已经显式或隐式配置了任何委托处理程序(例如通过启用Web API跟踪),则Web API无法构建第二个管道-委托处理程序已链接到第一个管道中-并且在第二个HttpServer的第一个请求上抛出此异常。
这个异常应该更清楚地说明正在发生什么。更好的方法是,这个问题甚至不应该存在-配置应该是配置,而不是单个处理程序。配置可以是委托处理程序的工厂。但我离题了...
虽然这个问题有点难以解决,但有一个非常简单的解决方法:
  1. 如果您正在使用OWIN,请将与批处理程序中使用的相同的HttpServer通过UseWebApi(this IAppBuilder builder, HttpServer httpServer);传递到OWIN管道。
  2. 如果您正在使用IIS + Web API(没有OWIN启动类),请将GlobalConfiguration.DefaultServer传递给批处理程序,以避免创建新的HttpServer

以下是一个示例OWIN启动类,它创建一个单一的HttpServer并将其传递给批处理程序和Web API。此示例使用OData批处理程序:

[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()
        {
            // ...
        }
    }

}

1
有趣!在startup.cs的默认public void Configuration(IAppBuilder app)中(其中OWIN默认配置了Cors和co),我该如何获取httpServer?谢谢。 - Peter Albert
1
在我的回答中添加了一个示例。 - crimbo
在调用UseWebApi之后,请调用IOC初始化,否则您可能会收到错误消息:“无法重复使用'ApiController'实例。'ApiController'必须根据传入的消息构建。请检查您的自定义'IHttpControllerActivator',并确保它不会制造相同的实例。”在我的情况下,它们是UseAutofacMiddlewareUseAutofacWebApi - Afshar Mohebi
@crimbo,我只是为了使用SignalR而使用OWIN,但是没有UseWebApi方法。有什么替代方案吗? - Ivan-Mark Debono

1
我之前在没有批处理的情况下遇到了这个错误。我自己创建了一个HttpClientFactory,它接受一个HandlerFactory,也是我自己写的。
它在构造函数中调用HandlerFactory.Create()方法,并存储生成的处理程序。
每当工厂需要创建新的HttpClient时,这些处理程序将传递给System.Net.Http.HttpClientFactory.Create(...)方法。
但是,由于.NET代码对处理程序本身进行了改变,使它们处于一种状态,意味着它们无法被重复使用,因此它只适用于单个调用。
我修改了构造函数,使其不会提前创建处理程序,而是每次都会创建处理程序。现在它可以正常工作了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接