From b114e4e9fd3f6ceb57947fc1c55c8f290b76b6bf Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 3 Nov 2016 00:17:01 -0700 Subject: [PATCH] Get rid of RpcEndpoint and samples - Merge RpcEndpoint and HubEndPoint, still need to move discovery of hub methods to another class. --- samples/SocketsSample/JsonRpc/Echo.cs | 15 -- samples/SocketsSample/Startup.cs | 4 +- samples/SocketsSample/wwwroot/index.html | 4 +- samples/SocketsSample/wwwroot/rpc.html | 79 -------- .../DependencyInjectionExtensions.cs | 1 - .../HubEndPoint.cs | 175 ++++++++++++++++- .../RpcEndpoint.cs | 185 ------------------ .../SignalRAppBuilderExtensions.cs | 2 +- .../HttpDispatcherAppBuilderExtensions.cs | 2 +- 9 files changed, 170 insertions(+), 297 deletions(-) delete mode 100644 samples/SocketsSample/JsonRpc/Echo.cs delete mode 100644 samples/SocketsSample/wwwroot/rpc.html delete mode 100644 src/Microsoft.AspNetCore.SignalR/RpcEndpoint.cs diff --git a/samples/SocketsSample/JsonRpc/Echo.cs b/samples/SocketsSample/JsonRpc/Echo.cs deleted file mode 100644 index e8711ad7d2..0000000000 --- a/samples/SocketsSample/JsonRpc/Echo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SocketsSample -{ - public class Echo - { - public string Send(string message) - { - return message; - } - } -} diff --git a/samples/SocketsSample/Startup.cs b/samples/SocketsSample/Startup.cs index 58e4043469..3193d98693 100644 --- a/samples/SocketsSample/Startup.cs +++ b/samples/SocketsSample/Startup.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SocketsSample.Hubs; @@ -50,8 +49,7 @@ namespace SocketsSample app.UseSockets(routes => { - routes.MapSocketEndpoint("/chat"); - routes.MapSocketEndpoint>("/jsonrpc"); + routes.MapEndpoint("/chat"); }); } } diff --git a/samples/SocketsSample/wwwroot/index.html b/samples/SocketsSample/wwwroot/index.html index 556cd5831a..dc7f4b4460 100644 --- a/samples/SocketsSample/wwwroot/index.html +++ b/samples/SocketsSample/wwwroot/index.html @@ -6,15 +6,13 @@

ASP.NET Sockets

-

Chat sample

-

JSON RPC

+

ASP.NET SignalR

diff --git a/samples/SocketsSample/wwwroot/rpc.html b/samples/SocketsSample/wwwroot/rpc.html deleted file mode 100644 index 3aaad25f12..0000000000 --- a/samples/SocketsSample/wwwroot/rpc.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - -

WebSockets

- -
- - -
- -
    - - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR/DependencyInjectionExtensions.cs b/src/Microsoft.AspNetCore.SignalR/DependencyInjectionExtensions.cs index 8b472db598..2b9a72822d 100644 --- a/src/Microsoft.AspNetCore.SignalR/DependencyInjectionExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR/DependencyInjectionExtensions.cs @@ -12,7 +12,6 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>)); services.AddSingleton(typeof(IHubContext<>), typeof(HubContext<>)); services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>)); - services.AddSingleton(typeof(RpcEndpoint<>), typeof(RpcEndpoint<>)); services.AddSingleton, SignalROptionsSetup>(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs index c29620c014..e7b7eccce8 100644 --- a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs +++ b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs @@ -1,29 +1,47 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Threading.Tasks; +using Channels; using Microsoft.AspNetCore.Sockets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.SignalR { - public class HubEndPoint : RpcEndpoint where THub : Hub + public class HubEndPoint : EndPoint where THub : Hub { + private readonly Dictionary>> _callbacks + = new Dictionary>>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _paramTypes = new Dictionary(); + private readonly HubLifetimeManager _lifetimeManager; private readonly IHubContext _hubContext; + private readonly ILogger> _logger; + private readonly InvocationAdapterRegistry _registry; + private readonly IServiceScopeFactory _serviceScopeFactory; public HubEndPoint(HubLifetimeManager lifetimeManager, IHubContext hubContext, InvocationAdapterRegistry registry, - ILoggerFactory loggerFactory, + ILogger> logger, IServiceScopeFactory serviceScopeFactory) - : base(registry, loggerFactory, serviceScopeFactory) { _lifetimeManager = lifetimeManager; _hubContext = hubContext; + _registry = registry; + _logger = logger; + _serviceScopeFactory = serviceScopeFactory; + + DiscoverHubMethods(); } public override async Task OnConnectedAsync(Connection connection) { + // TODO: Dispatch from the caller + await Task.Yield(); + try { await _lifetimeManager.OnConnectedAsync(connection); @@ -31,18 +49,18 @@ namespace Microsoft.AspNetCore.SignalR using (var scope = _serviceScopeFactory.CreateScope()) { var hub = scope.ServiceProvider.GetService() ?? Activator.CreateInstance(); - Initialize(connection, hub); + InitializeHub(connection, hub); await hub.OnConnectedAsync(); } - await base.OnConnectedAsync(connection); + await DispatchMessagesAsync(connection); } finally { using (var scope = _serviceScopeFactory.CreateScope()) { var hub = scope.ServiceProvider.GetService() ?? Activator.CreateInstance(); - Initialize(connection, hub); + InitializeHub(connection, hub); await hub.OnDisconnectedAsync(); } @@ -50,16 +68,155 @@ namespace Microsoft.AspNetCore.SignalR } } - protected override void BeforeInvoke(Connection connection, THub hub) + private async Task DispatchMessagesAsync(Connection connection) { - Initialize(connection, hub); + var stream = connection.Channel.GetStream(); + var invocationAdapter = _registry.GetInvocationAdapter(connection.Metadata.Get("formatType")); + + while (true) + { + var invocationDescriptor = + await invocationAdapter.ReadInvocationDescriptorAsync( + stream, methodName => + { + Type[] types; + // TODO: null or throw? + return _paramTypes.TryGetValue(methodName, out types) ? types : null; + }); + + // Is there a better way of detecting that a connection was closed? + if (invocationDescriptor == null) + { + break; + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Received hub invocation: {invocation}", invocationDescriptor); + } + + InvocationResultDescriptor result; + Func> callback; + if (_callbacks.TryGetValue(invocationDescriptor.Method, out callback)) + { + result = await callback(connection, invocationDescriptor); + } + else + { + // If there's no method then return a failed response for this request + result = new InvocationResultDescriptor + { + Id = invocationDescriptor.Id, + Error = $"Unknown hub method '{invocationDescriptor.Method}'" + }; + + _logger.LogError("Unknown hub method '{method}'", invocationDescriptor.Method); + } + + await invocationAdapter.WriteInvocationResultAsync(result, stream); + } } - private void Initialize(Connection connection, THub hub) + private void InitializeHub(Connection connection, THub hub) { hub.Clients = _hubContext.Clients; hub.Context = new HubCallerContext(connection); hub.Groups = new GroupManager(connection, _lifetimeManager); } + + private void DiscoverHubMethods() + { + var type = typeof(THub); + + foreach (var methodInfo in type.GetTypeInfo().DeclaredMethods.Where(m => IsHubMethod(m))) + { + var methodName = type.FullName + "." + methodInfo.Name; + + if (_callbacks.ContainsKey(methodName)) + { + throw new NotSupportedException($"Duplicate definitions of '{methodInfo.Name}'. Overloading is not supported."); + } + + var parameters = methodInfo.GetParameters(); + _paramTypes[methodName] = parameters.Select(p => p.ParameterType).ToArray(); + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Hub method '{methodName}' is bound", methodName); + } + + _callbacks[methodName] = async (connection, invocationDescriptor) => + { + var invocationResult = new InvocationResultDescriptor() + { + Id = invocationDescriptor.Id + }; + + using (var scope = _serviceScopeFactory.CreateScope()) + { + var hub = scope.ServiceProvider.GetService(); + + bool created = false; + + if (hub == null) + { + hub = Activator.CreateInstance(); + created = true; + } + + InitializeHub(connection, hub); + + try + { + var arguments = invocationDescriptor.Arguments ?? Array.Empty(); + + var args = arguments + .Zip(parameters, (a, p) => Convert.ChangeType(a, p.ParameterType)) + .ToArray(); + + var result = methodInfo.Invoke(hub, args); + var resultTask = result as Task; + if (resultTask != null) + { + await resultTask; + if (methodInfo.ReturnType.GetTypeInfo().IsGenericType) + { + var property = resultTask.GetType().GetProperty("Result"); + invocationResult.Result = property?.GetValue(resultTask); + } + } + else + { + invocationResult.Result = result; + } + } + catch (TargetInvocationException ex) + { + _logger.LogError(0, ex, "Failed to invoke hub method"); + invocationResult.Error = ex.InnerException.Message; + } + catch (Exception ex) + { + _logger.LogError(0, ex, "Failed to invoke hub method"); + invocationResult.Error = ex.Message; + } + + if (created) + { + // Dispose the object if it's disposable and we created it + (hub as IDisposable)?.Dispose(); + } + } + + return invocationResult; + }; + }; + } + + private static bool IsHubMethod(MethodInfo m) + { + // TODO: Add more checks + return m.IsPublic; + } } } diff --git a/src/Microsoft.AspNetCore.SignalR/RpcEndpoint.cs b/src/Microsoft.AspNetCore.SignalR/RpcEndpoint.cs deleted file mode 100644 index bf501c4193..0000000000 --- a/src/Microsoft.AspNetCore.SignalR/RpcEndpoint.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Channels; -using Microsoft.AspNetCore.Sockets; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.SignalR -{ - // REVIEW: Should there be an RPC package? - public class RpcEndpoint : EndPoint where T : class - { - private readonly Dictionary>> _callbacks - = new Dictionary>>(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _paramTypes = new Dictionary(); - - private readonly ILogger _logger; - private readonly InvocationAdapterRegistry _registry; - protected readonly IServiceScopeFactory _serviceScopeFactory; - - public RpcEndpoint(InvocationAdapterRegistry registry, ILoggerFactory loggerFactory, IServiceScopeFactory serviceScopeFactory) - { - _logger = loggerFactory.CreateLogger>(); - _registry = registry; - _serviceScopeFactory = serviceScopeFactory; - - RegisterRPCEndPoint(); - } - - public override async Task OnConnectedAsync(Connection connection) - { - // TODO: Dispatch from the caller - await Task.Yield(); - - var stream = connection.Channel.GetStream(); - var invocationAdapter = _registry.GetInvocationAdapter(connection.Metadata.Get("formatType")); - - while (true) - { - var invocationDescriptor = - await invocationAdapter.ReadInvocationDescriptorAsync( - stream, methodName => - { - Type[] types; - // TODO: null or throw? - return _paramTypes.TryGetValue(methodName, out types) ? types : null; - }); - - // Is there a better way of detecting that a connection was closed? - if (invocationDescriptor == null) - { - break; - } - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Received RPC request: {request}", invocationDescriptor.ToString()); - } - - InvocationResultDescriptor result; - Func> callback; - if (_callbacks.TryGetValue(invocationDescriptor.Method, out callback)) - { - result = await callback(connection, invocationDescriptor); - } - else - { - // If there's no method then return a failed response for this request - result = new InvocationResultDescriptor - { - Id = invocationDescriptor.Id, - Error = $"Unknown method '{invocationDescriptor.Method}'" - }; - } - - await invocationAdapter.WriteInvocationResultAsync(result, stream); - } - } - - protected virtual void BeforeInvoke(Connection connection, T endpoint) - { - } - - protected virtual void AfterInvoke(Connection connection, T endpoint) - { - - } - - protected void RegisterRPCEndPoint() - { - var type = typeof(T); - - foreach (var methodInfo in type.GetTypeInfo().DeclaredMethods.Where(m => m.IsPublic)) - { - var methodName = type.FullName + "." + methodInfo.Name; - - if (_callbacks.ContainsKey(methodName)) - { - throw new NotSupportedException($"Duplicate definitions of '{methodInfo.Name}'. Overloading is not supported."); - } - - var parameters = methodInfo.GetParameters(); - _paramTypes[methodName] = parameters.Select(p => p.ParameterType).ToArray(); - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("RPC method '{methodName}' is bound", methodName); - } - - _callbacks[methodName] = async (connection, invocationDescriptor) => - { - var invocationResult = new InvocationResultDescriptor() - { - Id = invocationDescriptor.Id - }; - - using (var scope = _serviceScopeFactory.CreateScope()) - { - var value = scope.ServiceProvider.GetService(); - - bool created = false; - - if (value == null) - { - value = Activator.CreateInstance(); - created = true; - } - - BeforeInvoke(connection, value); - - try - { - var arguments = invocationDescriptor.Arguments ?? Array.Empty(); - - var args = arguments - .Zip(parameters, (a, p) => Convert.ChangeType(a, p.ParameterType)) - .ToArray(); - - var result = methodInfo.Invoke(value, args); - var resultTask = result as Task; - if (resultTask != null) - { - await resultTask; - if (methodInfo.ReturnType.GetTypeInfo().IsGenericType) - { - var property = resultTask.GetType().GetProperty("Result"); - invocationResult.Result = property?.GetValue(resultTask); - } - } - else - { - invocationResult.Result = result; - } - } - catch (TargetInvocationException ex) - { - _logger.LogError(0, ex, "Failed to invoke RPC method"); - invocationResult.Error = ex.InnerException.Message; - } - catch (Exception ex) - { - _logger.LogError(0, ex, "Failed to invoke RPC method"); - invocationResult.Error = ex.Message; - } - finally - { - AfterInvoke(connection, value); - } - - if (created) - { - // Dispose the object if it's disposable and we created it - (value as IDisposable)?.Dispose(); - } - } - - return invocationResult; - }; - }; - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR/SignalRAppBuilderExtensions.cs b/src/Microsoft.AspNetCore.SignalR/SignalRAppBuilderExtensions.cs index fc7a407e50..77b0803d57 100644 --- a/src/Microsoft.AspNetCore.SignalR/SignalRAppBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR/SignalRAppBuilderExtensions.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Builder public void MapHub(string path) where THub : Hub { - _routes.MapSocketEndpoint>(path); + _routes.MapEndpoint>(path); } } } diff --git a/src/Microsoft.AspNetCore.Sockets/HttpDispatcherAppBuilderExtensions.cs b/src/Microsoft.AspNetCore.Sockets/HttpDispatcherAppBuilderExtensions.cs index b6f24205e8..c92ca39c36 100644 --- a/src/Microsoft.AspNetCore.Sockets/HttpDispatcherAppBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Sockets/HttpDispatcherAppBuilderExtensions.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Builder _dispatcher = dispatcher; } - public void MapSocketEndpoint(string path) where TEndPoint : EndPoint + public void MapEndpoint(string path) where TEndPoint : EndPoint { _routes.AddPrefixRoute(path, new RouteHandler(c => _dispatcher.ExecuteAsync(path, c))); }