DelegatingHandler设置CurrentPrincipal

时间:2013-06-10 12:18:10

标签: c# unit-testing asp.net-web-api mstest

我正在尝试对DelegateHandler的实现进行单元测试。我的简化实施:

public class FooHandler
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Thread.CurrentPrincipal = new GenericPrincipal(
            new GenericIdentity("Vegard"), new[] { "A", "B" });

        return await base.SendAsync(request, cancellationToken);
    }
}

当我尝试对此进行单元测试时,我这样做:

public class TestHandler : DelegatingHandler
{
    private readonly Func<HttpRequestMessage,
        CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public TestHandler()
    {
        _handlerFunc = (r, c) => Return(HttpStatusCode.OK);
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return _handlerFunc(request, cancellationToken);
    }

    public static Task<HttpResponseMessage> Return(HttpStatusCode status)
    {
        return Task.Factory.StartNew(
            () => new HttpResponseMessage(status));
    }
}

[TestMethod]
public async Task SendAsync_CorrectTokens_IsAuthorized()
{
    var message = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");

    var handler = new AuthorizationHeaderHandler
        {
            InnerHandler = new TestHandler()
        };

    var invoker = new HttpMessageInvoker(handler);
    var result = await invoker.SendAsync(message, new CancellationToken());

    Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
    Assert.IsTrue(Thread.CurrentPrincipal.Identity.IsAuthenticated); // fails
    Assert.AreEqual("Vegard", Thread.CurrentPrincipal.Identity.Name); // fails
}

我猜这是因为HttpMessageInvoker在一个单独的线程上运行DelegateHandler。我可以强迫它们在同一个线程上吗?

2 个答案:

答案 0 :(得分:3)

  

我可以强制将它们放在同一个帖子上吗?

你不能。

更好的问题是“如何将Thread.CurrentPrincipal传递给正在执行请求的任何线程”? 这个问题的答案。

在ASP.NET中,

Thread.CurrentPrincipal很奇怪。事实上,我建议你根本不要使用它;请改用HttpContext.User。但如果你愿意,你可以通过理解这些要点来实现它:

  1. HttpContext.User由ASP.NET SynchronizationContext传播。
  2. 只要线程进入ASP.NET请求Thread.CurrentPrincipal
  3. HttpContext.User就会被SynchronizationContext覆盖。
  4. 不幸的是,您当前的测试在几个关键点上存在缺陷:

    • 请求完成后,Thread.CurrentPrincipal的值未定义。
    • 您运行测试的当前方式是,没有HttpContext(或ASP.NET SynchronizationContext),这会干扰主要用户的流动。

    要完全测试授权,您需要进行集成测试。

    另见my answer to this question

答案 1 :(得分:2)

您实际遇到的是await的行为。当您退出await时,Await会将主体重置为您进入等待时的状态。因此,当你调用await invoker.SendAsync时没有当前主体,在等待该调用之后将没有当前主体。

但是,您的测试处理程序应该看到正确的主体。你可以做的是让你的测试处理程序将当前主体存储在其SendAsync实现中,将其作为公共属性公开,然后让测试断言测试处理程序看到它应该的主体。这应该很好,这应该是你关心的行为。