Wasiim Ouro-sama
Wasiim Ouro-sama

Reputation: 131

.NET core angular project cannot send data if the connection is not in the connected state?

I'm running a .NET core angular project in VScode on windows and I'm trying to get signalR to work but unfortunately things don't seem to be going my way.Before when I had my angular and .net core projects split up(web API and angular project separate) with the same code, this issue did not occur. It's when I switched to a .NET core angular project that it's now happening. Thanks in advance.

when I try to send a message to the server, I get the following error

enter image description here ChatHub.cs

using System;
using System.Threading.Tasks;
using System.Collections.Generic;

using Microsoft.AspNetCore.SignalR;

namespace ProjectConker
{
    public class IMessage
    {
       public string timestamp;
       public string message;
       public string author;
       public string iconLink;
   }
    public class ChatHub : Hub
    {
        public Task SendToAll(string name, IMessage messageData)
        {
            Console.WriteLine(messageData.message);
             return Clients.All.SendAsync("ReceiveMessage", name, messageData);
        }
    } 
}

Angular Client component


import { Component, OnInit } from '@angular/core';
import { ITag } from '../Tag/tag';
import { TagComponent } from '../Tag/tag.component';
import { Router, ActivatedRoute, ParamMap, Params } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { IMessage } from './Message';
import { HubConnection, HubConnectionBuilder, HttpTransportType } from '@aspnet/signalr';
import { HttpClient } from '@angular/common/http';



@Component({
  selector: 'chat',
  templateUrl: "./chatComponent.component.html",
  styleUrls: ['./chatComponent.component.css']
  // providers: [ ChatService ]
})

export class ChatComponent implements OnInit {

  hubConnection: HubConnection;

  messageData: IMessage;
  message: string;

  //profile info
  author: string;
  iconLink: string;

  messages: IMessage[] = [];

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private http: HttpClient
  ) { }

  ngOnInit() {

    this.getRandomUser();

    this.hubConnection = new HubConnectionBuilder().withUrl("http://localhost:5000/api/chat", {
      skipNegotiation: true,
      transport: HttpTransportType.WebSockets
    }).build();

    this.hubConnection
      .start()
      .then(() => console.log('Connection started!'))
      .catch(err => console.log('Error while establishing connection :('));

    this.hubConnection.on('ReceiveMessage', (author: string, receivedMessage: IMessage) => {

      this.messages.push(receivedMessage);
    });

  }

  private getRandomUser() {
    this.http.get("https://randomuser.me/api/")
      .subscribe(
        (data: any) => {
          console.log(data);

          this.iconLink = data.results[0].picture.medium;
          this.author = data.results[0].name.first;

          console.log(this.iconLink);
        }
      );

  }

  public sendMessage(): void {

    var today = new Date();

    this.messageData = {
      author: this.author,
      message: this.message,
      timestamp: ("0" + today.getHours()).toString().slice(-2) + ":" + ("0" + today.getMinutes()).toString().slice(-2),
      iconLink: this.iconLink
    };
    this.hubConnection
      .invoke('SendToAll', this.author, this.messageData)
      .catch(err => console.error(err));

    this.message = '';
  }


}

angular client view

 <div class="outer-chat-container">
    <div class="chat-container">

        <ng-container *ngIf="messages.length > 0">
            <div class="message-box"  *ngFor="let message of messages">
              <img class="avatar" [src]='message.iconLink'>


              <div class="content">
                <div class="headline">
                    <p class="author">{{ message.author }}</p>
                    <div class="datetime">{{ message.timestamp }}</div>
                </div>


                <p class="message">{{ message.message }}</p>
              </div>
            </div>

         </ng-container>
    </div>
    <form class="send-message-form" (ngSubmit)="sendMessage()" #chatForm="ngForm">
        <div>
            <input type="text" name="message" [(ngModel)]="message" placeholder="Your message" required>
            <button type="submit">Send</button>
        </div>
    </form>
</div>

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

using ProjectConker.Models;
using ProjectConker.Roadmaps;
using ProjectConker.Searching;

namespace ProjectConker
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });

            services.AddHttpClient();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddCors(options => options.AddPolicy("CorsPolicy", 
            builder => 
            {
                builder.AllowAnyMethod().AllowAnyHeader()
                       .WithOrigins("*")
                       .AllowCredentials();
            }));

            services.AddSignalR();

            services.AddEntityFrameworkSqlServer();

           // services.AddIdentity<IdentityUser, IdentityRole>();

            services.AddDbContext<ConkerDbContext>(
    options => options.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll));

            services.AddScoped<SearchEngine>();
            services.AddTransient<RoadmapService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            // app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });

            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatHub>("/api/chat");
            });
        }
    }
}
C:\Users\wasii\Documents\CodingProjects\ProjectConker\ClientApp>ng version
Your global Angular CLI version (8.3.5) is greater than your local
version (6.0.8). The local Angular CLI version is used.

To disable this warning use "ng config -g cli.warnings.versionMismatch false".

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.0.8
Node: 10.16.3
OS: win32 x64
Angular: 6.1.10
... animations, common, compiler, compiler-cli, core, forms
... http, platform-browser, platform-browser-dynamic
... platform-server, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.8
@angular-devkit/build-angular     0.6.8
@angular-devkit/build-optimizer   0.6.8
@angular-devkit/core              0.6.8
@angular-devkit/schematics        0.6.8
@angular/cli                      6.0.8
@angular/language-service         6.0.5
@ngtools/webpack                  6.0.8
@schematics/angular               0.6.8
@schematics/update                0.6.8
rxjs                              6.2.1
typescript                        2.7.2
webpack                           4.8.3
C:\Users\wasii\Documents\CodingProjects\ProjectConker>dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   2.2.402
 Commit:    c7f2f96116

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18362
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.402\

Host (useful for support):
  Version: 2.2.7
  Commit:  b1e29ae826

.NET Core SDKs installed:
  2.2.402 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

Upvotes: 3

Views: 4752

Answers (1)

itminus
itminus

Reputation: 25350

Let's review your ChatComponent Component, it initializes and tries to set up a connection to the Hub. Its ngOnInit() returns immediately after it binds an event handler for ReceiveMessage.

  ngOnInit() {
    ...
    this.hubConnection = new HubConnectionBuilder().withUrl(...).build();
    this.hubConnection
      .start()
      .then(() => console.log('Connection started!'))
      .catch(err => console.log('Error while establishing connection :('));
    this.hubConnection.on('ReceiveMessage', ...);
  }

Note it will take some time to connect to the Hub. If you try to send messages to the server before the connection is set up, it throws.

How to fix:

Create a thenable to ensure the connection has been set up.

  private thenable: Promise<void>

  ngOnInit() {
    ...
    this.hubConnection = new HubConnectionBuilder().withUrl(...).build();
    this.start()
    this.hubConnection.on('ReceiveMessage', ...);
  }

  private start() {
    this.thenable = this.hubConnection.start();
    this.thenable
      .then(() => console.log('Connection started!'))
      .catch(err => console.log('Error while establishing connection :('));
  }

Whenever you want to communicate with the Hub, invoke by thenable in promise-style. For example, your sendMessage() should be changed in the following way:

public sendMessage(): void {
    this.thenable.then(() =>{
        var today = new Date();
        this.messageData = ...
        this.hubConnection
           .invoke('SendToAll', this.author, this.messageData)
           .catch(err => console.error(err));
           this.message = '';
    });
}

[Update]

There're two other bugs in your code :

  1. You registered the SignalR middleware after the Spa middleware. Be aware UseSpa() should be used as a catch-all middleware. You should invoke UseSignalR() before UseSpa():

    app.UseSignalR(routes =>
    {
        routes.MapHub<ChatHub>("/api/chat");
    });
    
    app.UseMvc(routes =>
    {
        ...
    });
    
    app.UseSpa(spa =>
    {
        ...
    });
    
    app.UseSignalR(routes =>                        
    {                                               
        routes.MapHub<ChatHub>("/api/chat");  
    });                                             
    
  2. If you're connecting to the same server, avoid using hard-coded url:

    this.hubConnection = new HubConnectionBuilder().withUrl("http://localhost:5000/api/chat", {
          skipNegotiation: true,
          transport: HttpTransportType.WebSockets
        }).build();
    

    Otherwise, it fails when you're using a different port/protocol.

Upvotes: 3

Related Questions