.NET core angular 项目连接未连接状态无法发送数据?

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

我是 运行 windows VScode 中的一个 .NET 核心 angular 项目,我正在尝试让 signalR 工作,但不幸的是,事情并没有发生当我用相同的代码将我的 angular 和 .net 核心项目分开(web API 和 angular 项目分开时,我的 way.Before 似乎就没了,这个问题没有发生。当我切换到 .NET 核心 angular 项目时,它正在发生。提前致谢。

当我尝试向服务器发送消息时,出现以下错误

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 客户端组件


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 客户端视图

 <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.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

让我们检查一下您的 ChatComponent 组件,它会初始化并尝试建立与集线器的连接。它 ngOnInit() returns 在绑定 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', ...);
  }

请注意,连接到集线器需要一些时间。如果您尝试在建立连接之前向服务器发送消息,它会抛出。

如何修复:

创建thenable以确保连接已建立。

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

每当您想与 Hub 通信时,通过 thenable 以承诺方式调用。例如,您的 sendMessage() 应按以下方式更改:

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

[更新]

您的代码中还有另外两个错误:

  1. 您在 Spa 中间件之后注册了 SignalR 中间件。请注意 UseSpa() 应该用作包罗万象的中间件。您应该在 UseSpa():

    之前调用 UseSignalR()
    app.UseSignalR(routes =>
    {
        routes.MapHub<ChatHub>("/api/chat");
    });
    
    app.UseMvc(routes =>
    {
        ...
    });
    
    app.UseSpa(spa =>
    {
        ...
    });
    
    app.UseSignalR(routes =>                        
    {                                               
        routes.MapHub<ChatHub>("/api/chat");  
    });                                             
    
  2. 如果您连接到同一台服务器,请避免使用硬编码 url:

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

    否则,当您使用不同的 port/protocol.

  3. 时它会失败