Adopt Bedrock client abstractions in SignalR client (#11484)

This commit is contained in:
Stephen Halter 2019-06-27 15:26:16 -07:00 committed by GitHub
parent 82149f92ed
commit 30fe3a2288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 527 additions and 297 deletions

View File

@ -109,6 +109,10 @@ namespace Microsoft.AspNetCore.Connections
Microsoft.AspNetCore.Connections.ConnectionDelegate Build();
Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware);
}
public partial interface IConnectionFactory
{
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(System.Net.EndPoint endPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
}
public partial interface IConnectionListener : System.IAsyncDisposable
{
System.Net.EndPoint EndPoint { get; }
@ -125,6 +129,11 @@ namespace Microsoft.AspNetCore.Connections
Binary = 1,
Text = 2,
}
public partial class UriEndPoint : System.Net.EndPoint
{
public UriEndPoint(System.Uri uri) { }
public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
}
namespace Microsoft.AspNetCore.Connections.Features
{

View File

@ -109,6 +109,10 @@ namespace Microsoft.AspNetCore.Connections
Microsoft.AspNetCore.Connections.ConnectionDelegate Build();
Microsoft.AspNetCore.Connections.IConnectionBuilder Use(System.Func<Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Connections.ConnectionDelegate> middleware);
}
public partial interface IConnectionFactory
{
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(System.Net.EndPoint endPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
}
public partial interface IConnectionListener : System.IAsyncDisposable
{
System.Net.EndPoint EndPoint { get; }
@ -125,6 +129,11 @@ namespace Microsoft.AspNetCore.Connections
Binary = 1,
Text = 2,
}
public partial class UriEndPoint : System.Net.EndPoint
{
public UriEndPoint(System.Uri uri) { }
public System.Uri Uri { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
}
namespace Microsoft.AspNetCore.Connections.Features
{

View File

@ -0,0 +1,25 @@
// 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.Net;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Connections
{
/// <summary>
/// A factory abstraction for creating connections to an endpoint.
/// </summary>
public interface IConnectionFactory
{
/// <summary>
/// Creates a new connection to an endpoint.
/// </summary>
/// <param name="endPoint">The <see cref="EndPoint"/> to connect to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}" /> that represents the asynchronous connect, yielding the <see cref="ConnectionContext" /> for the new connection when completed.
/// </returns>
ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default);
}
}

View File

@ -1,10 +1,7 @@
// 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.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

View File

@ -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;
using System.Net;
namespace Microsoft.AspNetCore.Connections
{
/// <summary>
/// An <see cref="EndPoint"/> defined by a <see cref="System.Uri"/>.
/// </summary>
public class UriEndPoint : EndPoint
{
/// <summary>
/// Initializes a new instance of the <see cref="UriEndPoint"/> class.
/// </summary>
/// <param name="uri">The <see cref="System.Uri"/> defining the <see cref="EndPoint"/>.</param>
public UriEndPoint(Uri uri)
{
Uri = uri ?? throw new ArgumentNullException(nameof(uri));
}
/// <summary>
/// The <see cref="System.Uri"/> defining the <see cref="EndPoint"/>.
/// </summary>
public Uri Uri { get; }
}
}

View File

@ -8,9 +8,8 @@ namespace Microsoft.AspNetCore.SignalR.Client
public static readonly System.TimeSpan DefaultHandshakeTimeout;
public static readonly System.TimeSpan DefaultKeepAliveInterval;
public static readonly System.TimeSpan DefaultServerTimeout;
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.Client.IRetryPolicy reconnectPolicy) { }
public HubConnection(Microsoft.AspNetCore.Connections.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.Net.EndPoint endPoint, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.Connections.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.Net.EndPoint endPoint, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.Client.IRetryPolicy reconnectPolicy) { }
public string ConnectionId { get { throw null; } }
public System.TimeSpan HandshakeTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
@ -145,11 +144,6 @@ namespace Microsoft.AspNetCore.SignalR.Client
Connecting = 2,
Reconnecting = 3,
}
public partial interface IConnectionFactory
{
System.Threading.Tasks.Task<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
System.Threading.Tasks.Task DisposeAsync(Microsoft.AspNetCore.Connections.ConnectionContext connection);
}
public partial interface IHubConnectionBuilder : Microsoft.AspNetCore.SignalR.ISignalRBuilder
{
Microsoft.AspNetCore.SignalR.Client.HubConnection Build();

View File

@ -8,9 +8,8 @@ namespace Microsoft.AspNetCore.SignalR.Client
public static readonly System.TimeSpan DefaultHandshakeTimeout;
public static readonly System.TimeSpan DefaultKeepAliveInterval;
public static readonly System.TimeSpan DefaultServerTimeout;
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.SignalR.Client.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.Client.IRetryPolicy reconnectPolicy) { }
public HubConnection(Microsoft.AspNetCore.Connections.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.Net.EndPoint endPoint, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public HubConnection(Microsoft.AspNetCore.Connections.IConnectionFactory connectionFactory, Microsoft.AspNetCore.SignalR.Protocol.IHubProtocol protocol, System.Net.EndPoint endPoint, System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.SignalR.Client.IRetryPolicy reconnectPolicy) { }
public string ConnectionId { get { throw null; } }
public System.TimeSpan HandshakeTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.TimeSpan KeepAliveInterval { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
@ -145,11 +144,6 @@ namespace Microsoft.AspNetCore.SignalR.Client
Connecting = 2,
Reconnecting = 3,
}
public partial interface IConnectionFactory
{
System.Threading.Tasks.Task<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
System.Threading.Tasks.Task DisposeAsync(Microsoft.AspNetCore.Connections.ConnectionContext connection);
}
public partial interface IHubConnectionBuilder : Microsoft.AspNetCore.SignalR.ISignalRBuilder
{
Microsoft.AspNetCore.SignalR.Client.HubConnection Build();

View File

@ -8,6 +8,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
@ -21,6 +22,7 @@ using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.SignalR.Client
{
@ -59,6 +61,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
private readonly IServiceProvider _serviceProvider;
private readonly IConnectionFactory _connectionFactory;
private readonly IRetryPolicy _reconnectPolicy;
private readonly EndPoint _endPoint;
private readonly ConcurrentDictionary<string, InvocationHandlerList> _handlers = new ConcurrentDictionary<string, InvocationHandlerList>(StringComparer.Ordinal);
// Holds all mutable state other than user-defined handlers and settable properties.
@ -172,6 +175,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
/// </summary>
/// <param name="connectionFactory">The <see cref="IConnectionFactory" /> used to create a connection each time <see cref="StartAsync" /> is called.</param>
/// <param name="protocol">The <see cref="IHubProtocol" /> used by the connection.</param>
/// <param name="endPoint">The <see cref="EndPoint"/> to connect to.</param>
/// <param name="serviceProvider">An <see cref="IServiceProvider"/> containing the services provided to this <see cref="HubConnection"/> instance.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="reconnectPolicy">
@ -181,8 +185,8 @@ namespace Microsoft.AspNetCore.SignalR.Client
/// <remarks>
/// The <see cref="IServiceProvider"/> used to initialize the connection will be disposed when the connection is disposed.
/// </remarks>
public HubConnection(IConnectionFactory connectionFactory, IHubProtocol protocol, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IRetryPolicy reconnectPolicy)
: this(connectionFactory, protocol, serviceProvider, loggerFactory)
public HubConnection(IConnectionFactory connectionFactory, IHubProtocol protocol, EndPoint endPoint, IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IRetryPolicy reconnectPolicy)
: this(connectionFactory, protocol, endPoint, serviceProvider, loggerFactory)
{
_reconnectPolicy = reconnectPolicy;
}
@ -192,27 +196,22 @@ namespace Microsoft.AspNetCore.SignalR.Client
/// </summary>
/// <param name="connectionFactory">The <see cref="IConnectionFactory" /> used to create a connection each time <see cref="StartAsync" /> is called.</param>
/// <param name="protocol">The <see cref="IHubProtocol" /> used by the connection.</param>
/// <param name="endPoint">The <see cref="EndPoint"/> to connect to.</param>
/// <param name="serviceProvider">An <see cref="IServiceProvider"/> containing the services provided to this <see cref="HubConnection"/> instance.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <remarks>
/// The <see cref="IServiceProvider"/> used to initialize the connection will be disposed when the connection is disposed.
/// </remarks>
public HubConnection(IConnectionFactory connectionFactory, IHubProtocol protocol, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: this(connectionFactory, protocol, loggerFactory)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
/// <summary>
/// Initializes a new instance of the <see cref="HubConnection"/> class.
/// </summary>
/// <param name="connectionFactory">The <see cref="IConnectionFactory" /> used to create a connection each time <see cref="StartAsync" /> is called.</param>
/// <param name="protocol">The <see cref="IHubProtocol" /> used by the connection.</param>
/// <param name="loggerFactory">The logger factory.</param>
public HubConnection(IConnectionFactory connectionFactory, IHubProtocol protocol, ILoggerFactory loggerFactory)
public HubConnection(IConnectionFactory connectionFactory,
IHubProtocol protocol,
EndPoint endPoint,
IServiceProvider serviceProvider,
ILoggerFactory loggerFactory)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_protocol = protocol ?? throw new ArgumentNullException(nameof(protocol));
_endPoint = endPoint ?? throw new ArgumentException(nameof(endPoint));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_logger = _loggerFactory.CreateLogger<HubConnection>();
@ -424,7 +423,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
Log.Starting(_logger);
// Start the connection
var connection = await _connectionFactory.ConnectAsync(_protocol.TransferFormat, cancellationToken);
var connection = await _connectionFactory.ConnectAsync(_endPoint, cancellationToken);
var startingConnectionState = new ConnectionState(connection, this);
// From here on, if an error occurs we need to shut down the connection because
@ -450,9 +449,9 @@ namespace Microsoft.AspNetCore.SignalR.Client
Log.Started(_logger);
}
private Task CloseAsync(ConnectionContext connection)
private ValueTask CloseAsync(ConnectionContext connection)
{
return _connectionFactory.DisposeAsync(connection);
return connection.DisposeAsync();
}
// This method does both Dispose and Start, the 'disposing' flag indicates which.

View File

@ -3,7 +3,11 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.SignalR.Client
{
@ -42,11 +46,11 @@ namespace Microsoft.AspNetCore.SignalR.Client
// The service provider is disposed by the HubConnection
var serviceProvider = Services.BuildServiceProvider();
var connectionFactory = serviceProvider.GetService<IConnectionFactory>();
if (connectionFactory == null)
{
var connectionFactory = serviceProvider.GetService<IConnectionFactory>() ??
throw new InvalidOperationException($"Cannot create {nameof(HubConnection)} instance. An {nameof(IConnectionFactory)} was not configured.");
}
var endPoint = serviceProvider.GetService<EndPoint>() ??
throw new InvalidOperationException($"Cannot create {nameof(HubConnection)} instance. An {nameof(EndPoint)} was not configured.");
return serviceProvider.GetService<HubConnection>();
}

View File

@ -1,35 +0,0 @@
// 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;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.SignalR.Client
{
/// <summary>
/// A factory abstraction for creating connections to a SignalR server.
/// </summary>
public interface IConnectionFactory
{
/// <summary>
/// Creates a new connection to a SignalR server using the specified <see cref="TransferFormat"/>.
/// </summary>
/// <param name="transferFormat">The transfer format the connection should use.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> that represents the asynchronous connect.
/// The <see cref="Task{TResult}.Result"/> property returns a <see cref="ConnectionContext"/> for the new connection.
/// </returns>
Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default);
// Current plan for IAsyncDisposable is that DisposeAsync will NOT take a CancellationToken
// https://github.com/dotnet/csharplang/blob/195efa07806284d7b57550e7447dc8bd39c156bf/proposals/async-streams.md#iasyncdisposable
/// <summary>
/// Disposes the specified <see cref="ConnectionContext"/>.
/// </summary>
/// <param name="connection">The connection to dispose.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous dispose.</returns>
Task DisposeAsync(ConnectionContext connection);
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Client for ASP.NET Core SignalR</Description>

View File

@ -3,13 +3,6 @@
namespace Microsoft.AspNetCore.SignalR.Client
{
public partial class HttpConnectionFactory : Microsoft.AspNetCore.SignalR.Client.IConnectionFactory
{
public HttpConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task DisposeAsync(Microsoft.AspNetCore.Connections.ConnectionContext connection) { throw null; }
}
public static partial class HubConnectionBuilderHttpExtensions
{
public static Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder WithUrl(this Microsoft.AspNetCore.SignalR.Client.IHubConnectionBuilder hubConnectionBuilder, string url) { throw null; }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
/// <summary>
/// A factory for creating <see cref="HttpConnection"/> instances.
/// </summary>
public class HttpConnectionFactory : IConnectionFactory
internal class HttpConnectionFactory : IConnectionFactory
{
private readonly HttpConnectionOptions _httpConnectionOptions;
private readonly ILoggerFactory _loggerFactory;
@ -31,22 +32,44 @@ namespace Microsoft.AspNetCore.SignalR.Client
throw new ArgumentNullException(nameof(options));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_httpConnectionOptions = options.Value;
_loggerFactory = loggerFactory;
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <inheritdoc />
public async Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default)
/// <summary>
/// Creates a new connection to an <see cref="UriEndPoint"/>.
/// </summary>
/// <param name="endPoint">The <see cref="UriEndPoint"/> to connect to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}" /> that represents the asynchronous connect, yielding the <see cref="ConnectionContext" /> for the new connection when completed.
/// </returns>
public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
{
var connection = new HttpConnection(_httpConnectionOptions, _loggerFactory);
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint));
}
if (!(endPoint is UriEndPoint uriEndPoint))
{
throw new NotSupportedException($"The provided {nameof(EndPoint)} must be of type {nameof(UriEndPoint)}.");
}
if (_httpConnectionOptions.Url != null && _httpConnectionOptions.Url != uriEndPoint.Uri)
{
throw new InvalidOperationException($"If {nameof(HttpConnectionOptions)}.{nameof(HttpConnectionOptions.Url)} was set, it must match the {nameof(UriEndPoint)}.{nameof(UriEndPoint.Uri)} passed to {nameof(ConnectAsync)}.");
}
// Shallow copy before setting the Url property so we don't mutate the user-defined options object.
var shallowCopiedOptions = ShallowCopyHttpConnectionOptions(_httpConnectionOptions);
shallowCopiedOptions.Url = uriEndPoint.Uri;
var connection = new HttpConnection(shallowCopiedOptions, _loggerFactory);
try
{
await connection.StartAsync(transferFormat, cancellationToken);
await connection.StartAsync(cancellationToken);
return connection;
}
catch
@ -57,10 +80,26 @@ namespace Microsoft.AspNetCore.SignalR.Client
}
}
/// <inheritdoc />
public Task DisposeAsync(ConnectionContext connection)
// Internal for testing
internal static HttpConnectionOptions ShallowCopyHttpConnectionOptions(HttpConnectionOptions options)
{
return connection.DisposeAsync().AsTask();
return new HttpConnectionOptions
{
HttpMessageHandlerFactory = options.HttpMessageHandlerFactory,
Headers = options.Headers,
ClientCertificates = options.ClientCertificates,
Cookies = options.Cookies,
Url = options.Url,
Transports = options.Transports,
SkipNegotiation = options.SkipNegotiation,
AccessTokenProvider = options.AccessTokenProvider,
CloseTimeout = options.CloseTimeout,
Credentials = options.Credentials,
Proxy = options.Proxy,
UseDefaultCredentials = options.UseDefaultCredentials,
DefaultTransferFormat = options.DefaultTransferFormat,
WebSocketConfiguration = options.WebSocketConfiguration,
};
}
}
}

View File

@ -2,9 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.SignalR.Client
{
@ -138,8 +142,44 @@ namespace Microsoft.AspNetCore.SignalR.Client
hubConnectionBuilder.Services.Configure(configureHttpConnection);
}
// Add HttpConnectionOptionsDerivedHttpEndPoint so HubConnection can read the Url from HttpConnectionOptions
// without the Signal.Client.Core project taking a new dependency on Http.Connections.Client.
hubConnectionBuilder.Services.AddSingleton<EndPoint, HttpConnectionOptionsDerivedHttpEndPoint>();
// Configure the HttpConnection so that it uses the correct transfer format for the configured IHubProtocol.
hubConnectionBuilder.Services.AddSingleton<IConfigureOptions<HttpConnectionOptions>, HubProtocolDerivedHttpOptionsConfigurer>();
// If and when HttpConnectionFactory is made public, it can be moved out of this assembly and into Http.Connections.Client.
hubConnectionBuilder.Services.AddSingleton<IConnectionFactory, HttpConnectionFactory>();
return hubConnectionBuilder;
}
private class HttpConnectionOptionsDerivedHttpEndPoint : UriEndPoint
{
public HttpConnectionOptionsDerivedHttpEndPoint(IOptions<HttpConnectionOptions> httpConnectionOptions)
: base(httpConnectionOptions.Value.Url)
{
}
}
private class HubProtocolDerivedHttpOptionsConfigurer : IConfigureNamedOptions<HttpConnectionOptions>
{
private readonly TransferFormat _defaultTransferFormat;
public HubProtocolDerivedHttpOptionsConfigurer(IHubProtocol hubProtocol)
{
_defaultTransferFormat = hubProtocol.TransferFormat;
}
public void Configure(string name, HttpConnectionOptions options)
{
Configure(options);
}
public void Configure(HttpConnectionOptions options)
{
options.DefaultTransferFormat = _defaultTransferFormat;
}
}
}
}

View File

@ -11,4 +11,8 @@
<Reference Include="Microsoft.AspNetCore.Http.Connections.Client" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.SignalR.Client.Tests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.SignalR.Client.FunctionalTests" />
</ItemGroup>
</Project>

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Channels;
@ -13,7 +14,6 @@ using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -42,10 +42,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
var hubConnectionBuilder = new HubConnectionBuilder();
if (protocol != null)
{
hubConnectionBuilder.Services.AddSingleton(protocol);
}
hubConnectionBuilder.WithUrl(url + path);
protocol ??= new JsonHubProtocol();
hubConnectionBuilder.Services.AddSingleton(protocol);
if (loggerFactory != null)
{
@ -57,20 +57,25 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
hubConnectionBuilder.WithAutomaticReconnect();
}
transportType ??= HttpTransportType.LongPolling | HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents;
var delegateConnectionFactory = new DelegateConnectionFactory(
GetHttpConnectionFactory(url, loggerFactory, path, transportType ?? HttpTransportType.LongPolling | HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents),
connection => ((HttpConnection)connection).DisposeAsync().AsTask());
GetHttpConnectionFactory(url, loggerFactory, path, transportType.Value, protocol.TransferFormat));
hubConnectionBuilder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
return hubConnectionBuilder.Build();
}
private Func<TransferFormat, Task<ConnectionContext>> GetHttpConnectionFactory(string url, ILoggerFactory loggerFactory, string path, HttpTransportType transportType)
private Func<EndPoint, ValueTask<ConnectionContext>> GetHttpConnectionFactory(string url, ILoggerFactory loggerFactory, string path, HttpTransportType transportType, TransferFormat transferFormat)
{
return async format =>
return async endPoint =>
{
var connection = new HttpConnection(new Uri(url + path), transportType, loggerFactory);
await connection.StartAsync(format);
var httpEndpoint = (UriEndPoint)endPoint;
var options = new HttpConnectionOptions { Url = httpEndpoint.Uri, Transports = transportType, DefaultTransferFormat = transferFormat };
var connection = new HttpConnection(options, loggerFactory);
await connection.StartAsync();
return connection;
};
}

View File

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -101,16 +102,19 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
using (StartServer<VersionStartup>(out var server))
{
var httpConnectionFactory = new HttpConnectionFactory(Options.Create(new HttpConnectionOptions
{
Url = new Uri(server.Url + "/version"),
Transports = transportType
}), LoggerFactory);
var httpConnectionFactory = new HttpConnectionFactory(
Options.Create(new HttpConnectionOptions
{
Transports = transportType,
DefaultTransferFormat = TransferFormat.Text
}),
LoggerFactory);
var tcs = new TaskCompletionSource<object>();
var proxyConnectionFactory = new ProxyConnectionFactory(httpConnectionFactory);
var connectionBuilder = new HubConnectionBuilder()
.WithUrl(new Uri(server.Url + "/version"))
.WithLoggerFactory(LoggerFactory);
connectionBuilder.Services.AddSingleton<IHubProtocol>(new VersionedJsonHubProtocol(1000));
connectionBuilder.Services.AddSingleton<IConnectionFactory>(proxyConnectionFactory);
@ -192,23 +196,18 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
private class ProxyConnectionFactory : IConnectionFactory
{
private readonly IConnectionFactory _innerFactory;
public Task<ConnectionContext> ConnectTask { get; private set; }
public ValueTask<ConnectionContext> ConnectTask { get; private set; }
public ProxyConnectionFactory(IConnectionFactory innerFactory)
{
_innerFactory = innerFactory;
}
public Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default)
public ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
{
ConnectTask = _innerFactory.ConnectAsync(transferFormat, cancellationToken);
ConnectTask = _innerFactory.ConnectAsync(endPoint, cancellationToken);
return ConnectTask;
}
public Task DisposeAsync(ConnectionContext connection)
{
return _innerFactory.DisposeAsync(connection);
}
}
public static IEnumerable<object[]> TransportTypes()

View File

@ -1,10 +1,20 @@
// 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.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.SignalR.Client.Tests
@ -17,16 +27,94 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
var testHandler = new TestHttpMessageHandler(autoNegotiate: false, handleFirstPoll: false);
testHandler.OnRequest((req, next, ct) => Task.FromException<HttpResponseMessage>(new Exception("BOOM")));
var factory = new HttpConnectionFactory(Options.Create(new HttpConnectionOptions() {
Url = new Uri("http://example.com"),
HttpMessageHandlerFactory = _ => testHandler
}), NullLoggerFactory.Instance);
var factory = new HttpConnectionFactory(
Options.Create(new HttpConnectionOptions
{
DefaultTransferFormat = TransferFormat.Text,
HttpMessageHandlerFactory = _ => testHandler,
}),
NullLoggerFactory.Instance);
// We don't care about the specific exception
await Assert.ThrowsAnyAsync<Exception>(() => factory.ConnectAsync(TransferFormat.Text));
await Assert.ThrowsAnyAsync<Exception>(async () => await factory.ConnectAsync(new UriEndPoint(new Uri("http://example.com"))));
// We care that the handler (and by extension the client) was disposed
Assert.True(testHandler.Disposed);
}
[Fact]
public async Task DoesNotSupportNonUriEndPoints()
{
var factory = new HttpConnectionFactory(
Options.Create(new HttpConnectionOptions { DefaultTransferFormat = TransferFormat.Text }),
NullLoggerFactory.Instance);
var ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await factory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 0)));
Assert.Equal("The provided EndPoint must be of type UriEndPoint.", ex.Message);
}
[Fact]
public async Task OptionsUrlMustMatchEndPointIfSet()
{
var url1 = new Uri("http://example.com/1");
var url2 = new Uri("http://example.com/2");
var factory = new HttpConnectionFactory(
Options.Create(new HttpConnectionOptions
{
Url = url1,
DefaultTransferFormat = TransferFormat.Text
}),
NullLoggerFactory.Instance);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await factory.ConnectAsync(new UriEndPoint(url2)));
Assert.Equal("If HttpConnectionOptions.Url was set, it must match the UriEndPoint.Uri passed to ConnectAsync.", ex.Message);
}
[Fact]
public void ShallowCopyHttpConnectionOptionsCopiesAllPublicProperties()
{
Func<HttpMessageHandler, HttpMessageHandler> handlerFactory = handler => handler;
Func<Task<string>> tokenProvider = () => Task.FromResult("");
Action<ClientWebSocketOptions> webSocketConfig = options => { };
var testValues = new Dictionary<string, object>
{
{ $"{nameof(HttpConnectionOptions.HttpMessageHandlerFactory)}", handlerFactory },
{ $"{nameof(HttpConnectionOptions.Headers)}", new Dictionary<string, string>() },
{ $"{nameof(HttpConnectionOptions.ClientCertificates)}", new X509CertificateCollection() },
{ $"{nameof(HttpConnectionOptions.Cookies)}", new CookieContainer() },
{ $"{nameof(HttpConnectionOptions.Url)}", new Uri("https://example.com") },
{ $"{nameof(HttpConnectionOptions.Transports)}", HttpTransportType.ServerSentEvents },
{ $"{nameof(HttpConnectionOptions.SkipNegotiation)}", true },
{ $"{nameof(HttpConnectionOptions.AccessTokenProvider)}", tokenProvider },
{ $"{nameof(HttpConnectionOptions.CloseTimeout)}", TimeSpan.FromDays(1) },
{ $"{nameof(HttpConnectionOptions.Credentials)}", Mock.Of<ICredentials>() },
{ $"{nameof(HttpConnectionOptions.Proxy)}", Mock.Of<IWebProxy>() },
{ $"{nameof(HttpConnectionOptions.UseDefaultCredentials)}", true },
{ $"{nameof(HttpConnectionOptions.DefaultTransferFormat)}", TransferFormat.Text },
{ $"{nameof(HttpConnectionOptions.WebSocketConfiguration)}", webSocketConfig },
};
var options = new HttpConnectionOptions();
var properties = typeof(HttpConnectionOptions)
.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
property.SetValue(options, testValues[property.Name]);
}
var shallowCopiedOptions = HttpConnectionFactory.ShallowCopyHttpConnectionOptions(options);
foreach (var property in properties)
{
Assert.Equal(testValues[property.Name], property.GetValue(shallowCopiedOptions));
testValues.Remove(property.Name);
}
Assert.Empty(testValues);
}
}
}

View File

@ -30,8 +30,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
await WithConnectionAsync(CreateConnection(loggerFactory: LoggerFactory), async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
}
@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(loggerFactory: LoggerFactory, transport: new TestTransport(onTransportStart: SyncPoint.Create(out var syncPoint))),
async (connection) =>
{
var firstStart = connection.StartAsync(TransferFormat.Text);
var firstStart = connection.StartAsync();
await syncPoint.WaitForSyncPoint().OrTimeout();
var secondStart = connection.StartAsync(TransferFormat.Text);
var secondStart = connection.StartAsync();
syncPoint.Continue();
await firstStart.OrTimeout();
@ -65,11 +65,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(loggerFactory: LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
var exception =
await Assert.ThrowsAsync<ObjectDisposedException>(
async () => await connection.StartAsync(TransferFormat.Text)).OrTimeout();
async () => await connection.StartAsync()).OrTimeout();
Assert.Equal(nameof(HttpConnection), exception.ObjectName);
});
@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
Assert.Equal(0, startCounter);
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
Assert.Equal(passThreshold, startCounter);
});
}
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
transport: new TestTransport(onTransportStart: OnTransportStart)),
async (connection) =>
{
var ex = await Assert.ThrowsAsync<AggregateException>(() => connection.StartAsync(TransferFormat.Text)).OrTimeout();
var ex = await Assert.ThrowsAsync<AggregateException>(() => connection.StartAsync()).OrTimeout();
Assert.Equal("Unable to connect to the server with any of the available transports. " +
"(WebSockets failed: Transport failed to start) (ServerSentEvents failed: Transport failed to start) (LongPolling failed: Transport failed to start)",
ex.Message);
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
// Start the connection and wait for the transport to start up.
var startTask = connection.StartAsync(TransferFormat.Text);
var startTask = connection.StartAsync();
await transportStart.WaitForSyncPoint().OrTimeout();
// While the transport is starting, dispose the connection
@ -232,7 +232,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
// Start the connection
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
// Dispose the connection
var stopTask = connection.DisposeAsync();
@ -268,7 +268,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
// Start the transport
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
Assert.NotNull(testTransport.Receiving);
Assert.False(testTransport.Receiving.IsCompleted);
@ -313,7 +313,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(httpHandler, LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(new byte[] { 0x42 }).OrTimeout();
// We should get the exception in the transport input completion.
@ -347,7 +347,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
await Assert.ThrowsAsync<AggregateException>(
() => connection.StartAsync(TransferFormat.Text).OrTimeout());
() => connection.StartAsync().OrTimeout());
});
}
}
@ -372,7 +372,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(httpHandler, loggerFactory: LoggerFactory, transport: sse),
async (connection) =>
{
var startTask = connection.StartAsync(TransferFormat.Text);
var startTask = connection.StartAsync();
Assert.False(connectResponseTcs.Task.IsCompleted);
Assert.False(startTask.IsCompleted);
connectResponseTcs.TrySetResult(null);
@ -430,7 +430,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
private static async Task AssertDisposedAsync(HttpConnection connection)
{
var exception =
await Assert.ThrowsAsync<ObjectDisposedException>(() => connection.StartAsync(TransferFormat.Text));
await Assert.ThrowsAsync<ObjectDisposedException>(() => connection.StartAsync());
Assert.Equal(nameof(HttpConnection), exception.ObjectName);
}
}

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
ITransport transport = null,
ITransportFactory transportFactory = null,
HttpTransportType? transportType = null,
TransferFormat transferFormat = TransferFormat.Text,
Func<Task<string>> accessTokenProvider = null)
{
var httpOptions = new HttpConnectionOptions
@ -35,24 +36,32 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
httpOptions.Url = new Uri(url);
}
return CreateConnection(httpOptions, loggerFactory, transport, transportFactory);
return CreateConnection(httpOptions, loggerFactory, transport, transportFactory, transferFormat);
}
private static HttpConnection CreateConnection(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory = null, ITransport transport = null, ITransportFactory transportFactory = null)
private static HttpConnection CreateConnection(
HttpConnectionOptions httpConnectionOptions,
ILoggerFactory loggerFactory = null,
ITransport transport = null,
ITransportFactory transportFactory = null,
TransferFormat transferFormat = TransferFormat.Text)
{
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
httpConnectionOptions.Url = httpConnectionOptions.Url ?? new Uri("http://fakeuri.org/");
httpConnectionOptions.Url ??= new Uri("http://fakeuri.org/");
httpConnectionOptions.DefaultTransferFormat = transferFormat;
if (transportFactory == null && transport != null)
{
transportFactory = new TestTransportFactory(transport);
}
if (transportFactory != null)
{
return new HttpConnection(httpConnectionOptions, loggerFactory, transportFactory);
}
else if (transport != null)
{
return new HttpConnection(httpConnectionOptions, loggerFactory, new TestTransportFactory(transport));
}
else
{
// Use the public constructor to get the default transport factory.
return new HttpConnection(httpConnectionOptions, loggerFactory);
}
}

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, url: requestedUrl, loggerFactory: noErrorScope.LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
connectionId = connection.ConnectionId;
});
}
@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory),
async (connection) =>
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync(TransferFormat.Text).OrTimeout());
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync().OrTimeout());
Assert.Equal("Negotiate redirection limit exceeded.", exception.Message);
});
}
@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory, accessTokenProvider: AccessTokenProvider),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
@ -327,10 +327,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (var noErrorScope = new VerifyNoErrorsScope())
{
await WithConnectionAsync(
CreateConnection(testHttpHandler, transportFactory: transportFactory.Object, loggerFactory: noErrorScope.LoggerFactory),
CreateConnection(testHttpHandler, transportFactory: transportFactory.Object, loggerFactory: noErrorScope.LoggerFactory, transferFormat: TransferFormat.Binary),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
}
@ -374,10 +374,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
.Returns(new TestTransport(transferFormat: TransferFormat.Text | TransferFormat.Binary));
await WithConnectionAsync(
CreateConnection(testHttpHandler, transportFactory: transportFactory.Object),
CreateConnection(testHttpHandler, transportFactory: transportFactory.Object, transferFormat: TransferFormat.Binary),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, loggerFactory: noErrorScope.LoggerFactory),
async (connection) =>
{
var exception = await Assert.ThrowsAsync<Exception>(() => connection.StartAsync(TransferFormat.Text).OrTimeout());
var exception = await Assert.ThrowsAsync<Exception>(() => connection.StartAsync().OrTimeout());
Assert.Equal("Test error.", exception.Message);
});
}
@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
async (connection) =>
{
var exception = await Assert.ThrowsAsync<TException>(
() => connection.StartAsync(TransferFormat.Text).OrTimeout());
() => connection.StartAsync().OrTimeout());
Assert.Equal(expectedExceptionMessage, exception.Message);
});

View File

@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, transportType: transportType, accessTokenProvider: AccessTokenProvider),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello world 1"));
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello world 2"));
});
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, transportType: transportType, loggerFactory: LoggerFactory),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
var feature = connection.Features.Get<IConnectionInherentKeepAliveFeature>();
Assert.NotNull(feature);
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, transportType: transportType),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello World"));
});
// Fail safe in case the code is modified and some requests don't execute as a result
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler, transportType: transportType),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello World"));
});
// Fail safe in case the code is modified and some requests don't execute as a result
@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
Assert.Contains("This is a test", Encoding.UTF8.GetString(await connection.Transport.Input.ReadAllAsync()));
});
}
@ -237,7 +237,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(testHttpHandler),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(data).OrTimeout();
@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
var exception = await Assert.ThrowsAsync<ObjectDisposedException>(
@ -284,7 +284,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(transport: transport),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
// This will throw OperationCanceledException if it's forcibly terminated
@ -292,6 +292,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
await transport.Receiving.OrTimeout();
});
}
[Fact]
public Task StartAsyncTransferFormatOverridesOptions()
{
var transport = new TestTransport();
return WithConnectionAsync(
CreateConnection(transport: transport, transferFormat: TransferFormat.Binary),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
Assert.Equal(TransferFormat.Text, transport.Format);
});
}
}
}
}

View File

@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(httpOptions),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
});
Assert.NotNull(httpClientHandler);
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
CreateConnection(httpOptions, loggerFactory: mockLoggerFactory.Object),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
});
}
catch

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
@ -22,6 +23,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
Assert.Equal("Cannot create HubConnection instance. An IConnectionFactory was not configured.", ex.Message);
}
[Fact]
public void CannotCreateConnectionWithNoEndPoint()
{
var builder = new HubConnectionBuilder();
builder.Services.AddSingleton<IConnectionFactory>(new HttpConnectionFactory(Options.Create(new HttpConnectionOptions()), NullLoggerFactory.Instance));
var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal("Cannot create HubConnection instance. An EndPoint was not configured.", ex.Message);
}
[Fact]
public void AddJsonProtocolSetsHubProtocolToJsonWithDefaultOptions()
{
@ -51,6 +62,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
var builder = new HubConnectionBuilder();
builder.Services.AddSingleton<IConnectionFactory>(new HttpConnectionFactory(Options.Create(new HttpConnectionOptions()), NullLoggerFactory.Instance));
builder.WithUrl("http://example.com");
Assert.NotNull(builder.Build());

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -78,19 +79,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
// Set up StartAsync to wait on the syncPoint when starting
var createCount = 0;
Task<ConnectionContext> ConnectionFactory(TransferFormat format)
ValueTask<ConnectionContext> ConnectionFactory(EndPoint endPoint)
{
createCount += 1;
return new TestConnection().StartAsync(format);
return new TestConnection().StartAsync();
}
Task DisposeAsync(ConnectionContext connection)
{
return connection.DisposeAsync().AsTask();
}
var builder = new HubConnectionBuilder();
var delegateConnectionFactory = new DelegateConnectionFactory(ConnectionFactory, DisposeAsync);
var builder = new HubConnectionBuilder().WithUrl("http://example.com");
var delegateConnectionFactory = new DelegateConnectionFactory(ConnectionFactory);
builder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
await AsyncUsing(builder.Build(), async connection =>
@ -116,16 +111,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
// Set up StartAsync to wait on the syncPoint when starting
var createCount = 0;
var onDisposeForFirstConnection = SyncPoint.Create(out var syncPoint);
Task<ConnectionContext> ConnectionFactory(TransferFormat format)
ValueTask<ConnectionContext> ConnectionFactory(EndPoint endPoint)
{
createCount += 1;
return new TestConnection(onDispose: createCount == 1 ? onDisposeForFirstConnection : null).StartAsync(format);
return new TestConnection(onDispose: createCount == 1 ? onDisposeForFirstConnection : null).StartAsync();
}
Task DisposeAsync(ConnectionContext connection) => connection.DisposeAsync().AsTask();
var builder = new HubConnectionBuilder();
var delegateConnectionFactory = new DelegateConnectionFactory(ConnectionFactory, DisposeAsync);
var builder = new HubConnectionBuilder().WithUrl("http://example.com");
var delegateConnectionFactory = new DelegateConnectionFactory(ConnectionFactory);
builder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
await AsyncUsing(builder.Build(), async connection =>
@ -596,12 +589,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
[Fact]
public async Task HubConnectionClosesWithErrorIfTerminatedWithPartialMessage()
{
var builder = new HubConnectionBuilder();
var builder = new HubConnectionBuilder().WithUrl("http://example.com");
var innerConnection = new TestConnection();
var delegateConnectionFactory = new DelegateConnectionFactory(
format => innerConnection.StartAsync(format),
connection => connection.DisposeAsync().AsTask());
endPoint => innerConnection.StartAsync());
builder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
var hubConnection = builder.Build();

View File

@ -1,6 +1,7 @@
// 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.Connections;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.DependencyInjection;
@ -12,11 +13,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
private static HubConnection CreateHubConnection(TestConnection connection, IHubProtocol protocol = null, ILoggerFactory loggerFactory = null)
{
var builder = new HubConnectionBuilder();
var builder = new HubConnectionBuilder().WithUrl("http://example.com");
var delegateConnectionFactory = new DelegateConnectionFactory(
connection.StartAsync,
c => c.DisposeAsync().AsTask());
endPoint => connection.StartAsync());
builder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);

View File

@ -3,9 +3,12 @@
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
@ -75,7 +78,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = default(ReconnectingConnectionFactory);
var startCallCount = 0;
var originalConnectionId = "originalConnectionId";
@ -189,7 +192,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var startCallCount = 0;
Task OnTestConnectionStart()
@ -281,7 +284,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory();
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -376,7 +379,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory();
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -431,7 +434,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory(() => new TestConnection(autoHandshake: false));
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -489,7 +492,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory(() => new TestConnection(autoHandshake: false));
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -596,7 +599,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory(() => new TestConnection(autoHandshake: false));
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -715,7 +718,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var connectionStartTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
async Task OnTestConnectionStart()
@ -806,7 +809,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
using (StartVerifiableLog(ExpectedErrors))
{
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory);
var builder = new HubConnectionBuilder().WithLoggerFactory(LoggerFactory).WithUrl("http://example.com");
var testConnectionFactory = new ReconnectingConnectionFactory();
builder.Services.AddSingleton<IConnectionFactory>(testConnectionFactory);
@ -886,7 +889,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
return _testConnectionTcs.Task;
}
public async Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default)
public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
{
var testConnection = _testConnectionFactory();
@ -894,7 +897,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
try
{
return await testConnection.StartAsync(transferFormat);
return new DisposeInterceptingConnectionContextDecorator(await testConnection.StartAsync(), this);
}
catch
{
@ -912,6 +915,31 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
await disposingTestConnection.DisposeAsync();
}
}
private class DisposeInterceptingConnectionContextDecorator : ConnectionContext
{
private readonly ConnectionContext _inner;
private readonly ReconnectingConnectionFactory _reconnectingConnectionFactory;
public DisposeInterceptingConnectionContextDecorator(ConnectionContext inner, ReconnectingConnectionFactory reconnectingConnectionFactory)
{
_inner = inner;
_reconnectingConnectionFactory = reconnectingConnectionFactory;
}
public override string ConnectionId { get => _inner.ConnectionId; set => _inner.ConnectionId = value; }
public override IFeatureCollection Features => _inner.Features;
public override IDictionary<object, object> Items { get => _inner.Items; set => _inner.Items = value; }
public override IDuplexPipe Transport { get => _inner.Transport; set => _inner.Transport = value; }
public override CancellationToken ConnectionClosed { get => _inner.ConnectionClosed; set => _inner.ConnectionClosed = value; }
public override EndPoint LocalEndPoint { get => _inner.LocalEndPoint; set => _inner.LocalEndPoint = value; }
public override EndPoint RemoteEndPoint { get => _inner.RemoteEndPoint; set => _inner.RemoteEndPoint = value; }
public override void Abort(ConnectionAbortedException abortReason) => _inner.Abort(abortReason);
public override void Abort() => _inner.Abort();
public override ValueTask DisposeAsync() => new ValueTask(_reconnectingConnectionFactory.DisposeAsync(_inner));
}
}
}
}

View File

@ -59,11 +59,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
[Fact]
public async Task ClosedEventRaisedWhenTheClientIsStopped()
{
var builder = new HubConnectionBuilder();
var builder = new HubConnectionBuilder().WithUrl("http://example.com");
var delegateConnectionFactory = new DelegateConnectionFactory(
format => new TestConnection().StartAsync(format),
connection => ((TestConnection)connection).DisposeAsync().AsTask());
endPoint => new TestConnection().StartAsync());
builder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
var hubConnection = builder.Build();

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
public override ValueTask DisposeAsync() => DisposeCoreAsync();
public async Task<ConnectionContext> StartAsync(TransferFormat transferFormat = TransferFormat.Binary)
public async ValueTask<ConnectionContext> StartAsync()
{
_started.TrySetResult(null);

View File

@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
public System.TimeSpan CloseTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Net.CookieContainer Cookies { get { throw null; } set { } }
public System.Net.ICredentials Credentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Connections.TransferFormat DefaultTransferFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Collections.Generic.IDictionary<string, string> Headers { get { throw null; } set { } }
public System.Func<System.Net.Http.HttpMessageHandler, System.Net.Http.HttpMessageHandler> HttpMessageHandlerFactory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Net.IWebProxy Proxy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }

View File

@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
public System.TimeSpan CloseTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Net.CookieContainer Cookies { get { throw null; } set { } }
public System.Net.ICredentials Credentials { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Connections.TransferFormat DefaultTransferFormat { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Collections.Generic.IDictionary<string, string> Headers { get { throw null; } set { } }
public System.Func<System.Net.Http.HttpMessageHandler, System.Net.Http.HttpMessageHandler> HttpMessageHandlerFactory { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Net.IWebProxy Proxy { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }

View File

@ -129,6 +129,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
/// <param name="loggerFactory">The logger factory.</param>
public HttpConnection(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory)
{
if (httpConnectionOptions == null)
{
throw new ArgumentNullException(nameof(httpConnectionOptions));
}
if (httpConnectionOptions.Url == null)
{
throw new ArgumentException("Options does not have a URL specified.", nameof(httpConnectionOptions));
@ -168,7 +173,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
/// </remarks>
public Task StartAsync(CancellationToken cancellationToken = default)
{
return StartAsync(TransferFormat.Binary, cancellationToken);
return StartAsync(_httpConnectionOptions.DefaultTransferFormat, cancellationToken);
}
/// <summary>

View File

@ -7,7 +7,9 @@ using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Http.Connections.Client
{
@ -108,6 +110,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
/// </summary>
public bool? UseDefaultCredentials { get; set; }
/// <summary>
/// Gets or sets the default <see cref="TransferFormat" /> to use if <see cref="HttpConnection.StartAsync(CancellationToken)"/>
/// is called instead of <see cref="HttpConnection.StartAsync(TransferFormat, CancellationToken)"/>.
/// </summary>
public TransferFormat DefaultTransferFormat { get; set; } = TransferFormat.Binary;
/// <summary>
/// Gets or sets a delegate that will be invoked with the <see cref="ClientWebSocketOptions"/> object used
/// to configure the WebSocket when using the WebSockets transport.

View File

@ -2,33 +2,26 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Client;
namespace Microsoft.AspNetCore.SignalR.Tests
{
public class DelegateConnectionFactory : IConnectionFactory
{
private readonly Func<TransferFormat, Task<ConnectionContext>> _connectDelegate;
private readonly Func<ConnectionContext, Task> _disposeDelegate;
private readonly Func<EndPoint, ValueTask<ConnectionContext>> _connectDelegate;
// We have no tests that use the CancellationToken. When we do, we can add it to the delegate. This is test code.
public DelegateConnectionFactory(Func<TransferFormat, Task<ConnectionContext>> connectDelegate, Func<ConnectionContext, Task> disposeDelegate)
public DelegateConnectionFactory(Func<EndPoint, ValueTask<ConnectionContext>> connectDelegate)
{
_connectDelegate = connectDelegate;
_disposeDelegate = disposeDelegate;
}
public Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken)
public ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
{
return _connectDelegate(transferFormat);
}
public Task DisposeAsync(ConnectionContext connection)
{
return _disposeDelegate(connection);
return _connectDelegate(endPoint);
}
}
}

View File

@ -77,21 +77,14 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
_invocationMessageBytes = hubProtocol.GetMessageBytes(new InvocationMessage(MethodName, arguments));
var delegateConnectionFactory = new DelegateConnectionFactory(
format =>
{
var connection = new DefaultConnectionContext();
// prevents keep alive time being activated
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new TestConnectionInherentKeepAliveFeature());
connection.Transport = _pipe;
return Task.FromResult<ConnectionContext>(connection);
},
connection =>
{
connection.Transport.Output.Complete();
connection.Transport.Input.Complete();
return Task.CompletedTask;
});
var delegateConnectionFactory = new DelegateConnectionFactory(endPoint =>
{
var connection = new DefaultConnectionContext();
// prevents keep alive time being activated
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new TestConnectionInherentKeepAliveFeature());
connection.Transport = _pipe;
return new ValueTask<ConnectionContext>(connection);
});
hubConnectionBuilder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);
_hubConnection = hubConnectionBuilder.Build();

View File

@ -55,19 +55,13 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
hubConnectionBuilder.AddMessagePackProtocol();
}
var delegateConnectionFactory = new DelegateConnectionFactory(format =>
var delegateConnectionFactory = new DelegateConnectionFactory(endPoint =>
{
var connection = new DefaultConnectionContext();
// prevents keep alive time being activated
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new TestConnectionInherentKeepAliveFeature());
connection.Transport = _pipe;
return Task.FromResult<ConnectionContext>(connection);
},
connection =>
{
connection.Transport.Output.Complete();
connection.Transport.Input.Complete();
return Task.CompletedTask;
return new ValueTask<ConnectionContext>(connection);
});
hubConnectionBuilder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);

View File

@ -40,19 +40,13 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
_pipe = new TestDuplexPipe();
var hubConnectionBuilder = new HubConnectionBuilder();
var delegateConnectionFactory = new DelegateConnectionFactory(format =>
var delegateConnectionFactory = new DelegateConnectionFactory(endPoint =>
{
var connection = new DefaultConnectionContext();
// prevents keep alive time being activated
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new TestConnectionInherentKeepAliveFeature());
connection.Transport = _pipe;
return Task.FromResult<ConnectionContext>(connection);
},
connection =>
{
connection.Transport.Output.Complete();
connection.Transport.Input.Complete();
return Task.CompletedTask;
return new ValueTask<ConnectionContext>(connection);
});
hubConnectionBuilder.Services.AddSingleton<IConnectionFactory>(delegateConnectionFactory);

View File

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
namespace ClientSample
{
@ -34,10 +33,18 @@ namespace ClientSample
baseUrl = string.IsNullOrEmpty(baseUrl) ? "http://localhost:5000/chat" : baseUrl;
Console.WriteLine($"Connecting to {baseUrl}...");
var connection = new HttpConnection(new Uri(baseUrl));
var connectionOptions = new HttpConnectionOptions
{
Url = new Uri(baseUrl),
DefaultTransferFormat = TransferFormat.Text,
};
var connection = new HttpConnection(connectionOptions, loggerFactory: null);
try
{
await connection.StartAsync(TransferFormat.Text);
await connection.StartAsync();
Console.WriteLine($"Connected to {baseUrl}");
var shutdown = new TaskCompletionSource<object>();

View File

@ -53,7 +53,7 @@ namespace ClientSample
return default;
}
public async Task<ConnectionContext> StartAsync()
public async ValueTask<ConnectionContext> StartAsync()
{
await _socket.ConnectAsync(_endPoint);
@ -245,11 +245,5 @@ namespace ClientSample
}
}
}
public Task StartAsync(TransferFormat transferFormat)
{
// Transfer format is irrelevant
return StartAsync();
}
}
}

View File

@ -1,11 +1,12 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using ClientSample;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.AspNetCore.SignalR.Client
{
@ -33,28 +34,17 @@ namespace Microsoft.AspNetCore.SignalR.Client
public static IHubConnectionBuilder WithEndPoint(this IHubConnectionBuilder builder, EndPoint endPoint)
{
builder.Services.AddSingleton<IConnectionFactory>(new TcpConnectionFactory(endPoint));
builder.Services.AddSingleton<IConnectionFactory, TcpConnectionFactory>();
builder.Services.AddSingleton(endPoint);
return builder;
}
private class TcpConnectionFactory : IConnectionFactory
{
private readonly EndPoint _endPoint;
public TcpConnectionFactory(EndPoint endPoint)
public ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
{
_endPoint = endPoint;
}
public Task<ConnectionContext> ConnectAsync(TransferFormat transferFormat, CancellationToken cancellationToken = default)
{
return new TcpConnection(_endPoint).StartAsync();
}
public Task DisposeAsync(ConnectionContext connection)
{
return connection.DisposeAsync().AsTask();
return new TcpConnection(endPoint).StartAsync();
}
}
}

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
// The test should connect to the server using WebSockets transport on Windows 8 and newer.
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
var connection = new HttpConnection(new Uri(url), HttpTransports.All, LoggerFactory);
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
@ -64,8 +64,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
// The test logic lives in the TestTransportFactory and FakeTransport.
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url) }, LoggerFactory, new TestTransportFactory());
await connection.StartAsync(TransferFormat.Text).OrTimeout();
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url), DefaultTransferFormat = TransferFormat.Text }, LoggerFactory, new TestTransportFactory());
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests
using (StartServer<Startup>(out var server))
{
var url = server.Url + "/echo";
var connection = new HttpConnection(new Uri(url), transportType, LoggerFactory);
await connection.StartAsync(TransferFormat.Text).OrTimeout();
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url), Transports = transportType, DefaultTransferFormat = TransferFormat.Text }, LoggerFactory);
await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout();
}
}
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
try
{
var message = new byte[] { 42 };
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
await connection.Transport.Output.WriteAsync(message).OrTimeout();
@ -238,7 +238,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
try
{
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync(TransferFormat.Binary).OrTimeout());
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => connection.StartAsync().OrTimeout());
Assert.Equal("Negotiation can only be skipped when using the WebSocket transport directly.", exception.Message);
}
catch (Exception ex)
@ -265,11 +265,11 @@ namespace Microsoft.AspNetCore.SignalR.Tests
const string message = "Major Key";
var url = server.Url + "/echo";
var connection = new HttpConnection(new Uri(url), transportType, LoggerFactory);
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url), Transports = transportType, DefaultTransferFormat = requestedTransferFormat }, LoggerFactory);
try
{
logger.LogInformation("Starting connection to {url}", url);
await connection.StartAsync(requestedTransferFormat).OrTimeout();
await connection.StartAsync().OrTimeout();
logger.LogInformation("Started connection to {url}", url);
var bytes = Encoding.UTF8.GetBytes(message);
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
try
{
logger.LogInformation("Starting connection to {url}", url);
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
logger.LogInformation("Started connection to {url}", url);
var bytes = Encoding.UTF8.GetBytes(message);
@ -373,7 +373,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var url = server.Url + "/auth";
var connection = new HttpConnection(new Uri(url), HttpTransportType.WebSockets, LoggerFactory);
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => connection.StartAsync(TransferFormat.Binary).OrTimeout());
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => connection.StartAsync().OrTimeout());
Assert.Contains("401", exception.Message);
}
@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var connection = new HttpConnection(options, LoggerFactory);
await Assert.ThrowsAsync<WebSocketException>(() => connection.StartAsync(TransferFormat.Binary).OrTimeout());
await Assert.ThrowsAsync<WebSocketException>(() => connection.StartAsync().OrTimeout());
}
}
@ -430,7 +430,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
try
{
logger.LogInformation("Starting connection to {url}", url);
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
await connection.StartAsync().OrTimeout();
Assert.True(false);
}
catch (Exception ex)
@ -470,17 +470,20 @@ namespace Microsoft.AspNetCore.SignalR.Tests
}
var url = server.Url + "/auth";
var connection = new HttpConnection(new HttpConnectionOptions()
{
AccessTokenProvider = () => Task.FromResult(token),
Url = new Uri(url),
Transports = HttpTransportType.ServerSentEvents
}, LoggerFactory);
var connection = new HttpConnection(
new HttpConnectionOptions()
{
Url = new Uri(url),
AccessTokenProvider = () => Task.FromResult(token),
Transports = HttpTransportType.ServerSentEvents,
DefaultTransferFormat = TransferFormat.Text,
},
LoggerFactory);
try
{
logger.LogInformation("Starting connection to {url}", url);
await connection.StartAsync(TransferFormat.Text).OrTimeout();
await connection.StartAsync().OrTimeout();
logger.LogInformation("Connected to {url}", url);
}
finally