SignalR: уведомление о продлении длительной работы от веб-API ASP.NET Core до клиента Angular 7 - PullRequest
0 голосов
/ 28 февраля 2019

EDITED : см. Внизу

Я новичок в SignalR и пытаюсь реализовать с его помощью простой сценарий с клиентом Angular7, использующим эту библиотеку, и веб-API ASP.NET Core.,Все, что мне нужно, это использовать SignalR для уведомления клиента о ходе выполнения некоторых длительных операций в методах контроллеров API.

После нескольких попыток я попал в точку, где, очевидно, установлено соединение,но затем, когда запускается длинная задача и отправляется сообщение, мой клиент, похоже, ничего не получает, и в веб-сокетах не появляется трафик (Chrome F12 - Network - WS).

Я публикую здесь подробности, которыеможет также быть полезным для других новичков ( полный исходный код в https://1drv.ms/u/s!AsHCfliT740PkZh4cHY3r7I8f-VQiQ). Возможно, я просто делаю какую-то очевидную ошибку, но в документации и поиске я не смог найти фрагмент кода, существенно отличающийсяот моего. Может кто-нибудь дать подсказку?

Моя начальная точка на стороне сервера была https://msdn.microsoft.com/en-us/magazine/mt846469.aspx, плюс документы на https://docs.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2. Я пытался создать фиктивное экспериментальное решение с этим.

Далее следуют мои фрагменты кода в форме рецепта.

(A) Сторона сервера

1.создать новую ASОсновное веб-API-приложение P.NET.Нет аутентификации или Docker, просто чтобы сохранить его минимальным.

2.Добавьте пакет NuGet Microsoft.AspNetCore.SignalR.

3.at Startup.cs, ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    // CORS
    services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
    {
        builder.AllowAnyMethod()
            .AllowAnyHeader()
            // https://github.com/aspnet/SignalR/issues/2110 for AllowCredentials
            .AllowCredentials()
            .WithOrigins("http://localhost:4200");
    }));
    // SignalR
    services.AddSignalR();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

и соответствующий Configure метод:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    // CORS
    app.UseCors("CorsPolicy");
    // SignalR: add to the API at route "/progress"
    app.UseSignalR(routes =>
    {
        routes.MapHub<ProgressHub>("/progress");
    });

    app.UseHttpsRedirection();
    app.UseMvc();
}

4.add ProgressHub класс, который просто наследуется от Hub:

public class ProgressHub : Hub
{
}

5.add TaskController с методом запуска какой-нибудь длительной операции:

[Route("api/task")]
[ApiController]
public class TaskController : ControllerBase
{
    private readonly IHubContext<ProgressHub> _progressHubContext;

    public TaskController(IHubContext<ProgressHub> progressHubContext)
    {
        _progressHubContext = progressHubContext;
    }

    [HttpGet("lengthy")]
    public async Task<IActionResult> Lengthy([Bind(Prefix = "id")] string connectionId)
    {
        await _progressHubContext
            .Clients
            .Client(connectionId)
            .SendAsync("taskStarted");

        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(500);
            Debug.WriteLine($"progress={i}");
            await _progressHubContext
                .Clients
                .Client(connectionId)
                .SendAsync("taskProgressChanged", i);
        }

        await _progressHubContext
            .Clients
            .Client(connectionId)
            .SendAsync("taskEnded");

        return Ok();
    }
}

(B) на стороне клиента

1.создайте новое приложение CLI Angular7 (без маршрутизации, просто длябудь проще).

2. npm install @aspnet/signalr --save.

3.my app.component код:

import { Component, OnInit } from '@angular/core';
import { HubConnectionBuilder, HubConnection, LogLevel } from '@aspnet/signalr';
import { TaskService } from './services/task.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  private _connection: HubConnection;

  public messages: string[];

  constructor(private _taskService: TaskService) {
    this.messages = [];
  }

  ngOnInit(): void {
    // https://codingblast.com/asp-net-core-signalr-chat-angular/
    this._connection = new HubConnectionBuilder()
      .configureLogging(LogLevel.Debug)
      .withUrl("http://localhost:44348/signalr/progress")
      .build();

    this._connection.on("taskStarted", data => {
      console.log(data);
    });
    this._connection.on("taskProgressChanged", data => {
      console.log(data);
      this.messages.push(data);
    });
    this._connection.on("taskEnded", data => {
      console.log(data);
    });

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

  public startJob() {
    this.messages = [];
    this._taskService.startJob('zeus').subscribe(
      () => {
        console.log('Started');
      },
      error => {
        console.error(error);
      }
    );
  }
}

Его минималистичный HTML-шаблон:

<h2>Test</h2>
<button type="button" (click)="startJob()">start</button>
<div>
  <p *ngFor="let m of messages">{{m}}</p>
</div>

Служба задач в приведенном выше коде является просто оболочкой для функции, которая вызывает HttpClient get<any>('https://localhost:44348/api/task/lengthy?id=' + id).


EDIT 1

После еще нескольких экспериментовЯ пришел с этими изменениями:

  • используйте .withUrl('https://localhost:44348/progress') как предложено.Кажется, что теперь он больше не запускает 404. Обратите внимание на изменение: я заменил http на https.

  • , не делая метод API асинхронным, так как кажется, что await не требуются (т.е. установите тип возвращаемого значения IActionResult и удалите async и await).

С этими изменениями Теперь я вижу ожидаемоепротоколировать сообщения на стороне клиента (Chrome F12).Глядя на них, кажется, что соединение привязывается к сгенерированному идентификатору k2Swgcy31gjumKtTWSlMLw:

Utils.js:214 [2019-02-28T20:11:48.978Z] Debug: Starting HubConnection.
Utils.js:214 [2019-02-28T20:11:48.987Z] Debug: Starting connection with transfer format 'Text'.
Utils.js:214 [2019-02-28T20:11:48.988Z] Debug: Sending negotiation request: https://localhost:44348/progress/negotiate.
core.js:16828 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Utils.js:214 [2019-02-28T20:11:49.237Z] Debug: Selecting transport 'WebSockets'.
Utils.js:210 [2019-02-28T20:11:49.377Z] Information: WebSocket connected to wss://localhost:44348/progress?id=k2Swgcy31gjumKtTWSlMLw.
Utils.js:214 [2019-02-28T20:11:49.378Z] Debug: Sending handshake request.
Utils.js:210 [2019-02-28T20:11:49.380Z] Information: Using HubProtocol 'json'.
Utils.js:214 [2019-02-28T20:11:49.533Z] Debug: Server handshake complete.
app.component.ts:39 Connection started!
app.component.ts:47 Task service succeeded

Таким образом, может случиться так, что я не получу уведомление, потому что мой идентификатор клиента не соответствует назначенному идентификаторуот SignalR (из приведенной выше статьи у меня сложилось впечатление, что я должен был предоставить ID, учитывая, что это аргумент контроллера API).Тем не менее, я не вижу ни одного доступного метода или свойства в прототипе соединения, позволяющего мне получить этот идентификатор, чтобы я мог передать его на сервер при запуске длинного задания.Может ли это быть причиной моей проблемы?Если это так, должен быть способ получения идентификатора (или установки его на стороне клиента).Что ты думаешь?

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Кажется, я наконец нашел это.Вероятно, проблема была вызвана неправильным идентификатором, поэтому я начал искать решение.Сообщение (https://github.com/aspnet/SignalR/issues/2200) подсказало мне использование групп, что кажется рекомендуемым решением в этих случаях. Итак, я изменил свой концентратор, чтобы он автоматически назначал текущий идентификатор соединения для группы "progress":

public sealed class ProgressHub : Hub
{
    public const string GROUP_NAME = "progress";

    public override Task OnConnectedAsync()
    {
        // https://github.com/aspnet/SignalR/issues/2200
        // https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups
        return Groups.AddToGroupAsync(Context.ConnectionId, "progress");
    }
}

Теперь мой метод API-контроллера:

[HttpGet("lengthy")]
public async Task<IActionResult> Lengthy()
{
    await _progressHubContext
        .Clients
        .Group(ProgressHub.GROUP_NAME)
        .SendAsync("taskStarted");
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(200);
        Debug.WriteLine($"progress={i + 1}");
        await _progressHubContext
            .Clients
            .Group(ProgressHub.GROUP_NAME)
            .SendAsync("taskProgressChanged", i + 1);
    }
    await _progressHubContext
        .Clients
        .Group(ProgressHub.GROUP_NAME)
        .SendAsync("taskEnded");

    return Ok();
}

И, конечно, я соответствующим образом обновил код клиента, чтобы ему больше не приходилось отправлять идентификатор при вызове метода API.

Полный демонстрационный репозиторий доступен по адресу https://github.com/Myrmex/signalr-notify-progress.

0 голосов
/ 28 февраля 2019

Вы устанавливаете маршрут для концентратора как /progress, но затем вы пытаетесь подключиться к /signalr/progress, который будет 404. Если вы откроете консоль разработчика, у вас должна быть ошибка соединения тамговорю вам столько же.

...