首页 > 解决方案 > 类似 SignalR 的功能不起作用

问题描述

我创建了一个类似的功能,以便用户可以喜欢我的应用程序中的帖子。我已经阅读过 SignalR 并尝试使用它,以便在用户喜欢/不喜欢帖子时实时自动更新喜欢的数量。但是,它不起作用,但我也没有收到任何错误。按下like按钮后,我控制台中的唯一消息是:

Information: WebSocket connected to wss://localhost:44351/hubs/like?access_token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJnZW9yZ2lhIiwicm9sZSI6WyJNZW1iZXIiLCJBZG1pbiJdLCJuYmYiOjE2MTk0NjQ3NzAsImV4cCI6MTYyMDA2OTU3MCwiaWF0IjoxNjE5NDY0NzcwfQ.1Bwf_Y2QJP_VjRUXaBeqz5sueV6oTIpVlOLU4kOEmLf2Y_hfxJbc5_f4yksY9R45YGz0qPWw-rc10I7pobFJYQ

这是我的 .net 代码:

 public class LikeHub : Hub
  {
        private readonly IPostRepository _postRepository;
        private readonly DataContext _context;
        private readonly IUserRepository _userRepository;

        public LikeHub(IPostRepository postRepository, DataContext context, IUserRepository userRepository)
        {
            _postRepository = postRepository;
            _context = context;
            _userRepository = userRepository;
        }

        public async Task SetLike(int userId, int postId)
        {
            Like l = new Like();

            Like temp = _context.Likes.Where(x => x.PostId == postId && x.UserId == userId).FirstOrDefault();

            if(temp != null)
            {
                _context.Likes.Remove(temp);
            } else
            {
                _context.Likes.Add(l);

                l.UserId = userId;
                l.PostId = postId;
            }

            await _context.SaveChangesAsync();

            int numOfLikes = _context.Likes.Where(x => x.PostId == postId).Count();

            await Clients.All.SendAsync("ReceiveMessage", numOfLikes, postId, userId);

        }
   }

这是我在 PostsService 中的 Angular 代码:

export class PostsService {

  hubUrl = environment.hubUrl;
  private hubConnection: HubConnection;
  likeMessageReceive: EventEmitter<{ numOfLikes: number, postId: number, userId: number }> = new EventEmitter<{ numOfLikes:number, postId: number, userId: number }>();


  constructor(private http: HttpClient) {}

   connectHubs(user: User) { 
      this.hubConnection = new HubConnectionBuilder()
      .withUrl(this.hubUrl + 'like', { accessTokenFactory: () => user.token, 
      skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets })
      .build();
  
      return  this.hubConnection.start()
                 .then(() => {
                     this.hubConnection.on('ReceiveMessage', (numOfLikes, postId, userId) => {
                       this.likeMessageReceive.emit({ numOfLikes, postId, userId });
                     });
                 })
                 .catch(error => console.log(error)); 
  }
  
  setLike(userId: number, postId: number) {
       this.hubConnection.invoke('SetLike', userId, postId);
  }
  
  closeHubConnections() {
      this.hubConnection.stop();
  }
}

这是我的 PostCardComponent 中的 Angular 代码,其中的like 按钮是:

export class PostCardComponent implements OnInit {

 @Input() post: Post;
  likesSubscription: Subscription;

 
  constructor(private postService:PostsService,public accountService:AccountService)
            { this.Login$ = this.accountService.Logged;}
ngOnInit(): void {

    this.likesSubscription = this.postService.likeMessageReceive.subscribe(result =>{
      if (result.postId === this.post.id) {
          this.post.likes.length = result.numOfLikes;
      }
  })
}

liked(post: Post) {
    const user: User = JSON.parse(localStorage.getItem('user'));
    this.postService.setLike(user.id, post.id);
  }
}

这是 PostListComponent,其中所有的帖子都是:

export class PostListComponent implements OnInit {

  posts: Post[];
  post: Post;
  likesSubscription: Subscription;
  localUser: User;


  constructor(private postService: PostsService) {}

ngOnInit(): void {
     this.postService.connectHubs(this.localUser);
  }

}

我不知道里面的代码this.hubConnection.on()是否正确,或者给定的参数是否正确。我还在 Startup.cs 类的端点中添加了 LikeHub。

标签: asp.netangularsignalrsignalr-hub

解决方案


我强烈建议从仔细重写此示例开始,这确实有助于更好地理解概念https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-5.0&tabs=visual -工作室

所以,这段代码有几个问题。PostsService 的 createLike 方法应该只负责通过现有连接发布调用。直到此刻,所有其他负责连接启动的代码都应该已经执行。 https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-5.0#connect-to-a-hub

因此,如果您不熟悉响应式编程和 rxjs,我建议您在 PostsService 中添加一些方法,例如 ConnectHubs(): Promise 在实际调用一些集线器方法之前准备集线器连接。

connectHubs() { 
    this.hubConnection = new HubConnectionBuilder()
    .withUrl(this.hubUrl + 'like', { accessTokenFactory: () => user.token, 
    skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets })
    .build();

    return  this.hubConnection.start()
               .then(() => {
                   this.hubConnection.on('ReceiveMessage', (numOfLikes, postId, userId) => {
                       // some logic to handle invocation
                   });
               })
               .catch(error => console.log(error)); 
}

setLike(userId: number, postId: number) {
     this.hubConnection.invoke('SetLike', userId, postId);
}

closeHubConnections() {
    this.hubConnection.stop();
}

然后在包含多个帖子的组件中,除了从 api 请求所有帖子之外,您还需要调用此 connectHubs 方法并等待此承诺显示所有帖子,以避免在可能之前设置喜欢。在这种情况下,最好也停止 ngOnDestroy 中的连接,以避免从一个客户端到同一个集线器的不必要的多个活动连接。或者你可以在你的基础组件中调用这个init方法,比如app组件,在这种情况下你不需要在ngOnDestroy中停止连接,但是你需要确保你的用户在建立连接之前已经登录。也许你可以找到一些很少会被销毁的组件,但它总是会在登录后打开

如果您知道 rxjs,您可以添加一些 BehaviorSubject 字段,例如

private isConnectedSubject = new BehaviorSubject(false);
isConnected$ = isConnectedSubject.asObservable();

然后,您可以添加类似 isConnectedSubject.next(true); 之类的东西,而不是在连接开始时返回承诺;并且在您的断开连接方法中,您可以添加 isConnectedSubject.next(false); 在您的组件中,您可以在集线器未以这种方式连接时禁用类似按钮:

<button [disabled]="!(postService.isConnected$ | async)" ...>

为了让你的控件知道这个中心的变化,如果你知道 RxJS,你可以添加一些带有 Observable 字段的主题字段,并在每次收到新消息时发布事件。或者您可以使用事件发射器https://angular.io/api/core/EventEmitter使其更简单,如下所示

服务:

likeMessageReceive = new EventEmitter<{ numOfLikes, postId, userId }>();

connectHubs() {
   ....
   this.hubConnection.on('ReceiveMessage', (numOfLikes, postId, userId) => {
     likeMessageReceive.emit({ numOfLikes, postId, userId })
     console.log(numOfLikes);
   })
   ....

帖子组件:

likesSubscription: Subscription;

ngOnInit() {
    this.likesSubscription = this.postsService.likeMessageReceive.subscribe(result =>{
        if (result.postId === this.post.id) {
            this.post.likes.length = numOfLikes;
        }
    })
}

liked(post: Post) {
    const user: User = JSON.parse(localStorage.getItem('user'));
    this.postService.setLike(user.id, post.id);
}

ngOnDestroy() {
    if (this.likesSubscription) {
        this.likesSubscription.unsubscribe();
    }
}

使用 rxjs 会非常相似,但是您将使用 Subject 而不是发射器,不要忘记取消订阅以避免意外行为和泄漏。


推荐阅读