Cliente SignalR .NET que se conecta al servicio Azure SignalR en una aplicación Blazor .NET Core 3

11

Estoy tratando de establecer una conexión entre mi aplicación ASP.NET Core 3.0 Blazor (del lado del servidor) y el servicio Azure SignalR. Terminaré inyectando mi cliente (servicio) SignalR en algunos componentes de Blazor para que actualicen mi UI / DOM en tiempo real.

Mi problema es que recibo el siguiente mensaje cuando llamo a mi .StartAsync()método en la conexión del concentrador:

El código de estado de respuesta no indica éxito: 404 (no encontrado).

BootstrapSignalRClient.cs

Este archivo carga mi configuración para el servicio SignalR, incluida la URL, la cadena de conexión, la clave, el nombre del método y el nombre del concentrador. Estas configuraciones se capturan en la clase estática SignalRServiceConfigurationy se usan más tarde.

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);
    }
}

Tengo experiencia en el uso de SignalR con .NET Core donde lo agrega para que el Startup.csarchivo que usa .AddSignalR().AddAzureSignalR()y asigne un concentrador en la configuración de la aplicación y lo haga de esta manera requiere que se establezcan ciertos parámetros de 'configuración' (es decir, cadena de conexión).

Dada mi situación, ¿de dónde HubConnectionBuilderobtiene la cadena de conexión o una clave para autenticarse en el servicio SignalR?

¿Es posible que el mensaje 404 sea el resultado de la clave / cadena de conexión que falta?

Jason Shave
fuente
1
.WithUrl(signalRConfig.Url + signalRConfig.HubName)¿Puedes verificar que esto está dando como resultado la URL correcta? (¿Por punto de corte o registro?)
Fildor
Me pareció útil tener el Uri base Uriy construir el completo a través de Uri (Uri, string)
Fildor
Curiosamente, era un 'arenque rojo' y no tenía nada que ver con el 404.
Jason Shave

Respuestas:

8

De acuerdo, resulta que la documentación carece de información clave aquí. Si usa el cliente .NET SignalR que se conecta al servicio Azure SignalR, debe solicitar un token JWT y presentarlo al crear la conexión del concentrador.

Si necesita autenticarse en nombre de un usuario, puede usar este ejemplo.

De lo contrario, puede configurar un punto final "/ negociar" utilizando una API web como una función de Azure para recuperar un token JWT y la URL del cliente por usted; Esto es lo que terminé haciendo para mi caso de uso. Aquí puede encontrar información sobre cómo crear una función de Azure para obtener su token JWT y URL .

Creé una clase para mantener estos dos valores como tales:

SignalRConnectionInfo.cs

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

También creé un método dentro de mi SignalRServicepara manejar la interacción con el punto final "/ negociar" de la API web en Azure, la instanciación de la conexión del concentrador y el uso de un evento + delegado para recibir mensajes de la siguiente manera:

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();
}

El functionsClientes simplemente un HttpClientpreconfigurado fuertemente tipado con una URL base y FunctionsClientConstants.SignalRes una clase estática con la ruta "/ negociar" que se agrega a la URL base.

Una vez que configuré todo esto, llamé al await hubConnection.StartAsync();y se "conectó".

Después de todo esto, configuré un ReceiveMessageevento estático y un delegado de la siguiente manera (en el mismo SignalRClient.cs):

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

Por último, implementé el 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)
}

¡He proporcionado actualizaciones de documentación al equipo del Servicio Azure SignalR y espero que esto ayude a alguien más!

Jason Shave
fuente