ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

TypeSpec 实战:http-client-csharp 的 SampleService 样例服务与 C 生成客户端定制

2026/9/17 13:47:13 拓冰建站 浏览量
TypeSpec 实战:http-client-csharp 的 SampleService 样例服务与 C 生成客户端定制 TypeSpec 实战http-client-csharp 的 SampleService 样例服务与 C# 生成客户端定制【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec本文围绕 TypeSpec 仓库中docs/samples/client/csharp/SampleService下的样例包展开它演示了如何用typespec/http-client-csharpemitter 把一个 TypeSpec 服务定义编译为完整的 C# HTTP 客户端并展示如何通过一个自定义 logging plugin 为生成的每个方法自动注入调用日志与异常处理。读完后你将掌握该样例的目录构成、本地编译步骤tsp compile .、emitter 配置方式以及 C# 生成器插件机制GeneratorPlugin Visitor的实现原理。样例包的定位一份可运行的参考实现SampleService 包代表一个示例 HTTP 服务以及用typespec/http-client-csharpemitter 编译后产生的生成代码。与只读文档不同这个包的核心价值在于它把规范定义 → 代码生成 → 生成代码增强的完整链路固化成了可复现的工程结构服务规范由 main.tsp 入口声明生成产物落在SampleClient/src/Generated目录下的完整 C# 项目SampleTypeSpecClient.cs、AnimalOperations.cs、Metrics.cs等生成过程被一个 logging plugin 增强——每个方法都会带上进入方法 / 抛出异常 / 退出方法的控制台日志该插件在 package.json 中以logging-plugin依赖声明。从目录结构看样例遵循一个 TypeSpec 项目 一个生成后的 .NET 客户端项目的布局docs/samples/client/csharp/SampleService/ ├── main.tsp # TypeSpec 入口 ├── package.json # 依赖emitter logging-plugin ├── tspconfig.yaml # emitter 配置 ├── readme.md # 样例说明 └── SampleClient/ # 生成后的 C# 客户端解决方案 └── src/Generated/ # 生成代码Client / Operations / Models / CollectionResults / Internal服务规范一行 import 复用了完整的测试规格打开 main.tsp其内容只有一行 importimport ../../../../../packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp;也就是说SampleService 复用的是 emitter 测试工程中的 Sample-TypeSpec.tsp。这份规格本身覆盖面非常广几乎是一份特性清单从源码可以看到它包含命名空间与服务声明namespace SampleTypeSpec带versioned(Versions)版本化、service标题、server模板化端点{sampleTypeSpecUrl}以及useAuth(ApiKeyAuth... | SampleOAuth2)的 API Key OAuth2 隐式流双认证声明丰富的枚举形态IntFixedEnum、FloatFixedEnum、StringFixedEnum等固定枚举以及IntExtensibleEnum、FloatExtensibleEnum、DaysOfWeekExtensibleEnum等可扩展枚举union复杂模型字面量属性requiredLiteralString: accept、可空属性、encode编码、dynamicModel动态模型、friendlyName/clientName命名定制、嵌入 header/query 参数的ModelWithEmbeddedNonBodyParametersXML 序列化XmlAdvancedModel全面覆盖attribute、unwrapped、Xml.name、命名空间ns等特性配合typespec/xml库分页与集合listpageItemsnextLinkurl 与 string 两种、continuationTokenbody 与 header 两种、以及PageT泛型分页共五种分页操作鉴别器类型层次discriminator(kind)的Animal→Pet/Dog与Plant→Tree两个继承体系及对应的AnimalOperations、PetOperations、DogOperations、PlantOperations接口分组客户端初始化参数clientInitialization声明Metrics客户端需要metricsNamespace构造参数并用paramAlias(notebookName)演示客户端参数与路径参数的别名映射Notebooks接口Multipart 与流multipartBody的uploadCat操作以及JsonlStreamStreamingItem/SSEStreamSampleEvents的收发操作依赖typespec/http/streams、typespec/sse、typespec/events库。这些特性正好一一对应生成代码里的结构后文会看到它们如何在 C# 客户端中落地。本地编译依赖声明与 emitter 配置依赖声明package.json样例的 package.json 声明了两个依赖{ dependencies: { typespec/http-client-csharp: 1.0.0-alpha.20260310.2, logging-plugin: file:../plugins/logging } }typespec/http-client-csharpC# 客户端 emitter锁定的版本为1.0.0-alpha.20260310.2这是可运行的前提之一——logging plugin 引用的生成器基类版本需要与 emitter 匹配logging-plugin通过file:../plugins/logging以本地路径引用的插件包安装后会被链接进node_modules。这一依赖正是样例说明中logging-plugin is specified in the package.json file的具体体现。插件包自身的 package.json 描述很直白A simple logging plugin that mutates a library generated with typespec/http-client-csharp to log method names to the console其files字段发布dist/**即包含插件程序集产物。emitter 配置tspconfig.yamltspconfig.yaml 定义了编译行为emit: - typespec/http-client-csharp options: typespec/http-client-csharp: emitter-output-dir: {project-root}/SampleClientemit指定本次编译启用的 emitteremitter-output-dir: {project-root}/SampleClient把生成代码输出到SampleClient目录。注意按 plugins.md 的说明plugins选项中的相对路径正是以解析后的emitter-output-dir为锚点理解这个目录是整个 emitter 选项体系的基准。编译命令在样例说明readme.md给出的操作步骤是克隆仓库后进入SampleService目录执行tsp compile .tsp compile .会解析当前目录的main.tsp与tspconfig.yaml把生成的 C# 代码写入SampleClient/src/Generated仓库中已提交了一份生成结果可直接阅读其结构。Logging Plugin为生成的每个方法注入日志插件如何被发现C# 生成器有两条插件发现路径见 plugins.md自动发现扫描项目node_modules中任何包的dist目录显式指定通过 emitter 的plugins选项传入 dll 文件、目录或含.csproj的目录。本样例走的是第一条路径logging-plugin被声明为file:依赖安装到node_modules后其插件程序集被生成器自动发现无需在tspconfig.yaml中额外配置。这也是样例刻意演示零配置插件接入的原因。插件实现三个类各司其职插件源码位于docs/samples/client/csharp/plugins/logging/Logging.Plugin/src/LoggingPlugin.cs 是插件入口继承GeneratorPlugin并在Apply中注册一个 visitorpublic class LoggingPlugin : GeneratorPlugin { public override void Apply(CodeModelGenerator generator) { generator.AddVisitor(new LoggingVisitor()); } }按 plugins.md 的说明GeneratorPlugin基类通过 MEF 的[InheritedExport]导出子类所在程序集一旦加载即被自动发现无需手工添加[Export]特性Apply收到的CodeModelGenerator提供AddVisitor、AddRewriter、AddMetadataReference、AddSharedSourceDirectory四个扩展点本样例只用了第一个。LoggingVisitor.cs 继承ScmLibraryVisitorSCM 即 Standard Client Model只重写了服务方法这一个访问点public class LoggingVisitor : ScmLibraryVisitor { protected override ScmMethodProviderCollection Visit(InputServiceMethod serviceMethod, ClientProvider clientProvider, ScmMethodProviderCollection methodProvider) { return new LoggingMethodProviderCollection(serviceMethod, clientProvider); } }即每当生成管线为某个InputServiceMethod服务操作构建方法集合时插件用LoggingMethodProviderCollection替换默认实现。LoggingMethodProviderCollection.cs 则是真正的改写者。它继承ScmMethodProviderCollection在BuildMethods()中先调用基类拿到全部方法再对每个方法做两件事ConvertToBodyStatementMethodProvider把表达式体方法如 return x;转换成语句体使方法体可以被包裹throw表达式体则转换为Terminate()语句用 Snippets API 构造try { Console.WriteLine(Entering method X.); ...body... } catch (Exception ex) { Console.WriteLine(An exception was thrown in method X: {0}, ex); throw; } finally { Console.WriteLine(Exiting method X.); }并method.Update(bodyStatements: statements)替换原方法体。var tryExp new TryExpression( [ InvokeConsoleWriteLine(Literal($Entering method {method.Signature.Name}.)), method.BodyStatements! ]); // catch打印异常后 rethrowfinally打印 Exiting method ... var statements new TryCatchFinallyStatement(tryExp, catches, finallyStatement); method.Update(bodyStatements: statements);从这段实现可以看到插件机制的典型用法不改写类型、不改写序列化只在方法构建这一层级拦截用抽象语法构造器TryExpression、CatchExpression、FinallyExpression、InvokeConsoleWriteLine等 Snippet安全地重组方法体——这正是轻量定制生成器、无需编写完整自定义 generator的模式。生成代码的结构规格特性如何落地为 C# 项目生成结果位于 SampleClient/src/Generated可以按目录反查规格特性顶层客户端SampleTypeSpecClient.cs及其SampleTypeSpecClient.RestClient.cs协议方法部分、SampleTypeSpecClientOptions.cs端点、认证等选项对应规格的server与useAuth、SampleTypeSpecModelFactory.cs模型工厂接口分组 → Operations规格中的AnimalOperations、PetOperations、DogOperations、PlantOperations、Metrics等接口分别生成AnimalOperations.cs/PetOperations.cs等每个接口对应独立的.RestClient.cs协议方法文件Metrics由于声明了clientInitialization其初始化参数metricsNamespace体现在客户端构造上Models 与序列化Models/下每个模型拆分为两个 partial——Thing.cs属性与Thing.Serialization.csJSON 读写枚举各自成文件如StringFixedEnum.cs、DaysOfWeekExtensibleEnum.cs分页结果CollectionResults/目录下的 20 个类型...GetWithContinuationTokenCollectionResult、...GetWithNextLinkAsyncCollectionResult等正是规格中五种list操作对应的分页迭代器/异步集合结果类型Internal 基础设施ClientPipelineExtensions.cs、ClientUriBuilder.cs、ModelSerializationExtensions.cs、ChangeTrackingDictionary.cs等运行时支撑类。这份生成代码本身就是一份规格特性 ↔ 生成代码的对照表想验证某个 TypeSpec 装饰器如encode(string)、dynamicModel、convenientAPI在 C# 客户端中的行为先在这个样例里搜对应生成文件是最快的方式。小结SampleService 样例用最小的入口文件一行 import 的main.tsp演示了typespec/http-client-csharp工作流的三个关键环节规范复用通过相对路径 import 复用 emitter 测试工程中覆盖枚举、XML、分页、鉴别器、流式等特性的完整规格一键编译package.json锁定 emitter 与插件依赖tspconfig.yaml配置emitter-output-dirtsp compile .即产出完整 C# 客户端项目生成定制logging plugin 借助node_modules自动发现机制以GeneratorPluginScmLibraryVisitor Snippet 方法体重写为每个生成方法注入进入/退出/异常三段日志。如果想深入插件机制建议按 plugins.md 通读插件扩展点与plugins选项如果想核对某个装饰器的生成效果直接在 SampleClient/src/Generated 中对照 Sample-TypeSpec.tsp 逐特性检索即可。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考