Это приложение использует Azure Signal R, работающее в режиме по умолчанию.
Я хочу, чтобы другое приложение отправляло сообщения клиенту blazor с помощью Azure Signal R. Теоретически это приложение должно быть чем угодно, но на практике оно будет из устойчивой функции Azure (функции 4.0, .Net 9, Изолированный режим), то есть длительный процесс, который по завершении отправит сообщение.
Для этого кода/вопроса я использую простое тестовое сообщение только для проверки подключения.
Моя проблема в том, что когда я отправляю сообщения через внешнее приложение, я не получаю никаких ошибок, а клиент не получает сообщений.
Настройка клиента
Program.cs
Код: Выделить всё
var signalRConnectionString = configuration.GetValue(ConfigurationConstants.ConfigurationKeys.SignalR.SignalRConnectionString);
services.AddSignalR(options =>
{
options.MaximumReceiveMessageSize = maximumReceiveMessageSize;
options.EnableDetailedErrors = true;
})
.AddAzureSignalR(options =>
{
options.ConnectionString = signalRConnectionString;
});
...
app.MapHub($"/{SignalRConstants.Hubs.Messages.HubName}");
Код: Выделить всё
public interface IMessagesHub
{
Task ConnectedAsync();
Task DisconnectedAsync(Exception? exception = null);
Task ReceiveTestMessage(string name, string message);
}
public class MessagesHub : Hub
{
public override async Task OnConnectedAsync()
{
await Clients.Caller.ConnectedAsync();
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
await Clients.Caller.DisconnectedAsync(exception);
await base.OnDisconnectedAsync(exception);
}
public async Task SendTestMessage(string name, string message)
{
Console.WriteLine($"### - [{Context.ConnectionId}] Send Test Message [{name}]: {message}");
await Clients.All.ReceiveTestMessage(name, message);
}
}
Код: Выделить всё
public interface IMessagesHubService : IAsyncDisposable
{
bool IsConnected { get; }
string ConnectionId { get; }
Task BeginMonitoringAsync(CancellationToken cancellationToken = default);
Task StopMonitoringAsync(CancellationToken cancellationToken = default);
Task SendTestMessage(string name, string message);
}
public class MessagesHubService(IConfigurationService configService,
NavigationManager navigationManager,
ISignalRHubConnectionFactory hubConnectionFactory,
IMessagesCallbackService callbackService,
ILoggerFactory loggerFactory)
: IMessagesHubService
{
private static HubConnection? _connection;
private readonly ILogger _logger = loggerFactory.CreateLogger();
public bool IsConnected => (_connection?.State ?? HubConnectionState.Disconnected) == HubConnectionState.Connected;
public string ConnectionId => _connection?.ConnectionId ?? "Not connected";
private async Task InitializeSignalRAsync()
{
if (_connection != null)
{
return _connection;
}
var hubRoute = SignalRConstants.Hubs.Messages.HubRoute;
if (await configService.IsFeatureFlagEnabledAsync(ConfigurationConstants.FeatureFlags.UseAzureSignalR))
{
hubRoute = $"/{SignalRConstants.Hubs.Messages.HubName}";
}
var hubConnectionUrl = navigationManager.ToAbsoluteUri(hubRoute);
_connection = await hubConnectionFactory.CreateHubConnectionAsync(hubConnectionUrl);
InitializeEvents(_connection);
return _connection;
}
private void InitializeEvents(HubConnection connection)
{
connection.On(SignalRConstants.Hubs.Messages.Methods.Connected, OnConnectedAsync);
connection.On(SignalRConstants.Hubs.Messages.Methods.Disconnected, OnDisconnectedAsync);
connection.On(SignalRConstants.Hubs.Messages.Methods.TestMessage, TestMessageReceivedAsync);
}
private async Task OnConnectedAsync()
{
await callbackService.ConnectedAsync();
}
private async Task OnDisconnectedAsync(Exception? exception = null)
{
await callbackService.DisconnectedAsync(exception);
}
private async Task TestMessageReceivedAsync(string name, string message)
{
await callbackService.ReceiveTestMessageAsync(name, message);
}
public async Task BeginMonitoringAsync(CancellationToken cancellationToken = default)
{
var connection = await InitializeSignalRAsync();
if (connection == null)
{
return false;
}
if (connection.State == HubConnectionState.Connected)
{
return true;
}
if (connection.State == HubConnectionState.Disconnected)
{
try
{
await connection.StartAsync(cancellationToken);
return true;
}
catch (Exception e)
{
_logger.WriteException(e, "Error while starting Signal R Service");
}
}
return false;
}
public async Task StopMonitoringAsync(CancellationToken cancellationToken = default)
{
var connection = await InitializeSignalRAsync();
if (connection == null)
{
return;
}
if (connection.State != HubConnectionState.Disconnected)
{
await connection.StopAsync(cancellationToken);
}
}
public async Task SendTestMessage(string name, string message)
{
var connection = await InitializeSignalRAsync();
if (connection == null)
{
return;
}
if (connection.State != HubConnectionState.Connected)
{
await BeginMonitoringAsync();
}
await connection.InvokeAsync(SignalRConstants.Hubs.Messages.Methods.Testing.SendTestMessage, name, message);
}
public async ValueTask DisposeAsync()
{
if (_connection != null)
{
await _connection.DisposeAsync();
_connection = null;
}
}
}
Код: Выделить всё
public class SignalRHubConnectionFactory(IConfigurationService configService,
ILoggerFactory loggerFactory) : ISignalRHubConnectionFactory
{
private readonly ILogger _logger = loggerFactory.CreateLogger();
public async Task CreateHubConnectionAsync(Uri hubRoute,
CancellationToken cancellationToken = default)
{
try
{
var ignoreCertificate = await configService.IsFeatureFlagEnabledAsync(ConfigurationConstants.FeatureFlags.IgnoreCertificateErrors);
var connection = new HubConnectionBuilder()
.WithUrl(hubRoute, options =>
{
//options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets;
options.HttpMessageHandlerFactory = (innerHandler) =>
{
if (ignoreCertificate && innerHandler is HttpClientHandler clientHandler)
{
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
}
return innerHandler;
//return new SignalRHubMessageHandler { InnerHandler = innerHandler };
};
})
.WithAutomaticReconnect()
.ConfigureLogging(config =>
{
config.SetMinimumLevel(LogLevel.Debug);
})
.Build();
return connection;
}
catch (Exception e)
{
_logger.WriteException(e, "Error during connection to SignalR Service");
throw;
}
}
}
Код: Выделить всё
public interface IMessagesCallbackService
{
event Func? OnConnectedAsync;
event Func? OnDisconnectedAsync;
event Func? OnTestMessageReceivedAsync;
Task ReceiveTestMessageAsync(string name, string message);
Task ConnectedAsync();
Task DisconnectedAsync(Exception? exception = null);
}
public class MessagesCallbackService : IMessagesCallbackService
{
public event Func? OnTestMessageReceivedAsync;
public event Func? OnConnectedAsync;
public event Func? OnDisconnectedAsync;
public async Task ConnectedAsync()
{
if (OnConnectedAsync != null)
{
await OnConnectedAsync.Invoke();
}
}
public async Task DisconnectedAsync(Exception? exception = null)
{
if (OnDisconnectedAsync != null)
{
await OnDisconnectedAsync.Invoke(exception);
}
}
public async Task ReceiveTestMessageAsync(string name, string message)
{
if (OnTestMessageReceivedAsync != null)
{
await OnTestMessageReceivedAsync.Invoke(name, message);
}
}
}
Внешнее приложение (приложение-функции)
Program.cs
Код: Выделить всё
var signalRConnectionString = configuration.GetValue(ConfigurationConstants.ConfigurationKeys.SignalRConnectionString);
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
}).AddAzureSignalR(signalRConnectionString);
Код: Выделить всё
public class SignalRMessagesService(IConfigurationService configService,
ISignalRConnectionFactory connectionFactory,
ILoggerFactory loggerFactory) : ISignalRMessagesService
{
private readonly ILogger _logger = loggerFactory.CreateLogger();
private ServiceManager? _serviceManager;
private HubConnection? _connection;
private async Task GetServiceManagerAsync()
{
if (_serviceManager != null)
{
return _serviceManager;
}
try
{
_serviceManager = await connectionFactory.GetAzureServiceManagerAsync();
return _serviceManager;
}
catch (Exception e)
{
_logger.WriteException(e, "Error building Service Manager");
throw;
}
}
public async Task SendTestMessageAsync(string name, string message, CancellationToken cancellationToken = default)
{
var serviceManager = await GetServiceManagerAsync();
if (serviceManager == null)
{
throw new ArgumentNullException(nameof(ServiceManager));
}
try
{
await using var serviceHubContext = await serviceManager.CreateHubContextAsync(SignalRConstants.Hubs.Messages.HubName, cancellationToken);
await serviceHubContext
.Clients
.All
.SendAsync(method:SignalRConstants.Hubs.Messages.Methods.TestMessage, arg1:name, arg2:message, cancellationToken: cancellationToken);
}
catch (Exception e)
{
_logger.WriteException(e, "Error sending Test Message");
throw;
}
}
public void Dispose()
{
_serviceManager?.Dispose();
}
public async ValueTask DisposeAsync()
{
}
}
Код: Выделить всё
public class SignalRConnectionFactory(IConfigurationService configService,
ILoggerFactory loggerFactory) : ISignalRConnectionFactory
{
private readonly ILogger _logger = loggerFactory.CreateLogger();
public Task GetAzureServiceManagerAsync()
{
var serviceManager = new ServiceManagerBuilder()
.WithOptions(option =>
{
option.ConnectionString = configService.GetConfigurationValue(ConfigurationConstants.ConfigurationKeys
.SignalRConnectionString);
option.UseJsonObjectSerializer(new JsonObjectSerializer(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}));
})
.WithLoggerFactory(loggerFactory)
.BuildServiceManager();
return Task.FromResult(serviceManager);
}
}
Что я упускаю или делаю неправильно, чтобы сообщения не передавались между приложениями через одно и то же соединение Azure Signal R?>
Подробнее здесь: https://stackoverflow.com/questions/798 ... fault-mode
Мобильная версия