4

I have a WebApi method looking more or less like this:

public async Task<HttpResponseMessage> Get(Guid id)
{
    var foo = await Store.GetFooAsync(id);
    if (foo.BelongsTo != User.Identity.Name)
        throw new HttpResponseException(HttpStatusCode.Forbidden);
    //return foo here
}

This seems to work fine in the actual application, where a custom IHttpModule sets the principal in both HttpContext.User and Thread.CurrentPrincipal.

However, when calling from a unit test:

Thread.CurrentPrincipal = principal;
var response = controller.Get(id).Result;

User is reset after await (i.e. on the continuation), so the test fails. This happens when running under R#8, and it wasn't happening with R#7.

My workaround was to save the current principal before the first await, but it's just a hack to satisfy the test runner needs.

How can I call my controller method and make sure continuations have the same principal as the original call?

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154

2 Answers2

1

I fixed this by upgrading the MVC packages. The new version keeps the principal in ApiController.RequestContext.Principal, separate from Thread.CurrentPrincipal, avoiding the problem completely.

My new test code starts with:

controller.RequestContext.Principal = principal;
Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
0

The short version is, use HttpContext.Current.Use instead of Thread.Principal.

The short version is, use Request.LogonUserIdentity or Request.RequestContext.HttpContext.User.

async/await preserves the original request context, not the thread that started the method. This makes senses, otherwise ASP.NET would have to freeze the thread and have it available for when await returns. The thread you run after await is a thread from the ThreadPool, so modifying its CurrentPrincipal is a very bad idea.

Check the transcript from Scott Hanselman's podcast "Everything .NET programmers know about Asynchronous Programming is wrong" for more.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • There's no HttpContext.Current when not running under ASP.NET. Also, API controllers read from CurrentPrincipal. – Diego Mijelshon Oct 18 '13 at 16:56
  • Actually there is, at Request.RequestContext.HttpContext but you can get the user identity in an even easier way by calling Request.LogonUserIdentity. The point is, `async/await` preserves the request context, not the thread context, so you shouldn't look for the user in Thread.Principal – Panagiotis Kanavos Oct 21 '13 at 07:05
  • `LogonUserIdentity` represents the user IIS runs under, not the person doing the request. And Request.RequestContext is for MVC, not WebApi. And Even in that case, the current context is null if you are not creating a real request (as is the case of my unit test, which just intantiates the controller and calls a method) – Diego Mijelshon Oct 21 '13 at 11:30
  • I think you really need to separate what "async\await` does from the behavior of your unit tests. `async\await` was built to preserve the request context. Unless your unit tests set the correct context, you will not be able to test the code correctly. The executing thread is almost guaranteed to be different after an `await` call. To put it simply, R#7 exhibited the wrong behavior – Panagiotis Kanavos Oct 21 '13 at 11:40
  • I understand what you are saying. The question is: how do I make my unit tests "preserve the request context"? IOW, How can I make sure `this.User` will return the right value after an async continuation? – Diego Mijelshon Oct 21 '13 at 15:58