news 2026/9/18 5:38:01

Aspire TypeScript AppHost 指南:使用 Aspire.Hosting.CodeGeneration.TypeScript 生成类型化托管 API 绑定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Aspire TypeScript AppHost 指南:使用 Aspire.Hosting.CodeGeneration.TypeScript 生成类型化托管 API 绑定

Aspire TypeScript AppHost 指南:使用 Aspire.Hosting.CodeGeneration.TypeScript 生成类型化托管 API 绑定

【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire

Aspire 的Aspire.Hosting.CodeGeneration.TypeScript模块提供了一整套 TypeScript AppHost 脚手架与代码生成工具:它根据 Aspire Type System(ATS)元数据,为用 TypeScript 编写 AppHost 提供类型安全的 SDK,并通过 JSON-RPC 驱动 .NET AppHost 服务器。读完本文,你将掌握从aspire init --language typescript初始化、理解脚手架产物、到通过aspire restore/aspire run管理生成绑定的完整工作流,并了解生成的 TypeScript SDK 内部结构、类型映射规则与运行机制。

这个模块到底是什么

在深入代码之前,先明确它的定位:Aspire.Hosting.CodeGeneration.TypeScript是"用 TypeScript 编写 AppHost"的工具链,而不是"在 Aspire 中托管 JavaScript 或 TypeScript 应用"的集成。区别很关键——它的服务对象是 AppHost(应用程序编排宿主)本身,而不是业务应用。

从仓库结构看,该模块位于 src/Aspire.Hosting.CodeGeneration.TypeScript,核心由以下几部分组成:

  • AtsTypeScriptCodeGenerator.cs:实现ICodeGenerator接口的代码生成器,负责产出整个 TypeScript SDK;
  • TypeScriptLanguageSupport.cs:实现ILanguageSupport接口,负责脚手架文件生成、语言检测与运行时配置;
  • TypeScriptApiProjector.cs:负责将AtsContext解析为 TypeScript 特有的 API 决策(类型映射、options 展平、回调塑形、Promise 包装);
  • Resources 下的base.mtstransport.mtspackage.json等作为嵌入资源随包发布。

此外,csproj 文件 中有一个值得注意的细节:该包设置了IsAspirePolyglotCompatible=false,注释明确说明"这是代码生成基础设施,不是可发现的 polyglot 集成",因此它不会出现在aspire add面向非 C# AppHost 的集成列表里——这正是 README 中"不要用aspire add安装它"的原因。

快速开始:初始化一个 TypeScript AppHost

前置条件

按照 README 与源码中 package.json 的 engines 字段,你需要准备:

  1. Aspire CLI(提供aspire initaspire restoreaspire run等命令);
  2. 受支持的 Node.js 运行时:引擎约束为^20.19.0 || ^22.13.0 || >=24(该约束与 ESLint 10 的 Node 版本要求保持一致,避免安装或运行时失败);
  3. 一个包管理器(npm、pnpm 或 yarn,CLI 会按已有 lockfile 识别)。

初始化命令

在应用目录下执行:

aspire init --language typescript

CLI 会做两件事:

  • 生成apphost.mts脚手架文件;
  • 自动恢复(restore)本代码生成包——所以请勿用aspire add手动安装它。

这里还有一个分支逻辑值得注意:如果应用目录已经存在根级package.json(所谓 brownfield 场景),AppHost 会被创建在一个嵌套的aspire-apphost/包中,避免与现有 Node.js 项目的包结构冲突。源码中通过IsNestedBrownfieldPackage检测:目标目录名为aspire-apphost且其父目录存在package.json时,即按嵌套包处理(见 TypeScriptLanguageSupport.cs)。

最小 AppHost 程序

脚手架生成的apphost.mts内容如下:

import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); await builder.build().run();

源码中脚手架的模板还附带了一些注释示例(见 TypeScriptLanguageSupport.cs),例如添加容器或 PostgreSQL:

// Add your resources here, for example: // const redis = await builder.addContainer("cache", "redis:latest"); // const postgres = await builder.addPostgres("db");

注意createBuilder的导入路径是./.aspire/modules/aspire.mjs——这正是下一节要讲的生成绑定。

脚手架产物全解析

TypeScriptLanguageSupport.Scaffold方法(源码)一次会生成一组配套文件,理解每个文件的用途对日常开发很有帮助:

文件作用
apphost.mtsAppHost 入口,导入生成的 SDK,声明资源与编排逻辑
package.json包清单,含脚本与依赖(详见下文)
tsconfig.apphost.json仅针对 AppHost 的 TypeScript 配置,避免破坏已有项目的 tsconfig 设置
eslint.config.mjsESLint 配置,启用@typescript-eslint/no-floating-promises,让未 await 的 AppHost Promise 直接以 lint 错误暴露
apphost.run.json运行配置文件,包含随机生成的 Dashboard/OTLP/Resource Service 端口(见下文)
.gitignore忽略node_modules/dist/.aspire/

package.json 的脚本与依赖

CreatePackageJson方法(源码)生成的脚本包括:

{ "scripts": { "aspire:lint": "eslint apphost.mts", "aspire:start": "aspire run", "aspire:build": "tsc -p tsconfig.apphost.json", "aspire:dev": "tsc --watch -p tsconfig.apphost.json" } }

在全新(非 brownfield)项目下还会额外生成lintdevbuildwatch四个别名脚本。依赖方面,运行时依赖vscode-jsonrpc@^8.2.0(JSON-RPC 传输层),开发依赖包括@types/nodeeslint@^10.0.3nodemontsx@^4.21.0typescript@^5.9.3typescript-eslint

需要注意:脚手架生成 package.json 时不会读取磁盘上已有的 package.json——所有合并工作由 CLI 侧的PackageJsonMerger负责,避免二次合并导致依赖对象迭代顺序问题。

apphost.run.json 的随机端口

apphost.run.json通过AppHostProfilePortGenerator生成随机端口,包含 https 配置文件:

{ "profiles": { "https": { "applicationUrl": "https://localhost:{DashboardHttpsPort};http://localhost:{DashboardHttpPort}", "environmentVariables": { "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{OtlpHttpsPort}", "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{ResourceServiceHttpsPort}" } } } }

测试场景下可通过ScaffoldRequest.PortSeed固定随机种子,保证端口可复现。

生成的绑定(Generated Bindings)

绑定从哪来

apphost.mts导入的 SDK 位于.aspire/modules/目录,它由两部分信息生成:

  1. Aspire Type System(ATS)元数据:来自Aspire.Hosting及各个托管集成(hosting integration)——ATS 描述了每个托管 API 的类型、能力(capability)与参数;
  2. aspire.config.json中配置的集成:决定当前 AppHost 实际引入哪些集成及其能力。

生成出的 SDK 提供 AppHost 使用的类型化 API,并通过 JSON-RPC 调用 .NET AppHost 服务器。生成流程的主入口是 AtsTypeScriptCodeGenerator.GenerateDistributedApplication,它会产出三个文件:

  • transport.mts:传输层,包含AspireClientHandleregisterCallbackCancellationTokenCapabilityError等 JSON-RPC 通信原语;
  • base.mts:基础类型库,手工维护而非生成,包含ReferenceExpressionAwaitable<T>ResourceBuilderBaseAspireList/AspireDict等;
  • aspire.mts真正根据 ATS 元数据生成的 SDK,即 AppHost 导入的文件。

绑定管理命令

命令作用
aspire restore在 AppHost 目录下执行,不启动 AppHost仅重新生成绑定
aspire run启动 AppHost(运行前同样会确保绑定是最新的)

铁律:永远不要手编辑.aspire/modules/下的任何文件——它是生成产物。需要改变 API 行为时,应该修改 AppHost 代码或调整aspire.config.json中的集成引用,然后重新aspire restore。从生成文件的头部注释 "GENERATED CODE - DO NOT EDIT" 也能印证这一点。

生成的 TypeScript SDK 内部结构与类型映射

AtsTypeScriptCodeGenerator的 XML 文档注释(源码)完整记录了 ATS 到 TypeScript 的类型映射规则,这是理解生成 SDK 的核心。

基本类型映射

ATS 类型TypeScript 类型
stringstring
numbernumber
booleanboolean
anyunknown
callback(context: EnvironmentContextHandle) => Promise<void>
T[](数组)T[](映射后的类型数组)

Handle 类型命名规则

ATS 类型 ID 使用{AssemblyName}/{TypeName}格式,映射为 TypeScript 的 Handle 类型别名时遵循三条规则:

  • 核心类型:类型名 +Handle,例如Aspire.Hosting/IDistributedApplicationBuilderBuilderHandleAspire.Hosting/DistributedApplicationApplicationHandleAspire.Hosting/DistributedApplicationExecutionContextExecutionContextHandle
  • 接口类型:接口名 +Handle(保留 I 前缀),例如Aspire.Hosting.ApplicationModel/IResourceIResourceHandle
  • 资源类型:类型名 +BuilderHandle,例如Aspire.Hosting.Redis/RedisResourceRedisResourceBuilderHandleAspire.Hosting/ContainerResourceContainerResourceBuilderHandle

这些别名在GenerateHandleTypeAliases中生成,形如type BuilderHandle = Handle<'Aspire.Hosting/IDistributedApplicationBuilder'>;(源码),属于内部类型,用户实际接触的是包装类。

Builder 类与 Promise 包装

  • 资源类型会生成对应的 builder 类及其 thenable 包装:RedisResourceRedisResourceBuilder类 +RedisResourceBuilderPromiseextends PromiseLike<RedisResourceBuilder>),这样 fluent 调用可以在未 await 的情况下继续链式书写;
  • 接口类型生成抽象基类并以BuilderBase后缀命名,例如IResourceResourceBuilderBase
  • 具体 builder 依据类型继承层级扩展接口 builder,实现"具体继承抽象"的类体系。

生成器中还有一个AspireClient客户端类,承载剩余的入口点方法(entry point capabilities)。从 TypeScriptApiProjector.cs 可以看到,每个入口点函数都把client: AspireClientRpc作为第一个参数显式传入,而AspireClientRpc的核心签名只有一个:

export interface AspireClientRpc { readonly connected: boolean; invokeCapability<TResult = unknown>(capabilityId: string, args?: Record<string, unknown>): Promise<TResult>; }

也就是说,一切 AppHost 操作最终都归结为对invokeCapability的 JSON-RPC 调用

方法命名与属性生成

  • 方法名默认派生自能力 ID:如Aspire.Hosting.Redis/addRedisaddRedis;TypeScript 侧统一使用 camelCase(能力 ID 本身就是规范形式);
  • 也可以通过[AspireExport(MethodName = "...")]特性显式覆盖;
  • 能力按PropertyGetter/PropertySetter/InstanceMethod/Method分类,getter/setter 会按属性名分组合并生成{ get, set }形式的属性接口(源码);
  • DTO 类型生成export interface,所有属性均为可选(propName?: type)以允许部分对象,且 PascalCase 属性名会被转换为 camelCase;
  • 每个方法/属性都会携带从 ATS 元数据投影出的 JSDoc 文档注释,包括@param@returns@deprecated标记。

文档与生成的代码永不漂移

TypeScriptApiProjector有一个很有意思的设计保证(见其 类注释):它同时被AtsTypeScriptCodeGenerator(生成实际 SDK)和TypeScriptApiExportWriter(生成规范 API 导出文档)消费,两者基于同一个解析结果输出,因此文档中重建的签名与真实发布的 SDK 签名不可能漂移。这正是 README 中"生成的 SDK"稳定性的源码级保证。

运行机制:从 AppHost 到 .NET 服务器的执行链路

TypeScriptLanguageSupport.GetRuntimeSpec(源码)定义了完整的运行契约:

阶段执行内容
安装依赖npm install
PreExecute(执行前)npx --no-install tsc --noEmit -p tsconfig.apphost.json(类型检查)
Execute(执行)npx --no-install tsx --tsconfig tsconfig.apphost.json {appHostFile}
WatchExecute(监听模式)nodemon监听ts,mts扩展名,忽略node_modules/.aspire/modules/,每次变更先tsc --noEmit再通过tsx重启

语言标识为typescript/nodejs(格式{language}/{runtime},为将来支持typescript/buntypescript/deno预留了扩展空间),检测模式为apphost.mtsapphost.ts两种文件 + 必须存在package.json

另外,模块通过NODE_EXTRA_CA_CERTS环境变量向 Node.js 注入 Aspire 的证书包(CertificateBundleEnvironmentVariable),保证 TLS 链路在本地开发环境可用;该属性通过反射探测方式设置,以保证与旧版 CLI 的兼容性。

ReferenceExpression:连接资源的表达式

base.mts中定义的ReferenceExpression是生成 SDK 里非常实用的能力(源码),它允许把端点引用组合成表达式,并在协议层序列化为$expr格式。其官方注释给出的示例:

const redis = await builder.addRedis("cache"); const endpoint = await redis.getEndpoint("tcp"); // Create a reference expression const expr = refExpr`redis://${endpoint}:6379`; // Use it in an environment variable await api.withEnvironment("REDIS_URL", expr);

序列化后的 JSON 形如:

{ "$expr": { "format": "redis://{0}:{1}", "valueProviders": [ { "$handle": "Aspire.Hosting.ApplicationModel/EndpointReference:1" }, { "$handle": "Aspire.Hosting.ApplicationModel/EndpointReference:2" } ] } }

条件表达式(condition+whenTrue/whenFalse)同样受支持,这让环境变量的取值可以跟随运行时条件动态解析。

测试与验证体系

仓库在 tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests 下提供了完整的测试套件,可作为你理解该模块行为的活文档:

  • TypeScriptLanguageSupportTests.cs:验证脚手架输出的完整性与正确性——包括 package.json 的名称、脚本、engines.node约束、依赖版本、tsconfig.apphost.json的 outDir、ESLint 配置内容等;还验证 brownfield 场景下输出仅包含 Aspire 想要的条目、不会破坏已有 package.json;
  • AtsTypeScriptCodeGeneratorTests.cs:验证代码生成器输出;
  • Snapshots 目录:存放快照测试基线,如AtsGeneratedAspire.verified.ts(整体生成的 SDK)、WithPersistenceCapability.verified.txtWithDataVolumeOptionsMerged.verified.ts(options 展平)等,展示真实生成结果;
  • TestTypes:测试用集成类型,如TestRedisResource.csTestMarkerResource.cs,用于验证各类能力的代码生成路径。

注意事项与最佳实践

  1. 不要用aspire add安装本包:它通过aspire init --language typescript自动还原,手动安装会破坏版本一致性;
  2. 不要编辑.aspire/modules/:所有改动通过修改 AppHost 或aspire.config.json中的集成引用来完成,随后执行aspire restore
  3. 区分概念:本模块服务于 TypeScript AppHost 的开发体验,与托管 JS/TS 应用是两回事,不要混淆使用场景;
  4. Node 版本:遵循^20.19.0 || ^22.13.0 || >=24的引擎约束,否则 ESLint 10 等工具链可能出现安装或运行失败;
  5. brownfield 场景:已有根package.json时 AppHost 会放入嵌套的aspire-apphost/包,生成独立的tsconfig.apphost.json,避免污染现有 TypeScript 配置。

通过本文,你可以完整掌握基于Aspire.Hosting.CodeGeneration.TypeScript的 TypeScript AppHost 开发流程:从初始化、理解脚手架与生成绑定,到读懂生成的 SDK 类型系统与运行链路。后续可以结合 src/Aspire.Hosting.CodeGeneration.TypeScript 的源码与 tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests 的测试深入实践。

【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 5:37:43

Kafka Streams核心架构解析:实时流处理与Flink/Spark选型对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 5:32:00

Flutter实现艺考笔记应用:分类管理与CRUD实战

1. 项目概述与背景作为一名长期从事移动应用开发的工程师&#xff0c;我最近接到了为艺考生开发一款真题题库应用的任务。这个项目最核心的需求之一就是实现一个高效、易用的学习笔记功能。艺考生在日常学习中需要大量记录专业知识点、整理错题、总结考试技巧&#xff0c;因此笔…

作者头像 李华
网站建设 2026/9/18 5:25:16

MATLAB实现RRT算法:机器人路径规划实战

1. 项目背景与核心需求在机器人自主导航领域&#xff0c;路径规划是最基础也最关键的环节之一。想象一下&#xff0c;当你把一个扫地机器人放在客厅中央&#xff0c;它需要自己规划出一条既能覆盖所有区域又不会撞到家具的路线——这就是路径规划要解决的核心问题。RRT&#xf…

作者头像 李华
网站建设 2026/9/18 5:21:44

海光DCU落地Kubernetes全指南:从device plugin到vDCU与DeepSeek部署

最近大模型落地这块&#xff0c;国产算力的存在感越来越强。我这边从去年开始就在搞海光 DCU 怎么接入 Kubernetes&#xff0c;一开始以为把 NVIDIA 那套 device plugin 换皮就能用&#xff0c;结果从驱动到调度器再到推理框架&#xff0c;几乎每个环节都踩了坑。这篇文章把 Cu…

作者头像 李华
网站建设 2026/9/18 5:21:20

物联网硬件功能安全分析:从电路失效到FMEDA失效率计算实战

简介&#xff1a;面向新能源汽车、物联网及嵌入式领域的硬件工程师&#xff0c;内容系统梳理了ISO26262中危害分析与风险评估&#xff08;HARA&#xff09;、故障模式及效应分析&#xff08;FMEA&#xff09;、故障树分析&#xff08;FTA&#xff09;、故障模式效应及诊断度分析…

作者头像 李华