Функция, подобная сигналу, не работает

#asp.net #angular #signalr #signalr-hub

Вопрос:

Я создал функцию «Нравится», чтобы пользователю могла понравиться запись в моем приложении. Я читал о SignalR и пытался использовать его, чтобы количество лайков могло автоматически обновляться в режиме реального времени всякий раз, когда пользователю нравится/не нравится сообщение. Однако это не работает, но я также не получаю никаких ошибок. Единственное сообщение в моей консоли после нажатия кнопки «Мне нравится» — это:

 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 amp;amp; 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:

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

Это угловой код в моем компоненте открытки, где кнопка «Нравится» находится:

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

Это компонент PostList, в котором все записи:

 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() или верны ли заданные параметры. Я также добавил LikeHub в свои конечные точки в классе Startup.cs.

Комментарии:

1. Не могли бы вы добавить console.log(numOfLikes) в обработчик on и посмотреть, что вы получаете? Что значит «не работает»? Обновляется ли база данных? Также, почему вы обновляете идентификатор пользователя с идентификатором понравившегося пользователя?

2. База данных не обновляется. Идентификатор пользователя-это идентификатор пользователя, которому понравилось сообщение, а postID-идентификатор понравившегося сообщения. Данные не отображаются, если я пишу console.log(numOfLikes), точка останова даже не достигает этой точки. Может быть, это потому, что я использовал тогда()?

3. О, действительно, даже не заметил 😀 Попробуйте переместить это .на прослушивателе вверх, после того как вы сделаете HubConnectionBuilder().**.build()

4. Пользователь и параметр post, переданные методу createLike (), отправляются правильно. Я обновил код и отделил connectionHub.on() от connectionHub.start(). Теперь точка останова достигает connectionHub.on(), но она все еще не достигает conole.log(numOfLikes)

5. Я перешел .дальше после сборки, но результат все тот же. Может быть, мне нужно использовать клиентов? Вызывающий абонент. SendAsync() в моем бэкэнде?

Ответ №1:

Я бы настоятельно рекомендовал начать с тщательного переписывания этого примера, это действительно должно помочь лучше понять концепции https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-5.0amp;tabs=visual-studio

Итак, в этом коде есть пара проблем. Подобный созданию метод PostsService должен отвечать только за вызов post через существующее соединение. Весь остальной код, ответственный за запуск соединения, должен быть уже выполнен до этого момента. https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-5.0#connect-to-a-hub

Итак, если вы не знакомы с реактивным программированием и rxjs, я бы рекомендовал вам добавить какой-нибудь метод в свой PostsService, например ConnectHubs(): Пообещайте подготовить ваши соединения-концентраторы, прежде чем фактически вызывать некоторые методы-концентраторы.

 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, чтобы избежать ненужных нескольких активных подключений к одному и тому же концентратору от одного клиента. Или вы можете просто вызвать этот метод инициализации в своем базовом компоненте, например в компоненте приложения, в этом случае вам не нужно будет останавливать соединение в ngOnDestroy, но вам нужно будет убедиться, что ваш пользователь вошел в систему до установления соединения. Возможно, вы сможете найти какой-нибудь компонент, в котором он будет уничтожен в редких случаях, но он всегда будет открыт после входа в систему

Если вы знаете rxjs, вы можете добавить некоторые поля объекта поведения, такие как

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

и тогда вместо того, чтобы возвращать обещание при запуске соединения, вы можете просто добавить что-то вроде isConnectedSubject.next(true);
и в вашем методе отключения вы можете добавить isConnectedSubject.next(ложь);
В вашем компоненте вы можете просто отключить кнопку «Нравится», пока концентратор не подключен таким образом:

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

Чтобы ваши элементы управления знали об изменениях в этом центре, если вы знаете RxJS, вы можете добавить некоторое поле темы с его наблюдаемым полем и публиковать события каждый раз при получении нового сообщения. Или вы можете сделать это проще с помощью излучателя событий 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 это было бы то же самое, но вы будете использовать тему вместо эмитента, не забывайте об отмене подписки, чтобы избежать неожиданного поведения и утечек.

Комментарии:

1. Большое спасибо!! Я внимательно прочитаю это и постараюсь обновить свой код после этих примеров, чтобы он соответствовал моим потребностям.

2. Итак, я попытался реализовать ваши предложения и обновил код Angular в своем вопросе тем, что я получил. Что-то явно не так, так как мои сообщения больше не загружаются, и я получаю эту ошибку: ERROR TypeError: Cannot read property 'subscribe' of undefined в OnInit от PostListComponent. Кнопка «Нравится» даже не появится, если пользователь не вошел в систему, поэтому мне не нужно ее отключать. Я думаю, что, возможно, код в OnInit списка сообщений не совсем правильный, но все ли остальное кажется нормальным?

3. @Alessia хорошо, во-первых, вы не создали экземпляр источника событий, вероятно, я забыл добавить этот код, вам нужно было сначала создать его, чтобы использовать его следующим образом: likeMessageReceive = новый источник событий<{ numOfLikes, postID, идентификатор пользователя }>();

4. @Alessia, и вы все еще создаете концентратор вместо вызова метода. Вам нужно было добавить connectHubs() в soe ngOnInit, например, в список, а затем просто выполнить вызовы в понравившемся методе, обновит ответ, код, который вы размещаете, должен был быть внутри подписки

5. drive.google.com/file/d/15mMiLf-u0xBVBv_rjUY9Hua25g7p0_Yd/…