Aspnetcore: HttpContextAccessor.HttpContext is null when using TestServer and .net 2.2.2

Created on 26 Feb 2019  Â·  29Comments  Â·  Source: dotnet/aspnetcore

Describe the bug

When using .net core 2.2.2 along with the TestServer(https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/TestHost/src/TestServer.cs), HttpContextAccessor.HttpContext will return null in certain situations. We're experiencing this is the repro: https://github.com/Microsoft/fhir-server/

If you have 2.2.1 installed, this behavior is not exhibited. It appears related to the use of authentication and calls that are made on the JWT backchannel handler.

To Reproduce

Steps to reproduce the behavior:

  1. Clone my repro repository here: https://github.com/brandonpollett/TestServerRepro
  2. Execute the unit tests with .net 2.2.2 and you will see a test failure
  3. Remove .net 2.2.2 and run with 2.2.1 and the tests will succeed

Expected behavior

The tests should pass while using 2.2.2 and 2.2.1

Additional context

When the unit tests pass the output of dotnet --info is:

C:\projects\TestServerRepro [master ≡]> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   2.2.103
 Commit:    8edbc2570a

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.103\

Host (useful for support):
  Version: 2.2.1
  Commit:  878dd11e62

.NET Core SDKs installed:
  1.0.4 [C:\Program Files\dotnet\sdk]
  1.1.0 [C:\Program Files\dotnet\sdk]
  2.0.0 [C:\Program Files\dotnet\sdk]
  2.0.2 [C:\Program Files\dotnet\sdk]
  2.1.2 [C:\Program Files\dotnet\sdk]
  2.1.4 [C:\Program Files\dotnet\sdk]
  2.1.100-preview-007354 [C:\Program Files\dotnet\sdk]
  2.1.100-preview-007363 [C:\Program Files\dotnet\sdk]
  2.1.100 [C:\Program Files\dotnet\sdk]
  2.1.103 [C:\Program Files\dotnet\sdk]
  2.1.104 [C:\Program Files\dotnet\sdk]
  2.1.200 [C:\Program Files\dotnet\sdk]
  2.1.201 [C:\Program Files\dotnet\sdk]
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.402 [C:\Program Files\dotnet\sdk]
  2.1.403 [C:\Program Files\dotnet\sdk]
  2.1.503 [C:\Program Files\dotnet\sdk]
  2.2.103 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

When failing the output of dotnet--info is:

C:\projects\TestServerRepro [master ≡]> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   2.2.104
 Commit:    73f036d4ac

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.104\

Host (useful for support):
  Version: 2.2.2
  Commit:  a4fd7b2c84

.NET Core SDKs installed:
  1.0.4 [C:\Program Files\dotnet\sdk]
  1.1.0 [C:\Program Files\dotnet\sdk]
  2.0.0 [C:\Program Files\dotnet\sdk]
  2.0.2 [C:\Program Files\dotnet\sdk]
  2.1.2 [C:\Program Files\dotnet\sdk]
  2.1.4 [C:\Program Files\dotnet\sdk]
  2.1.100-preview-007354 [C:\Program Files\dotnet\sdk]
  2.1.100-preview-007363 [C:\Program Files\dotnet\sdk]
  2.1.100 [C:\Program Files\dotnet\sdk]
  2.1.103 [C:\Program Files\dotnet\sdk]
  2.1.104 [C:\Program Files\dotnet\sdk]
  2.1.200 [C:\Program Files\dotnet\sdk]
  2.1.201 [C:\Program Files\dotnet\sdk]
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.402 [C:\Program Files\dotnet\sdk]
  2.1.403 [C:\Program Files\dotnet\sdk]
  2.1.503 [C:\Program Files\dotnet\sdk]
  2.2.104 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
area-servers enhancement

Most helpful comment

I can do one better and get a full-on workaround by adding an HttpMessageHandler that abandons the ExecutionContext for you. Then you just have to wrap the ClientHandler up in that and it seems to work:

This workaround unblocked us. Our scenario is this: We have multiple microservices. We use JWT Bearer auth and Open ID Connect using our own IdentityServer4 IdP. In order for our tests to work we need to set up two TestServers: the microservice we are testing and the IdP. Then we delegate the JWT verification from our microservice to our IdP.

public class ApiServiceFactory : WebApplicationFactory<Api.Startup>
{
  private WebApplicationFactory<Identity.Startup> _identityServerFactory;

  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
    var backChannelHandler = _identityServerFactory.CreateHandler();
    builder.ConfigureServices(
      s => s.AddSingleton<IPostConfigureOptions<IdentityServerAuthenticationOptions>>(
        new PostConfigureOptions<IdentityServerAuthenticationOptions>(
            "AuthScheme",
          opt => { opt.JwtBackChannelHandler = new SuppressExecutionContextHandler(backChannelHandler); })));
  }
}

This regression is unfortunate, but glad we're unblocked now. I'm not fully understanding the reasoning why this is an edge case. It seems like a common case for anyone testing with authentication. Thanks for the workaround @anurse .

All 29 comments

I'm having similar issues, though forcing Microsoft.AspNetCore.App version to 2.2.2 in the test projects resolves them for me. I'm suspecting this is because an Microsoft.NET.Sdk project doesn't get the same resolution rules for the Microsoft.AspNetCore.App and instead runs with the _lowest_ possible version accepted by Microsoft.AspNetCore.TestHost - building in diagnostic mode seems to confirm this. So while I think this is a bug, it's more of a design problem for the versionless packages approach. (I'm not a team member, take this with a grain of salt, please.)

@brandonpollett thank you for isolating the repro. I'm also hitting this issue.

@skolima Can you tell me what you mean by forcing the test project to a version of Microsoft.AspNetCore.App I added the following <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.3" /> (2.2.3 was released recently but has the same issue) to my test project and it did not solve the problem. Thanks for your help!

@parekhkb exactly this - that did solve the problem in my case.

I think we run into the same problem in our project.

This is our code:

[TestClass]
public class GroupControllerAutomatedTests : IDisposable
{
    private readonly TestServer _testServer;

    public HttpClient Client { get; }

    public GroupControllerAutomatedTests()
    {
        var apiBuilder = new WebHostBuilder();

        var builder = apiBuilder
            .UseContentRoot(TestStartup.GetContentRootPath())
            .ConfigureAppConfiguration(TestStartup.ConfigConfiguration)
            .UseEnvironment("Development")
            .UseStartup<TestStartup>()
            .UseSetting("AutomatedTests", "true");

        this._testServer = new TestServer(builder);
        this.Client = this._testServer.CreateClient();
    }

    [TestMethod]
    public async Task ListGroups()
    {
        var data = new []
        {
            new DbGroupMaster(1, "SuperAdministrators"),
            new DbGroupMaster(2, "Administrators"),
            new DbGroupMaster(3, "Systems"),
            new DbGroupMaster(4, "Authenticated"),
            new DbGroupMaster(5, "Anonymous"),
            new DbGroupMaster(6, "Internal"),
        };

        var response = await this.GetAsync<DbGroupMaster[]>("API/groups/List/ENG");

        Assert.IsTrue(data.DeepEqual(response, out var error, "InsertedUserId", "InsertedDateTime"), $"Demo data is not equal to API result. {error}");
    }

    protected async Task<T> GetAsync<T>(string url, bool addAccessToken = true)
    {
        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(this.Client.BaseAddress + url)
        };
        if (addAccessToken)
            request.Headers.Add("authorization", $"Bearer {await this.GetAccessToken()}");
        var result = await this.Client.SendAsync(request);
        result.EnsureSuccessStatusCode();                              //We received 401.
        var content = await result.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(content);
    }
}

In our API/groups controller we get null for IHttpContextAccessor:
devenv_2019-03-15_19-54-39

in TestProject we are using:

    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
    <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />

and in controller project:

    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.3" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.2.0" />

Before we were using:

    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
    <PackageReference Include="MSTest.TestFramework" Version="1.3.2" />

and

    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.3" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.1.1" />

I have exactly the same issue -> integration tests are broken because of null at HttpContextAccessor.HttpContext.
Also I tries adding explicit version like:
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.3" />
but with no success. I had to rollback to 2.2.1

Looks like there is indeed a regression here. The problem lies in the nested HTTP calls. When you make a call back to the application using the HttpClient produced by TestServer.CreateClient() (or handlers produced by TestServer.CreateHandler()) the HttpContextAccessor gets cleared out after the nested call is made and isn't restored to it's previous content.

This behavior does not appear to occur in 2.2.0. After a brief binary search it appears to have been introduces in 2.2.2. I do see a change to HttpContextAccessor in 2.2.2 (https://github.com/aspnet/AspNetCore/pull/6036 by @JunTaoLuo), which definitely seems suspiciously relevant :). I think something in that PR regressed this TestServer scenario where there are nested HTTP calls.

Repro details below. cc @Tratcher @davidfowl @JunTaoLuo

The following program repros the issue

class Program
{
    public static async Task Main(string[] args)
    {
        HttpClient client = null;
        var builder = WebHost.CreateDefaultBuilder()
            .Configure(app =>
            {
                app.Use(async (context, next) =>
                {
                    var accessor = context.RequestServices.GetRequiredService<IHttpContextAccessor>();
                    if (context.Request.Path.StartsWithSegments("/inner"))
                    {
                        if (accessor.HttpContext == null)
                        {
                            throw new System.Exception("Invalid During Nested Call!");
                        }
                    }
                    else if (context.Request.Path.StartsWithSegments("/outer"))
                    {
                        var nestedResp = await client.GetAsync("/inner");
                        if (accessor.HttpContext == null)
                        {
                            Console.WriteLine("HttpContextAccessor NULL after nested!");
                        }
                        else
                        {
                            Console.WriteLine("HttpContextAccessor NON-NULL after nested!");
                        }
                    }
                    else
                    {
                        await next();
                    }
                });
            })
            .ConfigureServices(services =>
            {
                services.AddHttpContextAccessor();
            });
        Console.WriteLine($"Runtime Path: {typeof(string).Assembly.Location}");
        Console.WriteLine($"ASP.NET Path: {typeof(HttpContext).Assembly.Location}");
        var server = new TestServer(builder);
        client = server.CreateClient();
        var resp = await client.GetAsync("/outer");
        resp.EnsureSuccessStatusCode();
    }
}

If I build this and then run it against 2.2.0, I get:

> dotnet exec --fx-version 2.2.0 .\bin\Debug\netcoreapp2.2\UnitTests.dll
Runtime Path: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.4\System.Private.CoreLib.dll
ASP.NET Path: C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\2.2.0\Microsoft.AspNetCore.Http.Abstractions.dll
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/outer
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/inner
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 6.7586ms 200
HttpContextAccessor NON-NULL after nested!

If I run against 2.2.2, I get:

> dotnet exec --fx-version 2.2.2 .\bin\Debug\netcoreapp2.2\UnitTests.dll
Runtime Path: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.4\System.Private.CoreLib.dll
ASP.NET Path: C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\2.2.2\Microsoft.AspNetCore.Http.Abstractions.dll
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/outer
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/inner
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 6.3915ms 200
HttpContextAccessor NULL after nested!

Nested HttpClient's are one of the least realistic things that you can do with TestServer. You end up with two HttpContextAccessor async local's on the same thread. You'd never get that with a real server.

I don't think we should try to change HttpContextAccessor to address this. We may be able to work around it in TestServer. At some point in this flow you need to split the execution context so the nested call can run in isolation.

At some point in this flow you need to split the execution context so the nested call can run in isolation.

Yeah, I came to a similar conclusion. I think we need to abandon the ExecutionContext when we "send" the request in ClientHandler so that a new context will be established.

Perhaps as simple as this change? https://github.com/aspnet/AspNetCore/compare/master...anurse/spike-suppress-flow (just for illustrative purposes)

It would break anyone trying to intentionally hold an AsyncLocal across the TestServer boundary though.

Can you apply that around the recursive call in your repro above?

I can do one better and get a full-on workaround by adding an HttpMessageHandler that abandons the ExecutionContext for you. Then you just have to wrap the ClientHandler up in that and it seems to work:

› dotnet exec .\bin\Debug\netcoreapp2.2\UnitTests.dll
Runtime Path: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.4\System.Private.CoreLib.dll
ASP.NET Path: C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\2.2.4\Microsoft.AspNetCore.Http.Abstractions.dll
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/outer
[OUTER] (before) HttpContextAccessor.HttpContext.Request.Path = /outer
[INNER] HttpContextAccessor.HttpContext.Request.Path = /inner
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/2.0 GET http://localhost/inner
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 7.6767ms 200
HttpContextAccessor NON-NULL after nested!
[OUTER] (after) HttpContextAccessor.HttpContext.Request.Path = /outer

My main concern is the unintended consequences of abandoning the EC here. Perhaps this would live best as a known-issue workaround rather than a fix.

Just use UnsafeQueueUserWorkItem. Feels like we should have a clean ExecutionContext per request no? I can see people taking advantage of being able to set an async local from the outside and have it flow. Lets use UnsafeQueueUserWorkItem by default and then expose an option to not do that on the TestServer for those rare cases where its being used in that way.

Feels like we should have a clean ExecutionContext per request no?

💯 agree. Just want to make sure we have an escape hatch because it will break some code (arguably wrong code ;)). An option to "not abandon the EC" seems reasonable to me.

Just use UnsafeQueueUserWorkItem

Returning a value from UnsafeQueueUserWorkItem seems to introduce more complexity than just suppressing the flow from Task.Run, since you have to dance with a TCS, but whatever. As long as you abandon the EC somehow :)


Ok, I'll throw this in to preview 6 as low pri. We'll see what we can pull out here. This seems like another area I might be able to prove I can still write code ;P.

@brandonpollett if you want a workaround for now, see my comment above.

Setting up a recursive HttpClient call is so manual and rare that I think the workaround is adequate, there's no need to change the default experience. At most we'd provide an opt-in solution, not an opt-out one.

At most we'd provide an opt-in solution, not an opt-out one.

I don't agree with that, ideally the TestServer should act like a real server first and foremost which is why a clean ExecutionContext per request makes sense.

TestServer walks a fine line between emulating real server behavior and providing useful test functionality. E.g. It doesn't covert application exceptions to 500's like a real server because it can flow the exception directly to the caller. Note this client/server pairing doesn't exist in real servers, and there's no first class way to set up this recursive scenario in the test server.

Before we start suppressing the execution context flow for this specialized scenario we need to make sure we're not compromising more useful, mainstream scenarios. What else would we loose?

What else would we loose?

The thing we lose is fairly simple, it's the flow of ExecutionContext between the "client" and the "server". That means AsyncLocal and things like current culture. Since TestServer is designed to emulate a real client/server interaction, and a real client/server interaction has a machine boundary, this seems reasonable. The fact that we flow the EC at all is entirely misleading, and unlike suppressing 500s I think it reduces the value and usefulness of TestServer rather than improving it. I agree that where it adds value, we can compromise the "emulation" but this does not add value, it reduces it.

there's no first class way to set up this recursive scenario in the test server

Of course there is. We literally give you the ability to create HttpMessageHandlers that can "invoke" the server. Regardless of if it was intentional, we definitely gave users the ability to make recursive calls.

A user trying to flow AsyncLocals between the client and server in this scenario seems like the edge case and is not the intended design. I don't think we should optimize for the edge case (that doesn't work in a real server) here.

To me, disabling EC flow by default seems like the best option here and in 3.0 it can be relatively easily justified as a potential breaking change. We can annoucement it, do it in an early preview and provide an opt-out switch.

I can do one better and get a full-on workaround by adding an HttpMessageHandler that abandons the ExecutionContext for you. Then you just have to wrap the ClientHandler up in that and it seems to work:

This workaround unblocked us. Our scenario is this: We have multiple microservices. We use JWT Bearer auth and Open ID Connect using our own IdentityServer4 IdP. In order for our tests to work we need to set up two TestServers: the microservice we are testing and the IdP. Then we delegate the JWT verification from our microservice to our IdP.

public class ApiServiceFactory : WebApplicationFactory<Api.Startup>
{
  private WebApplicationFactory<Identity.Startup> _identityServerFactory;

  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
    var backChannelHandler = _identityServerFactory.CreateHandler();
    builder.ConfigureServices(
      s => s.AddSingleton<IPostConfigureOptions<IdentityServerAuthenticationOptions>>(
        new PostConfigureOptions<IdentityServerAuthenticationOptions>(
            "AuthScheme",
          opt => { opt.JwtBackChannelHandler = new SuppressExecutionContextHandler(backChannelHandler); })));
  }
}

This regression is unfortunate, but glad we're unblocked now. I'm not fully understanding the reasoning why this is an edge case. It seems like a common case for anyone testing with authentication. Thanks for the workaround @anurse .

Thanks @parekhkb, that's a more concrete example and it's not even recursive, it's multi-tier. It makes sense we'd need to prevent cross contamination between the server instances.

Awesome, we'll schedule this fix for 3.0

I found the first use of trying to preserve an async local across the client server boundary (https://github.com/aspnet/AspNetCore/blob/f5ff181222d6f20f07e6b3d707dc24cb95966cc2/src/Mvc/test/Mvc.FunctionalTests/JsonOutputFormatterTestBase.cs#L160-L173). MVC is currently doing it to verify the Activity gets written to the response (I know because just broke all of these tests with a PR 😄)

Lol :). Well I think we'd still provide an option to "enable" flowing the local. It would just restrict your ability to use "nested" calls back through the test server.

I have the same symptom, I'm attempting to write integration tests using the TestServer.

My service uses authentication and to fake the authentication, I add the following to my TestServer host builder configuration:

            services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    RequireSignedTokens = false,
                    RequireExpirationTime = false,
                    ValidateActor = false,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateTokenReplay = false,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = false,
                    SignatureValidator = (token, parameters) => new JwtSecurityToken(token),
                };
                options.RequireHttpsMetadata = false;
                options.Authority = null;
                options.BackchannelHttpHandler = new ContextHandler();
            });

      // snip ...

        private class ContextHandler : DelegatingHandler
        {
        }

When I attempt to use HttpContextAccessor.HttpContext it returns null. Not sure how to workaround this problem, other than to replace the services that call it with test doubles (reducing the value of doing integration tests).

Any suggestions?

The workaround provided by @anurse is here https://github.com/aspnet/AspNetCore/issues/7975#issuecomment-481536061

I'll look at making this change and adding a setting to turn it back off. Technically a breaking change which will need some announcement.

After a lot of hours, I found a workaround for my case.
That work for me... https://github.com/aspnet/AspNetCore/issues/5144#issuecomment-498267645

That (https://github.com/aspnet/AspNetCore/issues/7975#issuecomment-481536061) other doesn't work because in IClassFixture<WebApplicationFactory<TStartup>>.WithWebHostBuilder().CreateClient() I can't give me condition to create my own HTTPClient with SuppressExecutionContextHandler and Server Handler.

Was this page helpful?
0 / 5 - 0 ratings