简体   繁体   中英

DelegatingHandler setting CurrentPrincipal

I am trying to unit test an implementation of DelegateHandler. My simplified implementation:

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

When I try to unit test this, I do it like this:

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
}

My guess is that this happens because HttpMessageInvoker runs the DelegateHandler on a separate thread. Can I force these to be on the same thread?

Can I force these to be on the same thread?

You can't.

A better question is "how do I flow Thread.CurrentPrincipal to whatever thread is executing the request"? There is an answer to this question.

Thread.CurrentPrincipal is odd in ASP.NET. In fact, I recommend you don't use it at all; use HttpContext.User instead. But if you want, you can get it to work by understanding these points:

  1. HttpContext.User is flowed by the ASP.NET SynchronizationContext .
  2. Thread.CurrentPrincipal is overwritten by HttpContext.User whenever a thread enters an ASP.NET request SynchronizationContext .

Unfortunately, your current test is flawed in a couple of key points:

  • After a request is completed, the value of Thread.CurrentPrincipal is undefined.
  • The current way you're running your tests, there is no HttpContext (or ASP.NET SynchronizationContext ), and this interferes with the flowing of the principal user.

To fully test authorization, you'd need an integration test.

Also see my answer to this question .

What you're actually running into is the behavior of await. Await will reset the principal to whatever it was when you entered the await when you exit the await. So since there is no current principal when you call await invoker.SendAsync, there will be no current principal after you await that call.

However, your test handler should see the right principal. What you could do is have your test handler store the current principal in its SendAsync implementation, expose it as a public property, and then have your test assert that the test handler saw the principal it was supposed to. That should work fine, and that should be the behavior you care about.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM