Skip to content

Commit dad1ca6

Browse files
Make RequestServices work for cloned longpolling HttpContext (dotnet#23014)
1 parent 5155e11 commit dad1ca6

File tree

3 files changed

+265
-79
lines changed

3 files changed

+265
-79
lines changed

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
1717
using Microsoft.AspNetCore.Http.Features;
1818
using Microsoft.AspNetCore.Internal;
19+
using Microsoft.Extensions.DependencyInjection;
1920
using Microsoft.Extensions.Logging;
2021

2122
namespace Microsoft.AspNetCore.Http.Connections.Internal
@@ -99,6 +100,9 @@ public HttpConnectionContext(string connectionId, string connectionToken, ILogge
99100
// Used for testing only
100101
internal Task DisposeAndRemoveTask { get; set; }
101102

103+
// Used for LongPolling because we need to create a scope that spans the lifetime of multiple requests on the cloned HttpContext
104+
internal IServiceScope ServiceScope { get; set; }
105+
102106
public Task TransportTask { get; set; }
103107

104108
public Task PreviousPollTask { get; set; } = Task.CompletedTask;
@@ -251,6 +255,8 @@ public async Task DisposeAsync(bool closeGracefully = false)
251255
(identity as IDisposable)?.Dispose();
252256
}
253257
}
258+
259+
ServiceScope?.Dispose();
254260
}
255261

256262
await disposeTask;

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
1515
using Microsoft.AspNetCore.Http.Features;
1616
using Microsoft.AspNetCore.Internal;
17+
using Microsoft.Extensions.DependencyInjection;
1718
using Microsoft.Extensions.Logging;
1819
using Microsoft.Extensions.Primitives;
1920

@@ -537,8 +538,7 @@ private async Task<bool> EnsureConnectionStateAsync(HttpConnectionContext connec
537538
var existing = connection.HttpContext;
538539
if (existing == null)
539540
{
540-
var httpContext = CloneHttpContext(context);
541-
connection.HttpContext = httpContext;
541+
CloneHttpContext(context, connection);
542542
}
543543
else
544544
{
@@ -606,7 +606,7 @@ private static void CloneUser(HttpContext newContext, HttpContext oldContext)
606606
}
607607
}
608608

609-
private static HttpContext CloneHttpContext(HttpContext context)
609+
private static void CloneHttpContext(HttpContext context, HttpConnectionContext connection)
610610
{
611611
// The reason we're copying the base features instead of the HttpContext properties is
612612
// so that we can get all of the logic built into DefaultHttpContext to extract higher level
@@ -660,14 +660,13 @@ private static HttpContext CloneHttpContext(HttpContext context)
660660

661661
CloneUser(newHttpContext, context);
662662

663-
// Making request services function property could be tricky and expensive as it would require
664-
// DI scope per connection. It would also mean that services resolved in middleware leading up to here
665-
// wouldn't be the same instance (but maybe that's fine). For now, we just return an empty service provider
666-
newHttpContext.RequestServices = EmptyServiceProvider.Instance;
663+
connection.ServiceScope = context.RequestServices.CreateScope();
664+
newHttpContext.RequestServices = connection.ServiceScope.ServiceProvider;
667665

668666
// REVIEW: This extends the lifetime of anything that got put into HttpContext.Items
669667
newHttpContext.Items = new Dictionary<object, object>(context.Items);
670-
return newHttpContext;
668+
669+
connection.HttpContext = newHttpContext;
671670
}
672671

673672
private async Task<HttpConnectionContext> GetConnectionAsync(HttpContext context)

0 commit comments

Comments
 (0)