C#通过API接口返回流式响应内容---SignalR方式
1、背景
在上两篇《C#通过API接口返回流式响应内容—分块编码方式》和《C#通过API接口返回流式响应内容—SSE方式》实现了流式响应的内容。
上面的这两个主要是通过HTTP
的一些功能,除了这些之外,还有WebSocket
的方式。C#
中的WebSocket
的有比较多的方案:SignalR
、Fleck
、FreeIM
、WebSocket-Sharp
、NetCoreServer
等。其中SignalR
是微软官网推荐的、Fleck
是WebSocket
的C#
的开源项目,并且用的比较多,其他三个(FreeIM、WebSocket-Sharp、NetCoreServer
)本人未使用,但原理相同,大家可以网上找找方案。
本篇主要是使用SignalR
的方案
2、效果
3、具体代码
3.1、创建一个Hub
创建一个Hub服务,本质上它是一个服务器,用作一个服务转发。它是通过继承Hub实现。
using Microsoft.AspNetCore.SignalR;
namespace SignalRHub.Hubs
{
public class DeepSeekChat:Hub
{
public async Task SendDeepSeekStream(string msg)
{
await Clients.All.SendAsync("ReceiveMsg", msg); //非常简单的方法,就是直接推送数据
}
}
}
3.2、配置Hub
在program.cs
中配置Hub
using Microsoft.AspNetCore.SignalR;
using SignalRHub.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSignalR(); //【添加SignalR服务】
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.MapHub<DeepSeekChat>("/dschat"); //【配置Endpoint】
app.Run();
3.3、配置推送服务
当我们实现了DeepSeekChat时,我们如何通过服务器,将数据推送到终端呢?因此我们需对外的API接口,当我们调用API的接口时,API方法内部调用signalr的实例,并调用推送方法,从而将我们的内容推送到终端。
在apicontroller中或者program.cs
在apicontroller中的方法如下,【这个是一个Get方法】
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SignalRHub.Hubs;
namespace SignalRHub.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ViaAPIController : ControllerBase
{
private readonly IHubContext<DeepSeekChat> chatHub;
public ViaAPIController(IHubContext<DeepSeekChat> chatHub)
{
this.chatHub = chatHub;
}
[HttpGet("sendmsg")]
public async Task SendMsg()
{
//模拟deepseek的对话内容
var phrases = new string[] { "你好!", "我是", "北京清华长庚医院", "信息管理部的", "郑林" };
foreach (var item in phrases)
{
await chatHub.Clients.All.SendAsync("ReceiveMsg", item);
await Task.Delay(1000);
}
}
}
}
在program.cs的代码如下:
//上文中的其他配置略过
app.UseAuthorization();
app.MapControllers();
app.MapHub<DeepSeekChat>("/dschat");
//【这儿就是直接配置的,两者的效果是一样的,都是发送数据】
app.MapPost("sendNotification", async (string msg, IHubContext<DeepSeekChat> context) => {
var phrases = new string[] { "你好!", "我是", "北京清华长庚医院", "信息管理部的", "郑林" };
foreach (var item in phrases)
{
await context.Clients.All.SendAsync("ReceiveMsg", item);
await Task.Delay(1000);
}
});
app.Run();
两者的效果是一致的
3.4、前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignalR Client</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.18/signalr.min.js"></script>
</head>
<body>
<h3>流式响应</h3>
<div id="stockPrices"></div>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5071/dschat")
.build();
connection.on("ReceiveMsg", (msg) => {
const stockPricesDiv = document.getElementById("stockPrices");
stockPricesDiv.innerHTML+= msg +' ';
});
connection.start()
.then(() => {
console.log("SignalR connection started.");
})
.catch(err => {
console.error("Error connecting to SignalR: ", err);
});
window.addEventListener("beforeunload", () => {
connection.stop().then(() => {
console.log("SignalR connection stopped.");
}).catch(err => {
console.error("Error stopping SignalR connection: ", err);
});
});
</script>
</body>
</html>
比较简单
3.5、配置Cors
这样运行的话,浏览器是报错的,因为有Cors限制,因此需要在program.cs中配置
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
//设置CORS
builder.Services.AddCors(options => {
options.AddPolicy(name: MyAllowSpecificOrigins,
policy => {
policy.SetIsOriginAllowed(t=>true) //默认全部访问
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}
);
});
builder.Services.AddControllers();
//其他配置
//app.UseHttpsRedirection();
app.UseCors(MyAllowSpecificOrigins); //配置Cors
app.UseAuthorization();
app.MapControllers();
3.6、完整的program配置
完整的配置如下:
using Microsoft.AspNetCore.SignalR;
using SignalRHub.BackgroundServices;
using SignalRHub.Hubs;
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
//设置CORS
builder.Services.AddCors(options => {
options.AddPolicy(name: MyAllowSpecificOrigins,
policy => {
policy.SetIsOriginAllowed(t=>true) //
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}
);
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSignalR();//添加SignalR服务
//builder.Services.AddHostedService<Worker>(); //添加一个后台服务
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
app.UseCors(MyAllowSpecificOrigins); //配置Cors
app.UseAuthorization();
app.MapControllers();
app.MapHub<DeepSeekChat>("/dschat");
app.MapPost("sendNotification", async (string msg, IHubContext<DeepSeekChat> context) => {
var phrases = new string[] { "你好!", "我是", "北京清华长庚医院", "信息管理部的", "郑林" };
foreach (var item in phrases)
{
await context.Clients.All.SendAsync("ReceiveMsg", item);
await Task.Delay(1000);
}
});
app.Run();
4、原理
这个是一个原理图,只不过本文的样例是单向的
5、参考资料
1、SignalR微软官网
2、ASP.NET Core 配置跨域(CORS)
3、3个WebSocket的.Net开源项目