API docs for SignalR (#26445)
* API docs for SignalR * Apply suggestions from code review Co-authored-by: Stephen Halter <halter73@gmail.com> Co-authored-by: Pranav K <prkrishn@hotmail.com> Co-authored-by: Stephen Halter <halter73@gmail.com> Co-authored-by: Pranav K <prkrishn@hotmail.com>
This commit is contained in:
parent
cd94cd63b7
commit
659532b16c
|
|
@ -35,8 +35,19 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
|||
/// </remarks>
|
||||
public partial class HubConnection : IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The default timeout which specifies how long to wait for a message before closing the connection. Default is 30 seconds.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromSeconds(30); // Server ping rate is 15 sec, this is 2 times that.
|
||||
|
||||
/// <summary>
|
||||
/// The default timeout which specifies how long to wait for the handshake to respond before closing the connection. Default is 15 seconds.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultHandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// The default interval that the client will send keep alive messages to let the server know to not close the connection. Default is 15 second interval.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultKeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
|
||||
// This lock protects the connection state.
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
|||
}
|
||||
|
||||
// Prevents from being displayed in intellisense
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> of the current instance.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public new Type GetType()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<Description>Client for ASP.NET Core SignalR</Description>
|
||||
<TargetFrameworks>$(DefaultNetFxTargetFramework);netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<RootNamespace>Microsoft.AspNetCore.SignalR.Client</RootNamespace>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Client for ASP.NET Core SignalR</Description>
|
||||
<TargetFrameworks>$(DefaultNetFxTargetFramework);netstandard2.0</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Client for ASP.NET Core Connection Handlers</Description>
|
||||
<TargetFrameworks>$(DefaultNetFxTargetFramework);netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
/// </summary>
|
||||
public class NoTransportSupportedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="NoTransportSupportedException"/> exception with the provided <paramref name="message"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">Message of the exception.</param>
|
||||
public NoTransportSupportedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,8 +10,17 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
|
|||
/// </summary>
|
||||
public class TransportFailedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the transport that failed to connect.
|
||||
/// </summary>
|
||||
public string TransportType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="TransportFailedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="transportType">The name of the transport that failed to connect.</param>
|
||||
/// <param name="message">The reason the transport failed.</param>
|
||||
/// <param name="innerException">An optional extra exception if one was thrown while trying to connect.</param>
|
||||
public TransportFailedException(string transportType, string message, Exception innerException = null)
|
||||
: base($"{transportType} failed: {message}", innerException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,9 +5,19 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Part of the <see cref="NegotiationResponse"/> that represents an individual transport and the trasfer formats the transport supports.
|
||||
/// </summary>
|
||||
public class AvailableTransport
|
||||
{
|
||||
/// <summary>
|
||||
/// A transport available on the server.
|
||||
/// </summary>
|
||||
public string Transport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of formats supported by the transport. Examples include "Text" and "Binary".
|
||||
/// </summary>
|
||||
public IList<string> TransferFormats { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
||||
<RootNamespace>Microsoft.AspNetCore.Http.Connections</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ using Microsoft.AspNetCore.Internal;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol for reading and writing negotiate requests and responses.
|
||||
/// </summary>
|
||||
public static class NegotiateProtocol
|
||||
{
|
||||
private const string ConnectionIdPropertyName = "connectionId";
|
||||
|
|
@ -36,6 +39,11 @@ namespace Microsoft.AspNetCore.Http.Connections
|
|||
// Used to detect ASP.NET SignalR Server connection attempt
|
||||
private static ReadOnlySpan<byte> ProtocolVersionPropertyNameBytes => new byte[] { (byte)'P', (byte)'r', (byte)'o', (byte)'t', (byte)'o', (byte)'c', (byte)'o', (byte)'l', (byte)'V', (byte)'e', (byte)'r', (byte)'s', (byte)'i', (byte)'o', (byte)'n' };
|
||||
|
||||
/// <summary>
|
||||
/// Writes the <paramref name="response"/> to the <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The negotiation response generated in response to a negotiation request.</param>
|
||||
/// <param name="output">Where the <paramref name="response"/> is written to as Json.</param>
|
||||
public static void WriteResponse(NegotiationResponse response, IBufferWriter<byte> output)
|
||||
{
|
||||
var reusableWriter = ReusableUtf8JsonWriter.Get(output);
|
||||
|
|
@ -124,6 +132,11 @@ namespace Microsoft.AspNetCore.Http.Connections
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="NegotiationResponse"/> from the <paramref name="content"/> as Json.
|
||||
/// </summary>
|
||||
/// <param name="content">The bytes of a Json payload that represents a <see cref="NegotiationResponse"/>.</param>
|
||||
/// <returns>The parsed <see cref="NegotiationResponse"/>.</returns>
|
||||
public static NegotiationResponse ParseResponse(ReadOnlySpan<byte> content)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -5,14 +5,44 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// A response to a '/negotiate' request.
|
||||
/// </summary>
|
||||
public class NegotiationResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional Url to redirect the client to another endpoint.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional access token to go along with the Url.
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The public ID for the connection.
|
||||
/// </summary>
|
||||
public string ConnectionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The private ID for the connection.
|
||||
/// </summary>
|
||||
public string ConnectionToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum value between the version the client sends and the maximum version the server supports.
|
||||
/// </summary>
|
||||
public int Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of transports the server supports.
|
||||
/// </summary>
|
||||
public IList<AvailableTransport> AvailableTransports { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional error during the negotiate. If this is not null the other properties on the response can be ignored.
|
||||
/// </summary>
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods on <see cref="IEndpointRouteBuilder"/> that add routes for <see cref="ConnectionHandler"/>s.
|
||||
/// </summary>
|
||||
public static class ConnectionEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ using System;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Options used to change behavior of how connections are handled.
|
||||
/// </summary>
|
||||
public class ConnectionOptions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,20 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up <see cref="ConnectionOptions"/>.
|
||||
/// </summary>
|
||||
public class ConnectionOptionsSetup : IConfigureOptions<ConnectionOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Default timeout value for disconnecting idle connections.
|
||||
/// </summary>
|
||||
public static TimeSpan DefaultDisconectTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// Sets default values for options if they have not been set yet.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="ConnectionOptions"/>.</param>
|
||||
public void Configure(ConnectionOptions options)
|
||||
{
|
||||
if (options.DisconnectTimeout == null)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Connections.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Feature set on the <see cref="ConnectionContext"/> that provides access to the underlying <see cref="Http.HttpContext"/>
|
||||
/// associated with the connection if there is one.
|
||||
/// </summary>
|
||||
public interface IHttpContextFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Http.HttpContext"/> associated with the connection if available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Connections can run on top of HTTP transports like WebSockets or Long Polling, or other non-HTTP transports. As a result,
|
||||
/// this API can sometimes return <see langword="null"/> depending on the configuration of your application.
|
||||
/// </remarks>
|
||||
HttpContext HttpContext { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Connections.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Feature set on the <see cref="ConnectionContext"/> that exposes the <see cref="HttpTransportType"/>
|
||||
/// the connection is using.
|
||||
/// </summary>
|
||||
public interface IHttpTransportFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HttpTransportType"/> the connection is using.
|
||||
/// </summary>
|
||||
HttpTransportType TransportType { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ using Microsoft.AspNetCore.Http.Connections.Features;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to get the underlying <see cref="HttpContext"/> of the connection if there is one.
|
||||
/// </summary>
|
||||
public static class HttpConnectionContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,15 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.Http.Connections
|
||||
{
|
||||
/// <summary>
|
||||
/// Options used by the WebSockets transport to modify the transports behavior.
|
||||
/// </summary>
|
||||
public class WebSocketOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of time the WebSocket transport will wait for a graceful close before starting an ungraceful close.
|
||||
/// </summary>
|
||||
/// <value>Defaults to 5 seconds</value>
|
||||
public TimeSpan CloseTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using Microsoft.AspNetCore.SignalR.Protocol;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="MessagePackHubProtocol"/> options.
|
||||
/// </summary>
|
||||
public class MessagePackHubProtocolOptions
|
||||
{
|
||||
private MessagePackSerializerOptions? _messagePackSerializerOptions;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,34 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used by <see cref="IHubProtocol"/>s to get the <see cref="Type"/>(s) expected by the hub message being deserialized.
|
||||
/// </summary>
|
||||
public interface IInvocationBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> the invocation represented by the <paramref name="invocationId"/> is expected to contain.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the invocation being received.</param>
|
||||
/// <returns>The <see cref="Type"/> the invocation is expected to contain.</returns>
|
||||
Type GetReturnType(string invocationId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of <see cref="Type"/>s the method represented by <paramref name="methodName"/> takes as arguments.
|
||||
/// </summary>
|
||||
/// <param name="methodName">The name of the method being called.</param>
|
||||
/// <returns>A list of <see cref="Type"/>s the method takes as arguments.</returns>
|
||||
IReadOnlyList<Type> GetParameterTypes(string methodName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Type"/> the stream item is expected to contain.
|
||||
/// </summary>
|
||||
/// <param name="streamId">The ID of the stream the stream item is a part of.</param>
|
||||
/// <returns>The <see cref="Type"/> of the item the stream contains.</returns>
|
||||
Type GetStreamItemType(string streamId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,15 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CancelInvocationMessage"/> represents a cancellation of a streaming method.
|
||||
/// </summary>
|
||||
public class CancelInvocationMessage : HubInvocationMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CancelInvocationMessage"/> class.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the hub method invocation being canceled.</param>
|
||||
public CancelInvocationMessage(string invocationId) : base(invocationId)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,34 @@ using System;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an invocation that has completed. If there is an error then the invocation didn't complete successfully.
|
||||
/// </summary>
|
||||
public class CompletionMessage : HubInvocationMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional error message if the invocation wasn't completed successfully. This must be null if there is a result.
|
||||
/// </summary>
|
||||
public string? Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional result from the invocation. This must be null if there is an error.
|
||||
/// This can also be null if there wasn't a result from the method invocation.
|
||||
/// </summary>
|
||||
public object? Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether the completion contains a result.
|
||||
/// </summary>
|
||||
public bool HasResult { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="CompletionMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the invocation that has completed.</param>
|
||||
/// <param name="error">An optional error if the invocation failed.</param>
|
||||
/// <param name="result">An optional result if the invocation returns a result.</param>
|
||||
/// <param name="hasResult">Specifies whether the completion contains a result.</param>
|
||||
public CompletionMessage(string invocationId, string? error, object? result, bool hasResult)
|
||||
: base(invocationId)
|
||||
{
|
||||
|
|
@ -24,6 +46,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
HasResult = hasResult;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var errorStr = Error == null ? "<<null>>" : $"\"{Error}\"";
|
||||
|
|
@ -33,12 +56,30 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
|
||||
// Static factory methods. Don't want to use constructor overloading because it will break down
|
||||
// if you need to send a payload statically-typed as a string. And because a static factory is clearer here
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="CompletionMessage"/> with an error.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the invocation that is being completed.</param>
|
||||
/// <param name="error">The error that occurred during the invocation.</param>
|
||||
/// <returns>The constructed <see cref="CompletionMessage"/>.</returns>
|
||||
public static CompletionMessage WithError(string invocationId, string error)
|
||||
=> new CompletionMessage(invocationId, error, result: null, hasResult: false);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="CompletionMessage"/> with a result.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the invocation that is being completed.</param>
|
||||
/// <param name="payload">The result from the invocation.</param>
|
||||
/// <returns>The constructed <see cref="CompletionMessage"/>.</returns>
|
||||
public static CompletionMessage WithResult(string invocationId, object payload)
|
||||
=> new CompletionMessage(invocationId, error: null, result: payload, hasResult: true);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="CompletionMessage"/> without an error or result.
|
||||
/// This means the invocation was successful but there is no return value.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the invocation that is being completed.</param>
|
||||
/// <returns>The constructed <see cref="CompletionMessage"/>.</returns>
|
||||
public static CompletionMessage Empty(string invocationId)
|
||||
=> new CompletionMessage(invocationId, error: null, result: null, hasResult: false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes of a successful handshake message.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The protocol being used for the connection.</param>
|
||||
/// <returns>The bytes of a successful handshake message.</returns>
|
||||
public static ReadOnlySpan<byte> GetSuccessfulHandshake(IHubProtocol protocol) => _successHandshakeData.Span;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// A keep-alive message to let the other side of the connection know that the connection is still alive.
|
||||
/// </summary>
|
||||
public class PingMessage : HubMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// A static instance of the PingMessage to remove unneeded allocations.
|
||||
/// </summary>
|
||||
public static readonly PingMessage Instance = new PingMessage();
|
||||
|
||||
private PingMessage()
|
||||
|
|
|
|||
|
|
@ -3,15 +3,27 @@
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single item of an active stream.
|
||||
/// </summary>
|
||||
public class StreamItemMessage : HubInvocationMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The single item from a stream.
|
||||
/// </summary>
|
||||
public object? Item { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="StreamItemMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="invocationId">The ID of the stream.</param>
|
||||
/// <param name="item">An item from the stream.</param>
|
||||
public StreamItemMessage(string invocationId, object? item) : base(invocationId)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"StreamItem {{ {nameof(InvocationId)}: \"{InvocationId}\", {nameof(Item)}: {Item ?? "<<null>>"} }}";
|
||||
|
|
|
|||
|
|
@ -147,6 +147,12 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
// Currently used only for streaming methods
|
||||
internal ConcurrentDictionary<string, CancellationTokenSource> ActiveRequestCancellationSources { get; } = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Write a <see cref="HubMessage"/> to the connection.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="HubMessage"/> being written.</param>
|
||||
/// <param name="cancellationToken">Cancels the in progress write.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> that represents the completion of the write. If the write throws this task will still complete successfully.</returns>
|
||||
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple overloads with optional parameters", Justification = "Required to maintain compatibility")]
|
||||
public virtual ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,11 +8,22 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores <see cref="HubConnectionContext"/>s by ID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This API is meant for internal usage.
|
||||
/// </remarks>
|
||||
public class HubConnectionStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, HubConnectionContext> _connections =
|
||||
new ConcurrentDictionary<string, HubConnectionContext>(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="HubConnectionContext"/> by connection ID.
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The ID of the connection.</param>
|
||||
/// <returns>The connection for the <paramref name="connectionId"/>, null if there is no connection.</returns>
|
||||
public HubConnectionContext? this[string connectionId]
|
||||
{
|
||||
get
|
||||
|
|
@ -22,40 +33,75 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of connections in the store.
|
||||
/// </summary>
|
||||
public int Count => _connections.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="HubConnectionContext"/> to the store.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection to add.</param>
|
||||
public void Add(HubConnectionContext connection)
|
||||
{
|
||||
_connections.TryAdd(connection.ConnectionId, connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <see cref="HubConnectionContext"/> from the store.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection to remove.</param>
|
||||
public void Remove(HubConnectionContext connection)
|
||||
{
|
||||
_connections.TryRemove(connection.ConnectionId, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator over the connection store.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Enumerator"/> over the connections.</returns>
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IEnumerator"/> over the <see cref="HubConnectionStore"/>
|
||||
/// </summary>
|
||||
public readonly struct Enumerator : IEnumerator<HubConnectionContext>
|
||||
{
|
||||
private readonly IEnumerator<KeyValuePair<string, HubConnectionContext>> _enumerator;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="Enumerator"/> over the <see cref="HubConnectionStore"/>.
|
||||
/// </summary>
|
||||
/// <param name="hubConnectionList">The store of connections to enumerate over.</param>
|
||||
public Enumerator(HubConnectionStore hubConnectionList)
|
||||
{
|
||||
_enumerator = hubConnectionList._connections.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current connection the enumerator is on.
|
||||
/// </summary>
|
||||
public HubConnectionContext Current => _enumerator.Current.Value;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the enumerator.
|
||||
/// </summary>
|
||||
public void Dispose() => _enumerator.Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Moves the enumerator to the next value.
|
||||
/// </summary>
|
||||
/// <returns>True if there is another connection. False if there are no more connections.</returns>
|
||||
public bool MoveNext() => _enumerator.MoveNext();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the enumerator to the beginning.
|
||||
/// </summary>
|
||||
public void Reset() => _enumerator.Reset();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
/// </summary>
|
||||
public class HubMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="HubMetadata"/> of the given <see cref="Hub"/> type.
|
||||
/// </summary>
|
||||
/// <param name="hubType">The <see cref="Type"/> of the <see cref="Hub"/>.</param>
|
||||
public HubMetadata(Type hubType)
|
||||
{
|
||||
HubType = hubType;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to configure the <see cref="HubOptions"/>.
|
||||
/// </summary>
|
||||
public class HubOptionsSetup : IConfigureOptions<HubOptions>
|
||||
{
|
||||
internal static TimeSpan DefaultHandshakeTimeout => TimeSpan.FromSeconds(15);
|
||||
|
|
@ -23,6 +26,10 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
|
||||
private readonly List<string> _defaultProtocols = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="HubOptionsSetup"/> with a list of protocols added to Dependency Injection.
|
||||
/// </summary>
|
||||
/// <param name="protocols">The list of <see cref="IHubProtocol"/>s that are from Dependency Injection.</param>
|
||||
public HubOptionsSetup(IEnumerable<IHubProtocol> protocols)
|
||||
{
|
||||
foreach (var hubProtocol in protocols)
|
||||
|
|
@ -35,6 +42,10 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default values of the <see cref="HubOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="HubOptions"/> to configure.</param>
|
||||
public void Configure(HubOptions options)
|
||||
{
|
||||
if (options.KeepAliveInterval == null)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,27 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to configure the <see cref="HubOptions"/> for a specific <typeparamref name="THub"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="THub">The <see cref="Hub"/> type to configure.</typeparam>
|
||||
public class HubOptionsSetup<THub> : IConfigureOptions<HubOptions<THub>> where THub : Hub
|
||||
{
|
||||
private readonly HubOptions _hubOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the options configuration class.
|
||||
/// </summary>
|
||||
/// <param name="options">The global <see cref="HubOptions"/> from Dependency Injection.</param>
|
||||
public HubOptionsSetup(IOptions<HubOptions> options)
|
||||
{
|
||||
_hubOptions = options.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default values of the <see cref="HubOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to configure.</param>
|
||||
public void Configure(HubOptions<THub> options)
|
||||
{
|
||||
// Do a deep copy, otherwise users modifying the HubOptions<THub> list would be changing the global options list
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods on <see cref="IEndpointRouteBuilder"/> to add routes to <see cref="Hub"/>s.
|
||||
/// </summary>
|
||||
public static class HubEndpointRouteBuilderExtensions
|
||||
{
|
||||
private const DynamicallyAccessedMemberTypes HubAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<IsAspNetCoreApp>true</IsAspNetCoreApp>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Provides scale-out support for ASP.NET Core SignalR using a Redis server and the StackExchange.Redis client.</Description>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ using StackExchange.Redis;
|
|||
|
||||
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
||||
{
|
||||
/// <summary>
|
||||
/// The Redis scaleout provider for multi-server support.
|
||||
/// </summary>
|
||||
/// <typeparam name="THub">The type of <see cref="Hub"/> to manage connections for.</typeparam>
|
||||
public class RedisHubLifetimeManager<THub> : HubLifetimeManager<THub>, IDisposable where THub : Hub
|
||||
{
|
||||
private readonly HubConnectionStore _connections = new HubConnectionStore();
|
||||
|
|
@ -34,6 +38,12 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
private readonly AckHandler _ackHandler;
|
||||
private int _internalId;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="RedisHubLifetimeManager{THub}"/> with types from Dependency Injection.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger to write information about what the class is doing.</param>
|
||||
/// <param name="options">The <see cref="RedisOptions"/> that influence behavior of the Redis connection.</param>
|
||||
/// <param name="hubProtocolResolver">The <see cref="IHubProtocolResolver"/> to get an <see cref="IHubProtocol"/> instance when writing to connections.</param>
|
||||
public RedisHubLifetimeManager(ILogger<RedisHubLifetimeManager<THub>> logger,
|
||||
IOptions<RedisOptions> options,
|
||||
IHubProtocolResolver hubProtocolResolver)
|
||||
|
|
@ -41,6 +51,14 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the <see cref="RedisHubLifetimeManager{THub}"/> with types from Dependency Injection.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger to write information about what the class is doing.</param>
|
||||
/// <param name="options">The <see cref="RedisOptions"/> that influence behavior of the Redis connection.</param>
|
||||
/// <param name="hubProtocolResolver">The <see cref="IHubProtocolResolver"/> to get an <see cref="IHubProtocol"/> instance when writing to connections.</param>
|
||||
/// <param name="globalHubOptions">The global <see cref="HubOptions"/>.</param>
|
||||
/// <param name="hubOptions">The <typeparamref name="THub"/> specific options.</param>
|
||||
public RedisHubLifetimeManager(ILogger<RedisHubLifetimeManager<THub>> logger,
|
||||
IOptions<RedisOptions> options,
|
||||
IHubProtocolResolver hubProtocolResolver,
|
||||
|
|
@ -65,6 +83,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
_ = EnsureRedisServerConnection();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task OnConnectedAsync(HubConnectionContext connection)
|
||||
{
|
||||
await EnsureRedisServerConnection();
|
||||
|
|
@ -86,6 +105,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
await Task.WhenAll(connectionTask, userTask);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task OnDisconnectedAsync(HubConnectionContext connection)
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
|
|
@ -119,18 +139,21 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendAllAsync(string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var message = _protocol.WriteInvocation(methodName, args);
|
||||
return PublishAsync(_channels.All, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendAllExceptAsync(string methodName, object[] args, IReadOnlyList<string> excludedConnectionIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var message = _protocol.WriteInvocation(methodName, args, excludedConnectionIds);
|
||||
return PublishAsync(_channels.All, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendConnectionAsync(string connectionId, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connectionId == null)
|
||||
|
|
@ -150,6 +173,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return PublishAsync(_channels.Connection(connectionId), message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendGroupAsync(string groupName, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (groupName == null)
|
||||
|
|
@ -161,6 +185,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return PublishAsync(_channels.Group(groupName), message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendGroupExceptAsync(string groupName, string methodName, object[] args, IReadOnlyList<string> excludedConnectionIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (groupName == null)
|
||||
|
|
@ -172,12 +197,14 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return PublishAsync(_channels.Group(groupName), message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendUserAsync(string userId, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var message = _protocol.WriteInvocation(methodName, args);
|
||||
return PublishAsync(_channels.User(userId), message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connectionId == null)
|
||||
|
|
@ -200,6 +227,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return SendGroupActionAndWaitForAck(connectionId, groupName, GroupAction.Add);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connectionId == null)
|
||||
|
|
@ -222,6 +250,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return SendGroupActionAndWaitForAck(connectionId, groupName, GroupAction.Remove);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendConnectionsAsync(IReadOnlyList<string> connectionIds, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connectionIds == null)
|
||||
|
|
@ -240,6 +269,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return Task.WhenAll(publishTasks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendGroupsAsync(IReadOnlyList<string> groupNames, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (groupNames == null)
|
||||
|
|
@ -260,6 +290,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
return Task.WhenAll(publishTasks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SendUsersAsync(IReadOnlyList<string> userIds, string methodName, object[] args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (userIds.Count > 0)
|
||||
|
|
@ -352,6 +383,9 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the Redis connection.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_bus?.UnsubscribeAll();
|
||||
|
|
|
|||
Loading…
Reference in New Issue