Skip to content

Commit 21b925f

Browse files
authored
Prefer IHttpContextAccessor over HttpContextAccessor (#471)
This change will check if we're currently in a runtime environment that contains a IHttpContextAccessor; if we are, then we'll prefer that over HttpContextAccessor. This allows people to potentially customize what it means to get and set a HttpContext.
1 parent 115030b commit 21b925f

File tree

4 files changed

+100
-5
lines changed

4 files changed

+100
-5
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters/Hosting/HostingEnvironmentAccessor.cs

+27
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using System.Threading;
6+
using Microsoft.AspNetCore.Http;
57
using Microsoft.AspNetCore.SystemWebAdapters;
8+
using Microsoft.Extensions.DependencyInjection;
69
using Microsoft.Extensions.Options;
710

811
namespace System.Web.Hosting;
912

1013
internal sealed class HostingEnvironmentAccessor
1114
{
15+
private static HttpContextAccessor? _defaultHttpContextAccessor;
1216
private static HostingEnvironmentAccessor? _current;
1317

1418
private readonly IOptions<SystemWebAdaptersOptions> _options;
19+
private readonly IHttpContextAccessor? _accessor;
1520

1621
public HostingEnvironmentAccessor(IServiceProvider services, IOptions<SystemWebAdaptersOptions> options)
1722
{
1823
Services = services;
24+
_accessor = services.GetService<IHttpContextAccessor>();
1925
_options = options;
2026
}
2127

@@ -42,5 +48,26 @@ public static bool TryGet([MaybeNullWhen(false)] out HostingEnvironmentAccessor
4248
return current is not null;
4349
}
4450

51+
/// <summary>
52+
/// Gets an <see cref="IHttpContextAccessor"/> that is either registered to the current hosting runtime or the default one via <see cref="HttpContextAccessor"/>.
53+
/// </summary>
54+
public static IHttpContextAccessor HttpContextAccessor
55+
{
56+
get
57+
{
58+
if (_current?._accessor is { } current)
59+
{
60+
return current;
61+
}
62+
63+
if (_defaultHttpContextAccessor is null)
64+
{
65+
Interlocked.CompareExchange(ref _defaultHttpContextAccessor, new(), null);
66+
}
67+
68+
return _defaultHttpContextAccessor;
69+
}
70+
}
71+
4572
internal SystemWebAdaptersOptions Options => _options.Value;
4673
}

src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
using System.Linq;
77
using System.Security.Principal;
88
using System.Web.Caching;
9+
using System.Web.Hosting;
910
using System.Web.SessionState;
1011
using Microsoft.AspNetCore.Hosting;
11-
using Microsoft.AspNetCore.Http;
1212
using Microsoft.AspNetCore.Http.Features.Authentication;
1313
using Microsoft.AspNetCore.SystemWebAdapters;
1414
using Microsoft.AspNetCore.SystemWebAdapters.Features;
@@ -20,8 +20,6 @@ namespace System.Web;
2020

2121
public class HttpContext : IServiceProvider
2222
{
23-
private static readonly HttpContextAccessor _accessor = new();
24-
2523
private HttpRequest? _request;
2624
private HttpResponse? _response;
2725
private HttpServerUtility? _server;
@@ -30,8 +28,8 @@ public class HttpContext : IServiceProvider
3028

3129
public static HttpContext? Current
3230
{
33-
get => _accessor.HttpContext?.AsSystemWeb();
34-
set => _accessor.HttpContext = value?.AsAspNetCore();
31+
get => HostingEnvironmentAccessor.HttpContextAccessor.HttpContext?.AsSystemWeb();
32+
set => HostingEnvironmentAccessor.HttpContextAccessor.HttpContext = value?.AsAspNetCore();
3533
}
3634

3735
internal HttpContext(HttpContextCore context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.TestHost;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
10+
using Moq;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests;
14+
15+
[Collection(nameof(SelfHostedTests))]
16+
public class HttpContextCurrentTests
17+
{
18+
[Fact]
19+
public void CurrentReturnsNullByDefault()
20+
{
21+
Assert.Null(HttpContext.Current);
22+
}
23+
24+
[Fact]
25+
public void SavesByDefaultToHttpContextAccessor()
26+
{
27+
// Arrange
28+
var accessor = new HttpContextAccessor();
29+
var context = new DefaultHttpContext();
30+
31+
// Act
32+
HttpContext.Current = context;
33+
34+
// Assert
35+
Assert.Same(accessor.HttpContext, context);
36+
}
37+
38+
[Fact]
39+
public async Task UsesIHttpContextAccessor()
40+
{
41+
// Arrange
42+
var accessor = new Mock<IHttpContextAccessor>();
43+
accessor.SetupAllProperties();
44+
45+
using var host = await new HostBuilder()
46+
.ConfigureWebHost(webBuilder =>
47+
{
48+
webBuilder
49+
.UseTestServer()
50+
.ConfigureServices(services =>
51+
{
52+
services.AddSingleton(accessor.Object);
53+
services.AddSystemWebAdapters();
54+
})
55+
.Configure(app =>
56+
{
57+
});
58+
})
59+
.StartAsync();
60+
var context = new DefaultHttpContext();
61+
62+
// Act
63+
HttpContext.Current = context;
64+
65+
// Assert
66+
Assert.Same(context, accessor.Object.HttpContext);
67+
Assert.Null(new HttpContextAccessor().HttpContext);
68+
}
69+
}

test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/ResponseHeaderTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.Extensions.Hosting;
1414
using Microsoft.Net.Http.Headers;
1515
using Xunit;
16+
1617
using SameSiteMode = System.Web.SameSiteMode;
1718

1819
namespace Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests;

0 commit comments

Comments
 (0)