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).Тем не менее, я не вижу ни одного доступного метода или свойства в прототипе соединения, позволяющего мне получить этот идентификатор, чтобы я мог передать его на сервер при запуске длинного задания.Может ли это быть причиной моей проблемы?Если это так, должен быть способ получения идентификатора (или установки его на стороне клиента).Что ты думаешь?