Cliente SignalR .NET conectando-se ao Serviço SignalR do Azure em um aplicativo Blazor .NET Core 3

11

Estou tentando fazer uma conexão entre meu aplicativo ASP.NET Core 3.0 Blazor (lado do servidor) e o Serviço SignalR do Azure. Acabarei injetando meu cliente SignalR (serviço) em alguns componentes do Blazor para que eles atualizem minha UI / DOM em tempo real.

Meu problema é que estou recebendo a seguinte mensagem quando ligo meu .StartAsync()método na conexão do hub:

O código de status da resposta não indica sucesso: 404 (Não encontrado).

BootstrapSignalRClient.cs

Este arquivo carrega minha configuração para o Serviço SignalR, incluindo URL, cadeia de conexão, chave, nome do método e nome do hub. Essas configurações são capturadas na classe estática SignalRServiceConfiguratione usadas posteriormente.

public static class BootstrapSignalRClient
{
    public static IServiceCollection AddSignalRServiceClient(this IServiceCollection services, IConfiguration configuration)
    {
        SignalRServiceConfiguration signalRServiceConfiguration = new SignalRServiceConfiguration();
        configuration.Bind(nameof(SignalRServiceConfiguration), signalRServiceConfiguration);

        services.AddSingleton(signalRServiceConfiguration);
        services.AddSingleton<ISignalRClient, SignalRClient>();

        return services;
    }
}

SignalRServiceConfiguration.cs

public class SignalRServiceConfiguration
{
    public string ConnectionString { get; set; }
    public string Url { get; set; }
    public string MethodName { get; set; }
    public string Key { get; set; }
    public string HubName { get; set; }
}

SignalRClient.cs

public class SignalRClient : ISignalRClient
{
    public delegate void ReceiveMessage(string message);
    public event ReceiveMessage ReceiveMessageEvent;

    private HubConnection hubConnection;

    public SignalRClient(SignalRServiceConfiguration signalRConfig)
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(signalRConfig.Url + signalRConfig.HubName)
            .Build();            
    }

    public async Task<string> StartListening(string id)
    {
        // Register listener for a specific id
        hubConnection.On<string>(id, (message) => 
        {
            if (ReceiveMessageEvent != null)
            {
                ReceiveMessageEvent.Invoke(message);
            }
        });

        try
        {
            // Start the SignalR Service connection
            await hubConnection.StartAsync(); //<---I get an exception here
            return hubConnection.State.ToString();
        }
        catch (Exception ex)
        {
            return ex.Message;
        }            
    }

    private void ReceiveMessage(string message)
    {
        response = JsonConvert.DeserializeObject<dynamic>(message);
    }
}

Tenho experiência no uso do SignalR com o .NET Core, onde você o adiciona para que o Startup.csarquivo que use .AddSignalR().AddAzureSignalR()e mapeie um hub na configuração do aplicativo e faça dessa maneira exija que certos parâmetros de 'configuração' sejam estabelecidos (ou seja, cadeia de conexão).

Dada a minha situação, onde é HubConnectionBuilderque a cadeia de conexão ou uma chave se autentica no Serviço SignalR?

É possível que a mensagem 404 seja o resultado da falta da chave / cadeia de conexão?

Jason Shave
fonte
11
.WithUrl(signalRConfig.Url + signalRConfig.HubName)Você pode verificar se isso está resultando no URL correto? (Por ponto de interrupção ou registro em log?) #
Fildor 10/10/19
Eu achei útil ter o Uri base Urie construir o completo via Uri (Uri, string) #
Fildor 10/10/1919
curiosamente, foi um 'arenque vermelho' e não tinha nada a ver com o 404.
Jason Shave

Respostas:

8

Ok, então a documentação está faltando uma parte importante das informações aqui. Se você estiver usando o .NET SignalR Client que está se conectando ao Serviço SignalR do Azure, precisará solicitar um token JWT e apresentá-lo ao criar a conexão do hub.

Se você precisar se autenticar em nome de um usuário, poderá usar este exemplo.

Caso contrário, você pode configurar um ponto de extremidade "/ negociar" usando uma API da web, como uma Função do Azure, para recuperar um token JWT e uma URL do cliente para você; foi isso que acabei fazendo no meu caso de uso. Informações sobre como criar uma Função do Azure para obter seu token e URL JWT podem ser encontradas aqui.

Eu criei uma classe para armazenar esses dois valores como tal:

SignalRConnectionInfo.cs

public class SignalRConnectionInfo
{
    [JsonProperty(PropertyName = "url")]
    public string Url { get; set; }
    [JsonProperty(PropertyName = "accessToken")]
    public string AccessToken { get; set; }
}

Também criei um método dentro do meu SignalRServicepara lidar com a interação com o terminal "/ negociar" da API da Web no Azure, a instanciação da conexão do hub e o uso de um evento + delegado para receber mensagens da seguinte maneira:

SignalRClient.cs

public async Task InitializeAsync()
{
    SignalRConnectionInfo signalRConnectionInfo;
    signalRConnectionInfo = await functionsClient.GetDataAsync<SignalRConnectionInfo>(FunctionsClientConstants.SignalR);

    hubConnection = new HubConnectionBuilder()
        .WithUrl(signalRConnectionInfo.Url, options =>
        {
           options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
        })
        .Build();
}

A functionsClienté simplesmente uma rigidez de tipos HttpClientpré-configurado com um URL de base e a FunctionsClientConstants.SignalRé uma classe estática com o "/ negociar" caminho que é acrescentado ao URL de base.

Depois de configurar tudo, liguei para o await hubConnection.StartAsync();e "conectado"!

Depois de tudo isso, configurei um ReceiveMessageevento estático e um delegado da seguinte forma (na mesma SignalRClient.cs):

public delegate void ReceiveMessage(string message);
public static event ReceiveMessage ReceiveMessageEvent;

Por fim, implementei o ReceiveMessagedelegado:

await signalRClient.InitializeAsync(); //<---called from another method

private async Task StartReceiving()
{
    SignalRStatus = await signalRClient.ReceiveReservationResponse(Response.ReservationId);
    logger.LogInformation($"SignalR Status is: {SignalRStatus}");

    // Register event handler for static delegate
    SignalRClient.ReceiveMessageEvent += signalRClient_receiveMessageEvent;
}

private async void signalRClient_receiveMessageEvent(string response)
{
    logger.LogInformation($"Received SignalR mesage: {response}");
    signalRReservationResponse = JsonConvert.DeserializeObject<SignalRReservationResponse>(response);
    await InvokeAsync(StateHasChanged); //<---used by Blazor (server-side)
}

Forneci atualizações de documentação à equipe do Serviço SignalR do Azure e espero que isso ajude outra pessoa!

Jason Shave
fonte