DI enabled Microsoft.AspNetCore.Sockets (#47)
* DI enabled Microsoft.AspNetCore.Sockets - Added AddSockets extension method to IServiceCollection - Inject IApplicationLifetime into ConnectionManager to handle graceful shutdown and added test. - Call AddSockets from AddSignalR * PR feedback - Added AddSignalR overload that takes Action<SignalROptions>
This commit is contained in:
parent
7b814e8d92
commit
1b59fc6f80
|
|
@ -16,18 +16,17 @@ namespace SocketsSample
|
||||||
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
|
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddRouting();
|
|
||||||
|
|
||||||
services.AddSingleton<ProtobufInvocationAdapter>();
|
services.AddSingleton<ProtobufInvocationAdapter>();
|
||||||
services.AddSingleton<LineInvocationAdapter>();
|
services.AddSingleton<LineInvocationAdapter>();
|
||||||
|
|
||||||
services.AddSignalR()
|
services.AddSockets();
|
||||||
.AddSignalROptions(options =>
|
|
||||||
|
services.AddSignalR(options =>
|
||||||
{
|
{
|
||||||
options.RegisterInvocationAdapter<ProtobufInvocationAdapter>("protobuf");
|
options.RegisterInvocationAdapter<ProtobufInvocationAdapter>("protobuf");
|
||||||
options.RegisterInvocationAdapter<LineInvocationAdapter>("line");
|
options.RegisterInvocationAdapter<LineInvocationAdapter>("line");
|
||||||
});
|
});
|
||||||
// .AddRedis();
|
// .AddRedis();
|
||||||
|
|
||||||
services.AddSingleton<ChatEndPoint>();
|
services.AddSingleton<ChatEndPoint>();
|
||||||
services.AddSingleton<ProtobufSerializer>();
|
services.AddSingleton<ProtobufSerializer>();
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
public static class DependencyInjectionExtensions
|
public static class SignalRDependencyInjectionExtensions
|
||||||
{
|
{
|
||||||
public static ISignalRBuilder AddSignalR(this IServiceCollection services)
|
public static ISignalRBuilder AddSignalR(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
services.AddSockets();
|
||||||
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
||||||
services.AddSingleton(typeof(IHubContext<>), typeof(HubContext<>));
|
services.AddSingleton(typeof(IHubContext<>), typeof(HubContext<>));
|
||||||
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
||||||
|
|
@ -22,9 +23,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return new SignalRBuilder(services);
|
return new SignalRBuilder(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ISignalRBuilder AddSignalROptions(this ISignalRBuilder builder, Action<SignalROptions> configure)
|
public static ISignalRBuilder AddSignalR(this IServiceCollection services, Action<SignalROptions> setupAction)
|
||||||
{
|
{
|
||||||
builder.Services.Configure(configure);
|
return services.AddSignalR().AddSignalROptions(setupAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ISignalRBuilder AddSignalROptions(this ISignalRBuilder builder, Action<SignalROptions> setupAction)
|
||||||
|
{
|
||||||
|
builder.Services.Configure(setupAction);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,17 +5,22 @@ using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
{
|
{
|
||||||
public class ConnectionManager : IDisposable
|
public class ConnectionManager
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
|
private ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
|
||||||
public ConnectionManager()
|
public ConnectionManager(IApplicationLifetime lifetime)
|
||||||
{
|
{
|
||||||
_timer = new Timer(Scan, this, 0, 1000);
|
_timer = new Timer(Scan, this, 0, 1000);
|
||||||
|
|
||||||
|
// We hook stopping because we need the requests to end, Dispose doesn't work since
|
||||||
|
// that happens after requests are drained
|
||||||
|
lifetime.ApplicationStopping.Register(CloseConnections);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetConnection(string id, out ConnectionState state)
|
public bool TryGetConnection(string id, out ConnectionState state)
|
||||||
|
|
@ -96,7 +101,7 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private void CloseConnections()
|
||||||
{
|
{
|
||||||
// Stop firing the timer
|
// Stop firing the timer
|
||||||
_timer.Dispose();
|
_timer.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO.Pipelines;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Sockets;
|
using Microsoft.AspNetCore.Sockets;
|
||||||
using Microsoft.AspNetCore.Sockets.Routing;
|
using Microsoft.AspNetCore.Sockets.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
|
|
@ -16,15 +13,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
public static IApplicationBuilder UseSockets(this IApplicationBuilder app, Action<SocketRouteBuilder> callback)
|
public static IApplicationBuilder UseSockets(this IApplicationBuilder app, Action<SocketRouteBuilder> callback)
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var dispatcher = app.ApplicationServices.GetRequiredService<HttpConnectionDispatcher>();
|
||||||
var factory = new PipelineFactory();
|
|
||||||
|
|
||||||
var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
|
|
||||||
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory);
|
|
||||||
|
|
||||||
// Dispose the connection manager when application shutdown is triggered
|
|
||||||
var lifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
|
|
||||||
lifetime.ApplicationStopping.Register(state => ((IDisposable)state).Dispose(), manager);
|
|
||||||
|
|
||||||
var routes = new RouteBuilder(app);
|
var routes = new RouteBuilder(app);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Sockets;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public static class SocketsDependencyInjectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddSockets(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddRouting();
|
||||||
|
services.TryAddSingleton<ConnectionManager>();
|
||||||
|
services.TryAddSingleton<PipelineFactory>();
|
||||||
|
services.TryAddSingleton<HttpConnectionDispatcher>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets.Tests
|
namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
|
|
@ -15,7 +13,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReservedConnectionsHaveConnectionId()
|
public void ReservedConnectionsHaveConnectionId()
|
||||||
{
|
{
|
||||||
var connectionManager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var connectionManager = new ConnectionManager(lifetime);
|
||||||
var state = connectionManager.ReserveConnection();
|
var state = connectionManager.ReserveConnection();
|
||||||
|
|
||||||
Assert.NotNull(state.Connection);
|
Assert.NotNull(state.Connection);
|
||||||
|
|
@ -28,7 +27,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ReservedConnectionsCanBeRetrieved()
|
public void ReservedConnectionsCanBeRetrieved()
|
||||||
{
|
{
|
||||||
var connectionManager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var connectionManager = new ConnectionManager(lifetime);
|
||||||
var state = connectionManager.ReserveConnection();
|
var state = connectionManager.ReserveConnection();
|
||||||
|
|
||||||
Assert.NotNull(state.Connection);
|
Assert.NotNull(state.Connection);
|
||||||
|
|
@ -43,10 +43,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
public void AddNewConnection()
|
public void AddNewConnection()
|
||||||
{
|
{
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
using (var channel = new HttpConnection(factory))
|
using (var connection = new HttpConnection(factory))
|
||||||
{
|
{
|
||||||
var connectionManager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
var state = connectionManager.AddNewConnection(channel);
|
var connectionManager = new ConnectionManager(lifetime);
|
||||||
|
var state = connectionManager.AddNewConnection(connection);
|
||||||
|
|
||||||
Assert.NotNull(state.Connection);
|
Assert.NotNull(state.Connection);
|
||||||
Assert.NotNull(state.Connection.ConnectionId);
|
Assert.NotNull(state.Connection.ConnectionId);
|
||||||
|
|
@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
ConnectionState newState;
|
ConnectionState newState;
|
||||||
Assert.True(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
Assert.True(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
||||||
Assert.Same(newState, state);
|
Assert.Same(newState, state);
|
||||||
Assert.Same(channel, newState.Connection.Channel);
|
Assert.Same(connection, newState.Connection.Channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,10 +64,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
public void RemoveConnection()
|
public void RemoveConnection()
|
||||||
{
|
{
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
using (var channel = new HttpConnection(factory))
|
using (var connection = new HttpConnection(factory))
|
||||||
{
|
{
|
||||||
var connectionManager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
var state = connectionManager.AddNewConnection(channel);
|
var connectionManager = new ConnectionManager(lifetime);
|
||||||
|
var state = connectionManager.AddNewConnection(connection);
|
||||||
|
|
||||||
Assert.NotNull(state.Connection);
|
Assert.NotNull(state.Connection);
|
||||||
Assert.NotNull(state.Connection.ConnectionId);
|
Assert.NotNull(state.Connection.ConnectionId);
|
||||||
|
|
@ -75,11 +77,34 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
ConnectionState newState;
|
ConnectionState newState;
|
||||||
Assert.True(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
Assert.True(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
||||||
Assert.Same(newState, state);
|
Assert.Same(newState, state);
|
||||||
Assert.Same(channel, newState.Connection.Channel);
|
Assert.Same(connection, newState.Connection.Channel);
|
||||||
|
|
||||||
connectionManager.RemoveConnection(state.Connection.ConnectionId);
|
connectionManager.RemoveConnection(state.Connection.ConnectionId);
|
||||||
Assert.False(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
Assert.False(connectionManager.TryGetConnection(state.Connection.ConnectionId, out newState));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplicationStoppingClosesConnections()
|
||||||
|
{
|
||||||
|
using (var factory = new PipelineFactory())
|
||||||
|
using (var connection = new HttpConnection(factory))
|
||||||
|
{
|
||||||
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var connectionManager = new ConnectionManager(lifetime);
|
||||||
|
var state = connectionManager.AddNewConnection(connection);
|
||||||
|
|
||||||
|
var task = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var result = await connection.Input.ReadAsync();
|
||||||
|
|
||||||
|
Assert.True(result.IsCompleted);
|
||||||
|
});
|
||||||
|
|
||||||
|
lifetime.StopApplication();
|
||||||
|
|
||||||
|
await task;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Internal;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -20,7 +21,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetIdReservesConnectionIdAndReturnsIt()
|
public async Task GetIdReservesConnectionIdAndReturnsIt()
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var manager = new ConnectionManager(lifetime);
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
{
|
{
|
||||||
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
||||||
|
|
@ -41,7 +43,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SendingToReservedConnectionsThatHaveNotConnectedThrows()
|
public async Task SendingToReservedConnectionsThatHaveNotConnectedThrows()
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var manager = new ConnectionManager(lifetime);
|
||||||
var state = manager.ReserveConnection();
|
var state = manager.ReserveConnection();
|
||||||
|
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
|
|
@ -63,7 +66,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SendingToUnknownConnectionIdThrows()
|
public async Task SendingToUnknownConnectionIdThrows()
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var manager = new ConnectionManager(lifetime);
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
{
|
{
|
||||||
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
||||||
|
|
@ -83,7 +87,8 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SendingWithoutConnectionIdThrows()
|
public async Task SendingWithoutConnectionIdThrows()
|
||||||
{
|
{
|
||||||
var manager = new ConnectionManager();
|
var lifetime = new ApplicationLifetime();
|
||||||
|
var manager = new ConnectionManager(lifetime);
|
||||||
using (var factory = new PipelineFactory())
|
using (var factory = new PipelineFactory())
|
||||||
{
|
{
|
||||||
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue