Introducing HubActivator (#83)

Fixes #60
This commit is contained in:
Pawel Kadluczka 2016-12-30 11:42:02 -08:00 committed by GitHub
parent 8022afd3a2
commit dbf27c30c3
5 changed files with 153 additions and 33 deletions

View File

@ -0,0 +1,51 @@
// 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.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR
{
public class DefaultHubActivator<THub, TClient> : IHubActivator<THub, TClient>
where THub: Hub<TClient>
{
private readonly IServiceProvider _serviceProvider;
private bool? _created;
public DefaultHubActivator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public THub Create()
{
Debug.Assert(!_created.HasValue, "hub activators must not be reused.");
_created = false;
var hub = _serviceProvider.GetService<THub>();
if (hub == null)
{
hub = ActivatorUtilities.CreateInstance<THub>(_serviceProvider);
_created = true;
}
return hub;
}
public void Release(THub hub)
{
if (hub == null)
{
throw new ArgumentNullException(nameof(hub));
}
Debug.Assert(_created.HasValue, "hubs must be released with the hub activator they were created");
if (_created.Value)
{
hub.Dispose();
}
}
}
}

View File

@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.SignalR
IHubContext<THub> hubContext,
InvocationAdapterRegistry registry,
ILogger<HubEndPoint<THub>> logger,
IServiceScopeFactory serviceScopeFactory) : base(lifetimeManager, hubContext, registry, logger, serviceScopeFactory)
IServiceScopeFactory serviceScopeFactory)
: base(lifetimeManager, hubContext, registry, logger, serviceScopeFactory)
{
}
}
@ -63,14 +63,16 @@ namespace Microsoft.AspNetCore.SignalR
using (var scope = _serviceScopeFactory.CreateScope())
{
bool created;
var hub = CreateHub(scope.ServiceProvider, connection, out created);
await hub.OnConnectedAsync();
if (created)
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub, TClient>>();
var hub = hubActivator.Create();
try
{
hub.Dispose();
InitializeHub(hub, connection);
await hub.OnConnectedAsync();
}
finally
{
hubActivator.Release(hub);
}
}
@ -87,14 +89,16 @@ namespace Microsoft.AspNetCore.SignalR
{
using (var scope = _serviceScopeFactory.CreateScope())
{
bool created;
var hub = CreateHub(scope.ServiceProvider, connection, out created);
await hub.OnDisconnectedAsync(exception);
if (created)
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub, TClient>>();
var hub = hubActivator.Create();
try
{
hub.Dispose();
InitializeHub(hub, connection);
await hub.OnDisconnectedAsync(exception);
}
finally
{
hubActivator.Release(hub);
}
}
@ -145,22 +149,11 @@ namespace Microsoft.AspNetCore.SignalR
}
}
private THub CreateHub(IServiceProvider provider, Connection connection, out bool created)
private void InitializeHub(THub hub, Connection connection)
{
var hub = provider.GetService<THub>();
created = false;
if (hub == null)
{
hub = ActivatorUtilities.CreateInstance<THub>(provider);
created = true;
}
hub.Clients = _hubContext.Clients;
hub.Context = new HubCallerContext(connection);
hub.Groups = new GroupManager<THub>(connection, _lifetimeManager);
return hub;
}
private void DiscoverHubMethods()
@ -193,11 +186,13 @@ namespace Microsoft.AspNetCore.SignalR
using (var scope = _serviceScopeFactory.CreateScope())
{
bool created;
var hub = CreateHub(scope.ServiceProvider, connection, out created);
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub, TClient>>();
var hub = hubActivator.Create();
try
{
InitializeHub(hub, connection);
var result = methodInfo.Invoke(hub, invocationDescriptor.Arguments);
var resultTask = result as Task;
if (resultTask != null)
@ -224,10 +219,9 @@ namespace Microsoft.AspNetCore.SignalR
_logger.LogError(0, ex, "Failed to invoke hub method");
invocationResult.Error = ex.Message;
}
if (created)
finally
{
hub.Dispose();
hubActivator.Release(hub);
}
}

View File

@ -0,0 +1,13 @@
// 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;
namespace Microsoft.AspNetCore.SignalR
{
public interface IHubActivator<THub, TClient> where THub : Hub<TClient>
{
THub Create();
void Release(THub hub);
}
}

View File

@ -19,6 +19,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IConfigureOptions<SignalROptions>, SignalROptionsSetup>();
services.AddSingleton<JsonNetInvocationAdapter>();
services.AddSingleton<InvocationAdapterRegistry>();
services.AddScoped(typeof(IHubActivator<,>), typeof(DefaultHubActivator<,>));
services.AddRouting();
return new SignalRBuilder(services);

View File

@ -0,0 +1,61 @@
// 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 Moq;
using Moq.Protected;
using Xunit;
namespace Microsoft.AspNetCore.SignalR.Tests
{
public class DefaultHubActivatorTests
{
[Fact]
public void HubCreatedIfNotResolvedFromServiceProvider()
{
Assert.NotNull(
new DefaultHubActivator<Hub<object>, object>(Mock.Of<IServiceProvider>()).Create());
}
[Fact]
public void HubCanBeResolvedFromServiceProvider()
{
var hub = Mock.Of<Hub<object>>();
var mockServiceProvider = new Mock<IServiceProvider>();
mockServiceProvider
.Setup(sp => sp.GetService(typeof(Hub<object>)))
.Returns(hub);
Assert.Same(hub,
new DefaultHubActivator<Hub<object>, object>(mockServiceProvider.Object).Create());
}
[Fact]
public void DisposeNotCalledForHubsResolvedFromServiceProvider()
{
var mockServiceProvider = new Mock<IServiceProvider>();
mockServiceProvider
.Setup(sp => sp.GetService(typeof(Hub<object>)))
.Returns(() =>
{
var m = new Mock<Hub<object>>();
m.Protected().Setup("Dispose", ItExpr.IsAny<bool>());
return m.Object;
});
var hubActivator = new DefaultHubActivator<Hub<object>, object>(mockServiceProvider.Object);
var hub = hubActivator.Create();
hubActivator.Release(hub);
Mock.Get(hub).Protected().Verify("Dispose", Times.Never(), ItExpr.IsAny<bool>());
}
[Fact]
public void CannotReleaseNullHub()
{
Assert.Equal("hub",
Assert.Throws<ArgumentNullException>(
() => new DefaultHubActivator<Hub<object>, object>(Mock.Of<IServiceProvider>()).Release(null)).ParamName);
}
}
}