如何从TypeScript生成的DTO获取ServiceStack原始路由?
问题背景
生产环境中出现了一个问题:接口返回的JSON响应与生成的TypeScript DTO定义的预期模型不匹配,但HTTP状态码却返回成功。我们计划借助ServiceStack OpenApiFeature提供的OpenAPI文档(/openapi端点)中的JSON Schema元数据,对JSON响应进行验证。验证需要获取三个关键变量:
- HTTP方法:已明确指定
- HTTP响应状态码:可通过
client.responseFilter获取 - 路由路径:这是当前的核心难题
以示例DTO为例:
[Route("/api20/data", "GET")] public class GetData20 : IReturns<Data> { public string Id { get; set; } }
常规请求方式:
const data = await client.get(new GetData20({ id }));
我们需要获取该请求对应的RouteAttribute中定义的原始路由,但JsonServiceClient本身没有提供获取路由的API,它默认使用/json/reply/{DTO构造函数名}的路由,且生成的TypeScript DTO仅以注释形式包含路由信息。
最佳实践方案
1. 利用TypeScriptGenerator自动注入路由元数据(推荐)
通过配置ServiceStack的TypeScriptGenerator,在生成DTO时自动为每个类添加静态属性,存储路由和HTTP方法信息,无需手动维护。
.NET端配置示例
var generator = new TypeScriptGenerator(new MetadataTypesConfig { AddTypeScriptCode = (type, code) => { var routeAttr = type.FirstAttribute<RouteAttribute>(); if (routeAttr != null) { code.AddLine($"static route = \"{routeAttr.Path}\";"); code.AddLine($"static method = \"{routeAttr.Verb}\";"); } return code; } });
生成的TypeScript DTO示例
export class GetData20 { static route = "/api20/data"; static method = "GET"; public id?: string; // ...其他属性 }
客户端使用方式
发起请求时直接通过类的静态属性获取路由:
const dto = new GetData20({ id }); const route = GetData20.route; const method = GetData20.method; const data = await client.get(dto); // 后续验证时直接使用route和method
2. 启用真实TypeScript装饰器生成
配置TypeScriptGenerator生成真实的@Route装饰器(而非注释),通过装饰器元数据提取路由信息。
.NET端配置示例
var generator = new TypeScriptGenerator(new MetadataTypesConfig { GenerateDecorators = true, DecoratorsNamespace = "ServiceStack" });
生成的TypeScript DTO示例
import { Route } from 'ServiceStack'; @Route("/api20/data", "GET") export class GetData20 { public id?: string; // ...其他属性 }
客户端提取路由信息
可以通过装饰器的元数据存储(需自行实现或借助装饰器库)来获取路由和方法:
// 假设装饰器会将元数据存储在类的__route__属性上 const routeInfo = (GetData20 as any).__route__; const { route, method } = routeInfo;
3. 扩展JsonServiceClient实现路由自动关联
自定义扩展JsonServiceClient,提前维护DTO类型与路由的映射表,在请求发送前自动关联路由,并将路由信息传递给responseFilter。
步骤1:创建路由映射表
const dtoRouteMap: Record<string, { route: string; method: string }> = { GetData20: { route: "/api20/data", method: "GET" }, // 其他DTO的路由映射 };
步骤2:扩展JsonServiceClient
class ExtendedJsonServiceClient extends JsonServiceClient { lastRouteInfo?: { route: string; method: string }; async get<T>(dto: any): Promise<T> { const dtoName = dto.constructor.name; const routeInfo = dtoRouteMap[dtoName]; if (routeInfo) { this.lastRouteInfo = routeInfo; return super.get(routeInfo.route, dto); } return super.get(dto); } // 同理扩展post、put等方法 }
步骤3:在responseFilter中使用路由信息
const client = new ExtendedJsonServiceClient(baseUrl); client.responseFilter = (res) => { const { route, method } = client.lastRouteInfo!; const statusCode = res.status; // 利用route、method、statusCode从OpenAPI获取Schema并验证响应 };
4. 手动指定路由(备选方案)
如果上述方案暂时无法实施,可以在发起请求时手动指定路由,直接使用原始路由而非默认的/json/reply路径:
const data = await client.get("/api20/data", new GetData20({ id })); // 同时保存路由信息用于后续验证 const route = "/api20/data"; const method = "GET";
内容的提问来源于stack exchange,提问作者donotcodeit
相关产品推荐
相关产品推荐

