C# DelegatingHandler设置CurrentPrincipal

C# DelegatingHandler设置CurrentPrincipal,c#,unit-testing,asp.net-web-api,mstest,C#,Unit Testing,Asp.net Web Api,Mstest,我试图对DelegateHandler的一个实现进行单元测试。我的简化实施: public class FooHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Th

我试图对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);
    }
}
公共类foodhandler
:DelegatingHandler
{
受保护的覆盖异步任务SendAsync(
HttpRequestMessage请求,CancellationToken CancellationToken)
{
Thread.CurrentPrincipal=新的GenericPrincipal(
新的通用实体(“Vegard”)、新的[]{“A”、“B”});
返回wait base.sendaync(请求、取消令牌);
}
}
当我尝试进行单元测试时,我是这样做的:

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
}
公共类TestHandler:DelegatingHandler
{
私有只读函数_handlerFunc;
公共TestHandler()
{
_handlerFunc=(r,c)=>Return(HttpStatusCode.OK);
}
受保护的覆盖任务SendAsync(
HttpRequestMessage请求,CancellationToken CancellationToken)
{
返回_handlerFunc(请求、取消令牌);
}
公共静态任务返回(HttpStatusCode状态)
{
return Task.Factory.StartNew(
()=>新的HttpResponseMessage(状态));
}
}
[测试方法]
公共异步任务SendAsync\u CorrectTokens\u已授权()
{
var message=新的HttpRequestMessage(HttpMethod.Get)http://www.test.com");
var handler=新授权HeaderHandler
{
InnerHandler=新的TestHandler()
};
var invoker=newhttpmessageinvoker(处理程序);
var result=wait invoker.sendaync(消息,new CancellationToken());
AreEqual(HttpStatusCode.OK,result.StatusCode);
Assert.IsTrue(Thread.CurrentPrincipal.Identity.IsAuthenticated);//失败
Assert.AreEqual(“Vegard”,Thread.CurrentPrincipal.Identity.Name);//失败
}

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

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

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

我能强迫它们在同一条线上吗

你不能

更好的问题是“如何将
Thread.CurrentPrincipal
流到执行请求的任何线程”?这个问题有答案

Thread.CurrentPrincipal
在ASP.NET中是奇数。事实上,我建议你根本不要使用它;改用
HttpContext.User
。但如果您愿意,您可以通过了解以下几点来实现:

  • HttpContext.User
    由ASP.NET
    SynchronizationContext
    流式传输
  • 每当线程进入ASP.NET请求时,
    HttpContext.User
    就会覆盖
    Thread.CurrentPrincipal
  • 不幸的是,您当前的测试在几个关键点上存在缺陷:

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

    另见