0
votes

Too Long; Didn't Read: How does one implement a strongly-typed SignalR hub (custom methods) in Angular 6?


Ultimately, I'm trying to use a Strongly-Typed SignalR Hub in Angular 6 using services and interfaces. I'll start with the Hub first. Since SignalR has the option to now strongly-typed commands, I'd like to use these for better securing data. I wouldn't mind also having a way to do authentication, but that may be a story for a different time.

ApHub.cs

using System.Threading.Tasks;
using AckermanPuglisi.Thesaurus;
using Microsoft.AspNetCore.SignalR;

namespace Tinhalo.Hubs {
    public class ApHub : Hub<INlgClient> {
        private readonly IApContext _apContext;

        public ApHub(IApContext apContext) {
            _apContext = apContext;
        }

        public async Task GetTraits() {
            await Clients.Caller.GetAllTraits(_apContext.Traits);
        }

        public async Task GetEmotions() {
            await Clients.Caller.GetAllEmotions(_apContext.Emotions);
        }

        public async Task ConnectionMessage() {
            await Clients.Caller.CheckConnection("You are connected");
        }
    }
}

From there, I create the interface for SignalR to have the typed methods that I'll need to call in Angular. I've made the models already in a models folder, but I'll leave out the details on that because I'm not sure if they are relevant.

INlgClient.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AckermanPuglisi.Thesaurus.Data.Graph.Models;

namespace Tinhalo.Hubs {
    public interface INlgClient {
        Task GetAllTraits(ICharacterTrait[] apContextTraits);
        Task GetTrait(ICharacterTrait trait);
        Task GetAllEmotions(Emotion[] apContextEmotions);
        Task GetEmotion(Emotion emotion);   
        Task GetTraitEmotions(string traitName);
        Task CheckConnection(string connectMessage);
    }
}

These previous two things work for sure when they are un-typed. After that, I do some stuff in Angular 6, namely the service (which I've found out you can't start() the hub connection until you have window defined... else you get a XMLHttpRequest undefined error)

signal.service.ts

import { Injectable, Inject, OnInit, AfterViewInit } from '@angular/core';
import { HubConnection, HubConnectionBuilder, JsonHubProtocol } from "@aspnet/signalr";
import { Emotion } from '../models/emotion.model';
import { Trait } from '../models/trait.model';
import * as signalr from '@aspnet/signalr';
import { Subject } from 'rxjs';

@Injectable({
    providedIn: "root"
})
export class SignalService {
    public aphub: HubConnection;

    public hubConnected: any;

    constructor(@Inject('BASE_URL') baseUrl: string) {
        let options = {
            transport: signalr.HttpTransportType.WebSockets
                | signalr.HttpTransportType.LongPolling,
            AccessTokenFactory: async () => {
                // Get and return the access token.
                // This function can return a JavaScript Promise if asynchronous
                // logic is required to retrieve the access token.
            }
        };
        let protocol = new signalr.JsonHubProtocol();

        this.aphub = new signalr.HubConnectionBuilder()
            .configureLogging(signalr.LogLevel.Trace)
            .withUrl(`${baseUrl}aphub`, options)
            .withHubProtocol(protocol).build();
    }
}

Now, if I do this.aphub.getTraits((traits) => { ... }); which is the equivalent to hub.invoke('getTraits' ... ), it'll say that the method isn't defined at design-time but will show up on inspection during run time. This tells me that I totally need an interface for this - which I sorta have. I think the big issue is that I'll likely be able to implement my custom methods on the new class, but I don't know what I must custom implement because of the mix of private/internal stuff to SignalR HubConnections.

I may not even be implementing the right object, even.

aphub.interface.ts

import { HubConnection } from "@aspnet/signalr";

interface IAphub extends HubConnection {
    getTraits(): void;
    getTrait(traitName: string): void;
    getTraitEmotions(traitName: string): void;
    getEmotions(): void;
    getEmotion(emotionName: string): void;
    connectionMessage(): void;
}

class ApHub implements IAphub {
  /* What goes here? */
}

Please be kind, I'm fairly new to Angular itself but I get definitely get the C# part of it. I've been in MVC for quite a while - Typescript is a bit new to me, but I get most of it. SignalR seems the best way to go about asynchronous data because it has more than .Caller, but also .Broadcast which will come in handy later on down the line.

Google has a tonne of stuff on Angular, but with Angular 1.x, Angular 2.x, and all of the versioning confusion (ng-thing vs. ng2-thing vs. ngx-thing plus stuff with $ and $scope and again with dumptrucks full of Angular 4.x code), I just want to find an answer for my AspNetCore 2.1 (to be 3.0), SignalR@latest, and Angular 6 website.


I have tried to generate an interface using SignalR.exe, but it doesn't work saying it is missing a manifest - which is likely because it is a dotnet core application.

1
I suggest you try putting a TLDR version with an actual question. I've skimmed the question but the only question you have is a comment What does here?.Lazar Ljubenović
Added the topper - I had thought the title would've been the TL;DR.Manchuwook

1 Answers

0
votes

I dont know if can help you, but Ive created a boilerplate in angular6 to work with signalR.

You can try understand the code in my repository, I tried to make as more simple possible.

Feel free to clone or ask

FrontEnd

BackEnd

Before start the front-end you need follow this commands:

  • npm install
  • npm install @angular/cli

To run the project just type:

  • ng serve

To start the back-end you need follow this commands:

  • dotnet restore
  • dotnet watch run

I think that I got it what you want, I guess you want to mapping the methods in a hub(back) on front, right?