diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj
index 0a1bcdd0b9..8e8c1dfce6 100644
--- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj
+++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj
@@ -5,6 +5,8 @@
-
+
+
+
diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs
index 618082bc4a..b5a3acf406 100644
--- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs
+++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs
@@ -1,3 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+namespace Microsoft.AspNetCore.Builder
+{
+ public static partial class RequestThrottlingExtensions
+ {
+ public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseRequestThrottling(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
+ }
+}
+namespace Microsoft.AspNetCore.RequestThrottling
+{
+ public partial class RequestThrottlingMiddleware
+ {
+ public RequestThrottlingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions options) { }
+ [System.Diagnostics.DebuggerStepThroughAttribute]
+ public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
+ }
+ public partial class RequestThrottlingOptions
+ {
+ public RequestThrottlingOptions() { }
+ public int? MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj
index 0f80e6516a..9f49f115c0 100644
--- a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj
+++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj
@@ -1,13 +1,13 @@
-
+
netcoreapp3.0
-
+
diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs
index 95a94be56d..113f48d860 100644
--- a/src/Middleware/RequestThrottling/sample/Startup.cs
+++ b/src/Middleware/RequestThrottling/sample/Startup.cs
@@ -1,12 +1,12 @@
-using System;
-using System.Collections.Generic;
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
using System.IO;
-using System.Linq;
-using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.RequestThrottling;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -19,13 +19,20 @@ namespace RequestThrottlingSample
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
+ services.Configure(options =>
+ {
+ options.MaxConcurrentRequests = 2;
+ });
}
- public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
+ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
+ app.UseRequestThrottling();
+
app.Run(async context =>
{
- await context.Response.WriteAsync("Hello world!");
+ await context.Response.WriteAsync("Hello Request Throttling! ");
+ await Task.Delay(1000);
});
}
diff --git a/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs b/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs
new file mode 100644
index 0000000000..a09ddeb79d
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.RequestThrottling.Internal
+{
+ internal class RequestQueue : IDisposable
+ {
+ private SemaphoreSlim _semaphore;
+ private object _waitingRequestsLock = new object();
+ public readonly int MaxConcurrentRequests;
+ public int WaitingRequests { get; private set; }
+
+ public RequestQueue(int maxConcurrentRequests)
+ {
+ MaxConcurrentRequests = maxConcurrentRequests;
+ _semaphore = new SemaphoreSlim(maxConcurrentRequests);
+ }
+
+ public async Task EnterQueue()
+ {
+ var waitInQueueTask = _semaphore.WaitAsync();
+
+ var needsToWaitOnQueue = !waitInQueueTask.IsCompletedSuccessfully;
+ if (needsToWaitOnQueue)
+ {
+ lock (_waitingRequestsLock)
+ {
+ WaitingRequests++;
+ }
+
+ await waitInQueueTask;
+
+ lock (_waitingRequestsLock)
+ {
+ WaitingRequests--;
+ }
+ }
+ }
+
+ public void Release()
+ {
+ _semaphore.Release();
+ }
+
+ public int Count
+ {
+ get => _semaphore.CurrentCount;
+ }
+
+ public int ConcurrentRequests
+ {
+ get => MaxConcurrentRequests - _semaphore.CurrentCount;
+ }
+
+ public void Dispose()
+ {
+ _semaphore.Dispose();
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj
index 5014e9cec5..0090f373c0 100644
--- a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj
+++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj
@@ -1,4 +1,4 @@
-
+
ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation.
@@ -7,4 +7,10 @@
aspnetcore;queue;queuing
+
+
+
+
+
+
diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs
new file mode 100644
index 0000000000..17968b44d5
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.RequestThrottling;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for adding the to an application.
+ ///
+ public static class RequestThrottlingExtensions
+ {
+ ///
+ /// Adds the to limit the number of concurrently-executing requests.
+ ///
+ /// The .
+ /// The .
+ public static IApplicationBuilder UseRequestThrottling(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs
new file mode 100644
index 0000000000..9eaec8cfbc
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs
@@ -0,0 +1,116 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.RequestThrottling.Internal;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.RequestThrottling
+{
+ ///
+ /// Limits the number of concurrent requests allowed in the application.
+ ///
+ public class RequestThrottlingMiddleware
+ {
+ private readonly RequestQueue _requestQueue;
+ private readonly RequestThrottlingOptions _options;
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The representing the next middleware in the pipeline.
+ /// The used for logging.
+ /// The containing the initialization parameters.
+ public RequestThrottlingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options)
+ {
+ if (options.Value.MaxConcurrentRequests == null)
+ {
+ throw new ArgumentException("The value of 'options.MaxConcurrentRequests' must be specified.", nameof(options));
+ }
+
+ _next = next;
+ _logger = loggerFactory.CreateLogger();
+ _options = options.Value;
+ _requestQueue = new RequestQueue(_options.MaxConcurrentRequests.Value);
+ }
+
+ ///
+ /// Invokes the logic of the middleware.
+ ///
+ /// The .
+ /// A that completes when the request leaves.
+ public async Task Invoke(HttpContext context)
+ {
+ var waitInQueueTask = _requestQueue.EnterQueue();
+
+ if (waitInQueueTask.IsCompletedSuccessfully)
+ {
+ RequestThrottlingLog.RequestRunImmediately(_logger);
+ }
+ else
+ {
+ RequestThrottlingLog.RequestEnqueued(_logger, WaitingRequests);
+ await waitInQueueTask;
+ RequestThrottlingLog.RequestDequeued(_logger, WaitingRequests);
+ }
+
+ try
+ {
+ await _next(context);
+ }
+ finally
+ {
+ _requestQueue.Release();
+ }
+ }
+
+ ///
+ /// The number of live requests that are downstream from this middleware.
+ /// Cannot exceeed .
+ ///
+ internal int ConcurrentRequests
+ {
+ get => _requestQueue.ConcurrentRequests;
+ }
+
+ ///
+ /// Number of requests currently enqueued and waiting to be processed.
+ ///
+ internal int WaitingRequests
+ {
+ get => _requestQueue.WaitingRequests;
+ }
+
+ private static class RequestThrottlingLog
+ {
+ private static readonly Action _requestEnqueued =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(1, "RequestEnqueued"), "Concurrent request limit reached, queuing request. Current queue length: {QueuedRequests}.");
+
+ private static readonly Action _requestDequeued =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(2, "RequestDequeued"), "Request dequeued. Current queue length: {QueuedRequests}.");
+
+ private static readonly Action _requestRunImmediately =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Concurrent request limit has not been reached, running request immediately.");
+
+ internal static void RequestEnqueued(ILogger logger, int queuedRequests)
+ {
+ _requestEnqueued(logger, queuedRequests, null);
+ }
+
+ internal static void RequestDequeued(ILogger logger, int queuedRequests)
+ {
+ _requestDequeued(logger, queuedRequests, null);
+ }
+
+ internal static void RequestRunImmediately(ILogger logger)
+ {
+ _requestRunImmediately(logger, null);
+ }
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs
new file mode 100644
index 0000000000..64a832640f
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.RequestThrottling;
+
+namespace Microsoft.AspNetCore.RequestThrottling
+{
+ ///
+ /// Specifies options for the .
+ ///
+ public class RequestThrottlingOptions
+ {
+ ///
+ /// Maximum number of concurrent requests. Any extras will be queued on the server.
+ /// This is null by default because the correct value is application specific. This option must be configured by the application.
+ ///
+ public int? MaxConcurrentRequests { get; set; }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs
deleted file mode 100644
index 4c79b94777..0000000000
--- a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.RequestThrottling
-{
- internal class SemaphoreWrapper : IDisposable
- {
- private SemaphoreSlim _semaphore;
-
- public SemaphoreWrapper(int queueLength)
- {
- _semaphore = new SemaphoreSlim(queueLength);
- }
-
- public Task EnterQueue()
- {
- return _semaphore.WaitAsync();
- }
-
- public void LeaveQueue()
- {
- _semaphore.Release();
- }
-
- public int Count
- {
- get => _semaphore.CurrentCount;
- }
-
- public void Dispose()
- {
- _semaphore.Dispose();
- }
- }
-}
diff --git a/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj
index 8c0dd8e989..78b1c88692 100644
--- a/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj
+++ b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj
@@ -1,10 +1,16 @@
-
+
netcoreapp3.0
+
+
+
+
+
+
diff --git a/src/Middleware/RequestThrottling/test/MiddlewareTests.cs b/src/Middleware/RequestThrottling/test/MiddlewareTests.cs
new file mode 100644
index 0000000000..8124cddb49
--- /dev/null
+++ b/src/Middleware/RequestThrottling/test/MiddlewareTests.cs
@@ -0,0 +1,86 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Internal;
+using Xunit;
+
+namespace Microsoft.AspNetCore.RequestThrottling.Tests
+{
+ public class MiddlewareTests
+ {
+ [Fact]
+ public async Task RequestsCanEnterIfSpaceAvailible()
+ {
+ var middleware = TestUtils.CreateTestMiddleWare(maxConcurrentRequests: 1);
+ var context = new DefaultHttpContext();
+
+ // a request should go through with no problems
+ await middleware.Invoke(context).OrTimeout();
+ }
+
+ [Fact]
+ public async Task SemaphoreStatePreservedIfRequestsError()
+ {
+ var middleware = TestUtils.CreateTestMiddleWare(
+ maxConcurrentRequests: 1,
+ next: httpContext =>
+ {
+ throw new DivideByZeroException();
+ });
+
+ Assert.Equal(0, middleware.ConcurrentRequests);
+
+ await Assert.ThrowsAsync(() => middleware.Invoke(new DefaultHttpContext()));
+
+ Assert.Equal(0, middleware.ConcurrentRequests);
+ }
+
+ [Fact]
+ public async Task QueuedRequestsContinueWhenSpaceBecomesAvailible()
+ {
+ var blocker = new SyncPoint();
+ var firstRequest = true;
+
+ var middleware = TestUtils.CreateTestMiddleWare(
+ maxConcurrentRequests: 1,
+ next: httpContext =>
+ {
+ if (firstRequest)
+ {
+ firstRequest = false;
+ return blocker.WaitToContinue();
+ }
+ return Task.CompletedTask;
+ });
+
+ // t1 (as the first request) is blocked by the tcs blocker
+ var t1 = middleware.Invoke(new DefaultHttpContext());
+ Assert.Equal(1, middleware.ConcurrentRequests);
+ Assert.Equal(0, middleware.WaitingRequests);
+
+ // t2 is blocked from entering the server since t1 already exists there
+ // note: increasing MaxConcurrentRequests would allow t2 through while t1 is blocked
+ var t2 = middleware.Invoke(new DefaultHttpContext());
+ Assert.Equal(1, middleware.ConcurrentRequests);
+ Assert.Equal(1, middleware.WaitingRequests);
+
+ // unblock the first task, and the second should follow
+ blocker.Continue();
+ await t1.OrTimeout();
+ await t2.OrTimeout();
+ }
+
+ [Fact]
+ public void InvalidArgumentIfMaxConcurrentRequestsIsNull()
+ {
+ var ex = Assert.Throws(() =>
+ {
+ TestUtils.CreateTestMiddleWare(maxConcurrentRequests: null);
+ });
+ Assert.Equal("options", ex.ParamName);
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/RequestQueueTests.cs
similarity index 60%
rename from src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs
rename to src/Middleware/RequestThrottling/test/RequestQueueTests.cs
index b5cdfce18f..67eeda5d67 100644
--- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs
+++ b/src/Middleware/RequestThrottling/test/RequestQueueTests.cs
@@ -1,34 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using Xunit;
-using System.Threading;
using System.Threading.Tasks;
-using System;
-using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Testing;
+using Microsoft.AspNetCore.RequestThrottling.Internal;
+using Xunit;
namespace Microsoft.AspNetCore.RequestThrottling.Tests
{
- public class SemaphoreWrapperTests
+ public class RequestQueueTests
{
[Fact]
- public async Task TracksQueueLength()
+ public async Task LimitsIncomingRequests()
{
- using var s = new SemaphoreWrapper(1);
+ using var s = new RequestQueue(1);
Assert.Equal(1, s.Count);
await s.EnterQueue().OrTimeout();
Assert.Equal(0, s.Count);
- s.LeaveQueue();
+ s.Release();
Assert.Equal(1, s.Count);
}
+ [Fact]
+ public async Task TracksQueueLength()
+ {
+ using var s = new RequestQueue(1);
+ Assert.Equal(0, s.WaitingRequests);
+
+ await s.EnterQueue();
+ Assert.Equal(0, s.WaitingRequests);
+
+ var enterQueueTask = s.EnterQueue();
+ Assert.Equal(1, s.WaitingRequests);
+
+ s.Release();
+ await enterQueueTask;
+ Assert.Equal(0, s.WaitingRequests);
+ }
+
[Fact]
public void DoesNotWaitIfSpaceAvailible()
{
- using var s = new SemaphoreWrapper(2);
+ using var s = new RequestQueue(2);
var t1 = s.EnterQueue();
Assert.True(t1.IsCompleted);
@@ -43,21 +57,21 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests
[Fact]
public async Task WaitsIfNoSpaceAvailible()
{
- using var s = new SemaphoreWrapper(1);
+ using var s = new RequestQueue(1);
await s.EnterQueue().OrTimeout();
var waitingTask = s.EnterQueue();
Assert.False(waitingTask.IsCompleted);
- s.LeaveQueue();
+ s.Release();
await waitingTask.OrTimeout();
}
[Fact]
public async Task IsEncapsulated()
{
- using var s1 = new SemaphoreWrapper(1);
- using var s2 = new SemaphoreWrapper(1);
+ using var s1 = new RequestQueue(1);
+ using var s2 = new RequestQueue(1);
await s1.EnterQueue().OrTimeout();
await s2.EnterQueue().OrTimeout();
diff --git a/src/Middleware/RequestThrottling/test/TestUtils.cs b/src/Middleware/RequestThrottling/test/TestUtils.cs
new file mode 100644
index 0000000000..438d08ab8d
--- /dev/null
+++ b/src/Middleware/RequestThrottling/test/TestUtils.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.RequestThrottling;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.RequestThrottling.Tests
+{
+ public static class TestUtils
+ {
+ public static RequestThrottlingMiddleware CreateTestMiddleWare(int? maxConcurrentRequests, RequestDelegate next = null)
+ {
+ var options = new RequestThrottlingOptions
+ {
+ MaxConcurrentRequests = maxConcurrentRequests
+ };
+
+ return new RequestThrottlingMiddleware(
+ next: next ?? (context => Task.CompletedTask),
+ loggerFactory: NullLoggerFactory.Instance,
+ options: Options.Create(options)
+ );
+ }
+ }
+}
diff --git a/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs b/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs
index ca2e7fbcf3..6184a36946 100644
--- a/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs
+++ b/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
diff --git a/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj b/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj
index 9611dfbdaf..ef8199808c 100644
--- a/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj
+++ b/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj
@@ -1,4 +1,4 @@
-
+
ASP.NET Core middleware for caching HTTP responses on the server.
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj
new file mode 100644
index 0000000000..36c3a47a9a
--- /dev/null
+++ b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs
new file mode 100644
index 0000000000..618082bc4a
--- /dev/null
+++ b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs
@@ -0,0 +1,3 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
diff --git a/src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs b/src/Shared/SyncPoint/SyncPoint.cs
similarity index 98%
rename from src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs
rename to src/Shared/SyncPoint/SyncPoint.cs
index 55f4a034d5..ccf36d59df 100644
--- a/src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs
+++ b/src/Shared/SyncPoint/SyncPoint.cs
@@ -4,7 +4,7 @@
using System;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.SignalR.Tests
+namespace Microsoft.AspNetCore.Internal
{
public class SyncPoint
{
diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs
index 2088ef3927..61821226b2 100644
--- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs
+++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
@@ -90,8 +91,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
var startCounter = 0;
var expected = new Exception("Transport failed to start");
- // We have 4 cases here. Falling back once, falling back twice and each of these
- // with WebSockets available and not. If Websockets aren't available and
+ // We have 4 cases here. Falling back once, falling back twice and each of these
+ // with WebSockets available and not. If Websockets aren't available and
// we can't to test the fallback once scenario we don't decrement the passthreshold
// because we still try to start twice (SSE and LP).
if (!TestHelpers.IsWebSocketsSupported() && passThreshold > 2)
diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs
index f48742474b..647aa433ca 100644
--- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs
+++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.DependencyInjection;
diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj b/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj
index a2b40fbe25..cc45556edd 100644
--- a/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj
+++ b/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.0
@@ -8,6 +8,7 @@
+
diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs
index 421e1cef61..aa3c41bbed 100644
--- a/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs
+++ b/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging.Testing;
using Moq;
diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs
index 5b4c164ce3..2fcf129790 100644
--- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs
+++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs
@@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
diff --git a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj
index 6c03dfe4eb..1f6b36a8ba 100644
--- a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj
+++ b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.0
@@ -6,6 +6,7 @@
+
diff --git a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj
index c4fd5fd37b..24958a00b6 100644
--- a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj
+++ b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj
@@ -1,9 +1,12 @@
-
+
netcoreapp3.0
+
+
+
@@ -23,4 +26,4 @@
-
+
\ No newline at end of file
diff --git a/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs b/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs
index b69952b523..da55a5e675 100644
--- a/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs
+++ b/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Xunit;