编辑:见底部
我是SignalR的新手,并试图使用这个库和ASP.NET Core web API使用Angular7客户端实现一个简单的场景。我所需要的只是使用SignalR来通知客户端API控制器方法中一些冗长操作的进度。
经过多次尝试,我得到了一个显然建立连接的点,但是当长任务开始运行并发送消息时,我的客户似乎没有收到任何东西,也没有任何流量出现在网络套接字中(Chrome F12 - 网络 - WS)。
我在这里发布详细信息,这可能对其他新手有用(https://1drv.ms/u/s!AsHCfliT740PkZh4cHY3r7I8f-VQiQ的完整源代码)。可能我只是犯了一些明显的错误,但在文档和Google搜索中我找不到与我的本质不同的代码片段。任何人都可以提示吗?
我的服务器端的起点是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.创建一个新的ASP.NET核心Web API应用程序。没有身份验证或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.添加一个ProgressHub
类,它来自Hub:
public class ProgressHub : Hub
{
}
5.添加一个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.创建一个新的Angular7 CLI应用程序(没有路由,只是为了保持简单)。
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)
的函数的包装器。
经过一些实验,我带来了这些变化:
.withUrl('https://localhost:44348/progress')
。似乎现在它不再触发404.注意改变:我用http
取代了https
。await
(即将返回类型设置为IActionResult
并删除async
和await
)。通过这些更改,我现在可以在客户端看到预期的日志消息(Chrome F12)。看着它们,似乎连接绑定到生成的ID 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
因此,我可能会收到通知,因为我的客户ID与SignalR分配的ID不匹配(从上面引用的论文中我得到的印象是我有义务提供ID,因为它是一个参数API控制器)。然而,我在连接原型中看不到任何可用的方法或属性,允许我检索此ID,以便在启动冗长的作业时将其传递给服务器。这可能是我的问题的原因吗?如果是这样,应该有一种方法来获取ID(或从客户端设置它)。你怎么看?
您将集线器的路由设置为/progress
,但之后您尝试连接到/signalr/progress
,这将是404.如果您打开开发人员控制台,那么您应该有一个连接错误告诉您。
好像我终于找到了它。问题可能是由错误的ID引起的,所以我开始寻找解决方案。一篇文章(https://github.com/aspnet/SignalR/issues/2200)引导我使用群组,这似乎是这些案例的推荐解决方案。所以,我更改了我的集线器,以便它自动将当前连接ID分配给“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方法时不再需要发送ID。
https://github.com/Myrmex/signalr-notify-progress提供完整的演示存储库。