Use object indirection in HttpContextAccessor (#1066)

This commit is contained in:
Ben Adams 2018-11-16 06:34:44 +00:00 committed by David Fowler
parent ea1ee2b68c
commit 49d785c934
4 changed files with 21 additions and 22 deletions

View File

@ -7,21 +7,35 @@ namespace Microsoft.AspNetCore.Http
{ {
public class HttpContextAccessor : IHttpContextAccessor public class HttpContextAccessor : IHttpContextAccessor
{ {
private static AsyncLocal<(string traceIdentifier, HttpContext context)> _httpContextCurrent = new AsyncLocal<(string traceIdentifier, HttpContext context)>(); private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
public HttpContext HttpContext public HttpContext HttpContext
{ {
get get
{ {
var value = _httpContextCurrent.Value; return _httpContextCurrent.Value?.Context;
// Only return the context if the stored request id matches the stored trace identifier
// context.TraceIdentifier is cleared by HttpContextFactory.Dispose.
return value.traceIdentifier == value.context?.TraceIdentifier ? value.context : null;
} }
set set
{ {
_httpContextCurrent.Value = (value?.TraceIdentifier, value); var holder = _httpContextCurrent.Value;
if (holder != null)
{
// Clear current HttpContext trapped in the AsyncLocals, as its done.
holder.Context = null;
} }
if (value != null)
{
// Use an object indirection to hold the HttpContext in the AsyncLocal,
// so it can be cleared in all ExecutionContexts when its cleared.
_httpContextCurrent.Value = new HttpContextHolder { Context = value };
}
}
}
private class HttpContextHolder
{
public HttpContext Context;
} }
} }
} }

View File

@ -53,10 +53,6 @@ namespace Microsoft.AspNetCore.Http
{ {
_httpContextAccessor.HttpContext = null; _httpContextAccessor.HttpContext = null;
} }
// Null out the TraceIdentifier here as a sign that this request is done,
// the HttpContextAccessor implementation relies on this to detect that the request is over
httpContext.TraceIdentifier = null;
} }
} }
} }

View File

@ -44,7 +44,6 @@ namespace Microsoft.AspNetCore.Http
var accessor = new HttpContextAccessor(); var accessor = new HttpContextAccessor();
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.TraceIdentifier = "1";
accessor.HttpContext = context; accessor.HttpContext = context;
var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -76,7 +75,6 @@ namespace Microsoft.AspNetCore.Http
// Null out the accessor // Null out the accessor
accessor.HttpContext = null; accessor.HttpContext = null;
context.TraceIdentifier = null;
waitForNullTcs.SetResult(null); waitForNullTcs.SetResult(null);
@ -86,12 +84,11 @@ namespace Microsoft.AspNetCore.Http
} }
[Fact] [Fact]
public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfDifferentTraceIdentifier() public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfChanged()
{ {
var accessor = new HttpContextAccessor(); var accessor = new HttpContextAccessor();
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.TraceIdentifier = "1";
accessor.HttpContext = context; accessor.HttpContext = context;
var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -121,12 +118,8 @@ namespace Microsoft.AspNetCore.Http
await checkAsyncFlowTcs.Task; await checkAsyncFlowTcs.Task;
// Reset the trace identifier on the first request
context.TraceIdentifier = null;
// Set a new http context // Set a new http context
var context2 = new DefaultHttpContext(); var context2 = new DefaultHttpContext();
context2.TraceIdentifier = "2";
accessor.HttpContext = context2; accessor.HttpContext = context2;
waitForNullTcs.SetResult(null); waitForNullTcs.SetResult(null);
@ -142,7 +135,6 @@ namespace Microsoft.AspNetCore.Http
var accessor = new HttpContextAccessor(); var accessor = new HttpContextAccessor();
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.TraceIdentifier = "1";
accessor.HttpContext = context; accessor.HttpContext = context;
var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -172,7 +164,6 @@ namespace Microsoft.AspNetCore.Http
var accessor = new HttpContextAccessor(); var accessor = new HttpContextAccessor();
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.TraceIdentifier = "1";
accessor.HttpContext = context; accessor.HttpContext = context;
var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

View File

@ -34,7 +34,6 @@ namespace Microsoft.AspNetCore.Http
// Act // Act
var context = contextFactory.Create(new FeatureCollection()); var context = contextFactory.Create(new FeatureCollection());
var traceIdentifier = context.TraceIdentifier;
// Assert // Assert
Assert.Same(context, accessor.HttpContext); Assert.Same(context, accessor.HttpContext);
@ -42,7 +41,6 @@ namespace Microsoft.AspNetCore.Http
contextFactory.Dispose(context); contextFactory.Dispose(context);
Assert.Null(accessor.HttpContext); Assert.Null(accessor.HttpContext);
Assert.NotEqual(traceIdentifier, context.TraceIdentifier);
} }
[Fact] [Fact]