You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SignalR Core与.NET Core:如何实现契约校验并捕获参数错误?

SignalR TypeScript客户端契约校验与错误抛出方案

1. 生成强类型客户端(根源解决)

SignalR支持从.NET服务端Hub代码直接生成TypeScript强类型客户端,编译阶段就能拦截方法名错误、参数不匹配问题,完全不用等到运行时调试。

操作步骤:

  • 给服务端项目安装NuGet包 Microsoft.AspNetCore.SignalR.TypeScript
  • 在项目的.csproj文件中添加生成配置,指定Hub文件路径和输出的TypeScript文件位置:
<ItemGroup>
  <SignalRClient Include="Hubs/**/*.cs" />
</ItemGroup>
<Target Name="GenerateSignalRClient" AfterTargets="Build">
  <Exec Command="dotnet tool restore" />
  <Exec Command="dotnet signalr generate client -i $(ProjectDir)/Hubs/ChatHub.cs -o $(ProjectDir)/../Client/src/signalr-hub.ts" />
</Target>
  • 编译服务端项目后,会自动生成包含所有Hub方法强类型定义的TS文件,调用时只要方法名写错、参数类型/数量不对,VS Code会直接标红,编译也通不过。

2. 手动封装调用方法,添加前置校验

如果暂时不想生成强类型代码,自己封装一层invoke方法,提前做方法名和参数校验:

import { HubConnection } from "@microsoft/signalr";

class TypedHub {
  private conn: HubConnection;
  // 维护服务端真实存在的方法及所需参数数量
  private validMethods: Record<string, number> = {
    "SendMessage": 2,
    "JoinGroup": 1
  };

  constructor(connection: HubConnection) {
    this.conn = connection;
  }

  async invoke<T>(methodName: string, ...args: any[]): Promise<T> {
    // 校验方法是否存在
    if (!this.validMethods[methodName]) {
      throw new Error(`SignalR方法「${methodName}」不存在`);
    }
    // 校验参数数量
    if (args.length !== this.validMethods[methodName]) {
      throw new Error(`方法「${methodName}」需要${this.validMethods[methodName]}个参数,实际传了${args.length}个`);
    }
    // 调用原方法并增强错误信息
    try {
      return await this.conn.invoke(methodName, ...args);
    } catch (err) {
      throw new Error(`调用「${methodName}」失败: ${(err as Error).message}`);
    }
  }
}

// 使用示例
// const rawConn = new HubConnectionBuilder().withUrl("/chatHub").build();
// const hub = new TypedHub(rawConn);
// hub.invoke("SendMessage", "test") // 参数数量不对,直接抛出错误

3. 服务端主动校验并抛出异常

在服务端Hub里做参数校验,同时处理未定义的方法调用,主动抛出HubException,客户端就能通过catch捕获到错误:

服务端C#代码:

using Microsoft.AspNetCore.SignalR;
using System.Reflection;

public class ChatHub : Hub
{
    private static readonly HashSet<string> _validMethods = typeof(ChatHub)
        .GetMethods(BindingFlags.Public | BindingFlags.Instance)
        .Where(m => m.ReturnType.IsAssignableTo(typeof(Task)))
        .Select(m => m.Name)
        .ToHashSet();

    public async Task SendMessage(string user, string message)
    {
        if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(message))
        {
            throw new HubException("用户名和消息不能为空");
        }
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // 拦截所有方法调用,校验方法是否存在
    public override Task OnInvokeAsync(string methodName, object[] args, CancellationToken cancellationToken)
    {
        if (!_validMethods.Contains(methodName))
        {
            throw new HubException($"方法「${methodName}」不存在于服务端");
        }
        return base.OnInvokeAsync(methodName, args, cancellationToken);
    }
}

客户端调用示例:

try {
  await connection.invoke("aaaa"); // 服务端抛出异常,客户端catch能捕获
} catch (err) {
  console.error("调用失败:", err);
}

4. 用JSON Schema做参数结构校验

如果需要更复杂的参数结构校验,可以结合JSON Schema工具(比如Ajv)在客户端调用前验证参数:

import Ajv from "ajv";

const ajv = new Ajv();
// 定义SendMessage的参数校验规则
const sendMessageSchema = {
  type: "array",
  items: [
    { type: "string", minLength: 1 },
    { type: "string", minLength: 1 }
  ]
};

const validateSendMessage = ajv.compile(sendMessageSchema);

async function sendMessage(user: string, message: string) {
  const args = [user, message];
  if (!validateSendMessage(args)) {
    throw new Error("参数不符合要求: " + JSON.stringify(validateSendMessage.errors));
  }
  await connection.invoke("SendMessage", ...args);
}

内容的提问来源于stack exchange,提问作者Benyamin Limanto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:24:50