如何在 TypeScript 项目中重用现有的 C# 类定义

Posted

技术标签:

【中文标题】如何在 TypeScript 项目中重用现有的 C# 类定义【英文标题】:How to reuse existing C# class definitions in TypeScript projects 【发布时间】:2012-10-09 02:21:30 【问题描述】:

我将开始在我的 html 客户端项目中使用 TypeScript,该项目属于 MVC 项目,其中已经存在实体框架域模型。我希望我的两个项目(客户端和服务器端)完全分开,因为两个团队将为此工作...... JSON 和 REST 用于来回通信对象。

当然,我在客户端的domain 对象应该与服务器端的对象匹配。过去,我通常手动完成此操作。有没有办法重用我的 C# 类定义(特别是我的域模型中的 POJO 类)在 TypeScript 中创建相应的类?

【问题讨论】:

不,它们是完全不同的语言。 是的,我的意思是 POCO。我知道它们是完全不同的语言......但是以某种方式避免再次输入所有这些会很棒......我认为目前我将复制并粘贴我的 c# 代码......并且会重构它直到它编译TypeScript ......这样我确信我没有输入错误的属性......并且帮助我不必记住。但这显然一点都不好听。 我也对另一个方向感兴趣——从 Typescript 模型类到 C# 模型类,通过 JSON 双向反序列化。 我一直在寻找同样的东西,但我偶然发现了this Gulp plugin,尽管我不知道有多少支持 【参考方案1】:

目前没有任何东西可以将 C# 映射到 TypeScript。如果您有很多 POCO,或者您认为它们可能会经常更改,您可以创建一个转换器 - 类似于...

public class MyPoco 
    public string Name  get; set; 

export class MyPoco 
    public Name: string;

还有一个discussion on Codeplex about auto-generating from C#。

为了保持更新,TypeLite 可以从 C# 生成 TypeScript 接口:

http://type.litesolutions.net/

【讨论】:

是的,这样的事情是我想手动做的......好。不确定我现在有时间编写转换器.. 但是是的,这就是想法。谢谢。可能是微软现在就在我们说话的时候这样做。 您可以通过以下两种方式中的任何一种来执行此操作...在 EnvDTE 自动化的帮助下或使用 Mef 目录目录和反射/内省生成 .ts 文件。我更喜欢第二个,因为它不依赖于视觉。 我开始使用 dart 并遇到了如何在 c# 和 javascript 之间共享类型的相同问题。我希望 Typescript 能解决这个问题,但看起来答案还没有。我也被 saltarelle-compiler 宠坏了,它很好地将 c# 编译为 javascript saltarelle-compiler.com @DanielHarvey 枚举由 TypeLite 正确生成,可能在您发表评论后已修复。 TypeScriptPaste 会将 C# 转换为 Typescript。你必须有 Roslyn 编译器,这意味着 Visual Studio 2015,但它工作得非常好。它不是 100%,我不得不简化一些代码——例如,将 foreach 转换为 for 循环,编写我自己的 List 类,并且你必须构造你的代码,使其“纯”只有内部数据结构可以使用,但要在 VS 中修改和测试代码,然后通过 TypeScript 将其“编译”为 Javascript,这是一个很小的代价。【参考方案2】:

我有一个使用 T4 模板 (view source) 的小解决方案。

你来自任何 CLR POCO:

public class Parent : Person

    public string Name  get; set; 
    public bool? IsGoodParent  get; set; 
    public virtual ICollection<Child> Children  get; set; 

到 TypeScript 接口:

///<reference path="Child.d.ts" />
///<reference path="Person.d.ts" />
interface Parent extends Person 
    Name : string;
    IsGoodParent? : bool;
    Children : Child[];

【讨论】:

【参考方案3】:

我的解决方案是编写一个小型 codegen 实用程序,它只需要一个项目程序集(和引用程序集)并开始扫描涉及 typescript 和 c# 之间交互的类型。此实用程序将两个 javascript 都输出为 d.ts ... 该工具在构建后事件中被调用......就像一个魅力!

【讨论】:

不知道为什么我选择输出js和d.ts...现在它只是简单地输出.ts...直接而且非常可行。 嗨,保罗...您能与我们分享实用程序代码吗?我希望只创建一个 ts 文件(在某个文件夹位置),而不是像你说的那样创建一个 d.ts 文件。这是你的 util 可以做的吗??【参考方案4】:

这是我的解决方法。使用属性声明您的 C# 类,然后将生成 .d.ts 文件(使用 T4 转换)。有一个package on nuget,来源是available on github。我仍在从事该项目,但支持非常广泛。

【讨论】:

我分叉了这个项目并在此处添加了淘汰赛支持:github.com/omniscient/t4ts【参考方案5】:

我创建了一个小实用程序,可以从 C# 类生成 TypeScript 接口。以NuGet package 的形式提供。详细文档可以在project webpage 上找到。

【讨论】:

不错的库,辛苦了。不幸的是,没有找到调整产生值类型的方法,例如将“string”转换为“KnockoutObservableString”或将“string[]”转换为“KnockoutObservableArray”。这是近期的计划吗? 我用的是T4TS,花了14分钟。 @root,您实际上不应该在生成的定义中将 string[] 转换为 KnockoutObservableArray。当您实际使用这些类的序列化实例时,您将使用这些生成的定义。现在,您可能正在通过映射插件运行整个对象,但我会提醒您不要这样做。这可能是多余的,我总是喜欢手动将 C# 类的序列化实例(可能通过 ctor)转换为充满可观察对象的 TypeScript 类。唯一一次我建议通过映射插件盲目地运行它是用于 CRUD 风格的应用程序。 @Lukas Kabrt,非常感谢 TypeLite,它对我们的项目非常有益,我在这里简要介绍了它:underwatergorilladome.com/… @AllenRice,我发现手工制作的 Knockout 视图模型类很方便,我在这里使用您描述的方法并将其与自动生成的类结合起来,该类是通过的 DTO通过映射插件。【参考方案6】:

上面的TypeLite和T4TS看起来都不错,刚刚选了一个,TypeLite,fork了它以获得对

值类型可以为空的 camelCasing(TypeScript 根文档使用骆驼,这与 C# 配合得很好) 公共字段(喜欢干净易读的 POCO,也便于 C# 编译器使用) 禁用模块生成

然后我需要 C# interfaces 并认为是时候自己动手了,于是编写了一个简单的 T4 脚本来满足我的需要。它还包括枚举。无需 repo,只需

用法 没有库,没有 NuGet,只有这个简单的 T4 文件 - 在 Visual Studio 中使用“添加项目”并选择任何 T4 模板。然后将其粘贴到文件中。用“ACME”调整每一行。为每个 C# 类添加一行

<#= Interface<Acme.Duck>() #>

顺序很重要,任何已知类型都将用于以下接口。如果你只使用接口,文件扩展名可以是.d.ts,对于枚举你需要一个.ts文件,因为一个变量被实例化了。

自定义 破解脚本。

<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".ts" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ assembly name="$(TargetDir)ACME.Core.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>

<#= Interface<Acme.Bunny>() #>
<#= Interface<Acme.Duck>() #>
<#= Interface<Acme.Birdy>() #>
<#= Enums<Acme.CarrotGrade>() #>
<#= Interface<Acme.LinkParticle>() #>

<#+  
    List<Type> knownTypes = new List<Type>();

    string Interface<T>()
       
        Type t = typeof(T);     
        var sb = new StringBuilder();
        sb.AppendFormat("interface 0 \n", t.Name);
        foreach (var mi in GetInterfaceMembers(t))
        
            sb.AppendFormat("  0: 1;\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
        
        sb.AppendLine("");
        knownTypes.Add(t);
        return sb.ToString();
    

    IEnumerable<MemberInfo> GetInterfaceMembers(Type type)
    
        return type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
            .Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property);
    

    string ToCamelCase(string s)
    
        if (string.IsNullOrEmpty(s)) return s;
        if (s.Length < 2) return s.ToLowerInvariant();
        return char.ToLowerInvariant(s[0]) + s.Substring(1);
    

    string GetTypeName(MemberInfo mi)
    
        Type t = (mi is PropertyInfo) ? ((PropertyInfo)mi).PropertyType : ((FieldInfo)mi).FieldType;
        return this.GetTypeName(t);
    

    string GetTypeName(Type t)
    
        if(t.IsPrimitive)
        
            if (t == typeof(bool)) return "bool";
            if (t == typeof(char)) return "string";
            return "number";
        
        if (t == typeof(decimal)) return "number";            
        if (t == typeof(string)) return "string";
        if (t.IsArray)
                    
            var at = t.GetElementType();
            return this.GetTypeName(at) + "[]";
        
        if(typeof (System.Collections.IEnumerable).IsAssignableFrom(t)) 
        
            var collectionType = t.GetGenericArguments()[0]; // all my enumerables are typed, so there is a generic argument
            return GetTypeName(collectionType) + "[]";
                    
        if (Nullable.GetUnderlyingType(t) != null)
        
            return this.GetTypeName(Nullable.GetUnderlyingType(t));
        
        if(t.IsEnum) return "number";
        if(knownTypes.Contains(t)) return t.Name;
        return "any";
    

    string Enums<T>() // Enums<>, since Enum<> is not allowed.
    
        Type t = typeof(T);        
        var sb = new StringBuilder();        
        int[] values = (int[])Enum.GetValues(t);
        sb.AppendLine("var " + t.Name + " = ");
        foreach(var val in values) 
        
            var name = Enum.GetName(typeof(T), val);
            sb.AppendFormat("0: 1,\n", name, val);
        
        sb.AppendLine("");
        return sb.ToString();
    
#>

脚本的下一个级别是从 MVC JsonController 类创建服务接口。

【讨论】:

非常可爱,但你的Enum&lt;T&gt; 中有typeof(ParticleKind)。当然应该是typeof(T)?您还需要将 bool 更新为 boolean 以供以后的 TypeScript 版本使用 另请注意:这会破坏类中的枚举属性 1.嗯,是的,谢谢你的提醒,会解决这个问题的。 2. Typescript 自编写此脚本以来不断发展。看起来它可能需要一些更新,你当然是对的。我希望它可以作为你自己目的的起点。 我将其转换为帮助程序库,修复了错误,现在只使用我的 TT 文件中的单行条目来生成接口和新的const enums。谢谢你的例子。它非常接近,让我更快地获得了工作结果。【参考方案7】:

反过来呢?

查看erecruit TypeScript Translator。它带有随时可用的 C# 支持,但实际上是基于模板的(使用 Nunjucks 进行渲染),这意味着它可以生成其他任何东西——VB.NET、F#、C++、XML、SQL——任何你可以用模板编码的东西。

可用作 .NET 控制台程序、NodeJS 程序(适用于不在 Windows 上的程序)或 Visual Studio extension,并具有生成时保存功能。并且包括 MSBuild 支持,只是为了让您的构建服务器满意。 :-)

【讨论】:

我为此 +1 了,因为它确实是最好的解决方案,保存时生成是杀手级功能。【参考方案8】:

Web Essentials 允许在保存时将 C# 文件编译为 TypeScript .d.ts 文件。然后您可以从您的 .ts 文件中引用定义。

【讨论】:

您是否混淆了 .ts 文件和 c#?找不到这个选项 但经过一番搜索,我发现你的意思..实际上很有用。 实际上并非如此,我很确定该操作不会“编译”任何内容,它会生成代码。这让我很困惑,但不是 99% 正确,我会告诉你的 ;-) 如果我们可以配置它的命名空间和一些自定义,那就更有用了。 您现在必须手动安装“TypeScript 定义生成器”才能使用它 - 请参阅 here【参考方案9】:

您可以使用开源项目NSwag:在 GUI 中,您可以从现有的 .NET DLL 中选择 .NET 类并为其生成 TypeScript 接口。

该项目还提供命令行工具和对 T4 模板的支持以及为 Web API 控制器生成客户端代码...

【讨论】:

请注意,NSwag 只会从二进制文件(您还需要二进制文件的所有依赖项)或其 JSON 文件(Swagger 从二进制文件生成)生成 TS 文件。【参考方案10】:

大家看看https://github.com/reinforced/Reinforced.Typings。过去几天我一直在玩 typelite 和 t4 模板,最终完成了这个项目。它超级简单,就像一个魅力。拿到包,修改配置文件(大概10秒),build。一切都会自动完成,没有任何问题。祝福作者!

T4 模板的坏处在于,一旦您从 VS 构建,扫描的程序集就会被锁定,您必须重新启动 VS(这有多傻?)。 T4 Toolbox + 一些 VS 清理指令中有一些解决方法,但这些对我都不起作用。

【讨论】:

【参考方案11】:

如果您使用的是 Visual Studio,请添加 Typewriter 扩展。

Visual Studio Gallery

Website/Documentation

更新

在 VS 2015 中安装 Web Essentials 后,您可以右键单击类文件,然后 > Web Essentials > 从上下文菜单中创建 Typescript Intellisense 文件。

【讨论】:

它创建了用于智能感知支持的 d.ts 文件,但是如何使用它,然后与创建底层类有什么关系?我去了 WebEssentails,找不到任何文档。有什么建议? TIA。 @hio 只需在您需要使用的任何 .ts 文件中引用 d.ts 文件即可。当/如果您更改类时,底层 d.ts 文件将更新。(提示:您可以将 d.ts 文件拖放到 .ts 文件的顶部,它会为您创建引用。) 谢谢迈克尔!我尝试将 d.ts 文件放到一个空的 .ts 文件中,它只将文件添加到包含文件夹中。在 VS2015 和新的 VS2017 中尝试过,结果相同。你知道有什么视频或教程可以展示这一切是如何工作的吗? TIA 我尝试将 d.ts 文件放到编辑器中的 .ts 文件中,它创建了引用。我想我现在明白了。教程仍然受欢迎。谢谢! @hlo 抱歉,不知道任何教程。当我走 Web Essentials 路线时,我一直在制作重复的 .ts 文件,因为我的域模型位于类库中,并且在我的客户端代码中,我通常需要创建浏览器需要的 dto 或 vm 模型知道关于。如果您遇到所有这些问题,请尝试使用 Typewriter 扩展程序。它允许您在任何地方拥有您的 .ts 模型文件。【参考方案12】:

尝试 Reinforced.Typings 框架。好像解决了你的问题。

    从NuGet安装

    导航到您的 POCO 并在其上方添加 [TsInterface] 属性

    using Reinforced.Typings.Attributes;
    namespace YourNamespace 
        [TsInterface]
        public class YourPoco
        
            public int YourNumber  get;set; 
            public string YourString  get;set; 
            public List<string> YourArray  get;set; 
            public Dictionary<int, object> YourDictionary  get;set; 
        
    
    
    重建您的项目

    %Your_Project_Directory%/Scripts/project.ts文件中找出生成的TypeScript代码并手动添加到项目中

    module YourNamespace 
        export interface IYourPoco
        
            YourNumber: number;
            YourString: string;
            YourArray: string[];
            YourDictionary:  [key: int]: any ;
        
    
    
    对所有 POCO 执行相同操作,并在其他 TypeScript 代码中引用 project.ts

在documentation wiki查看更多详情

【讨论】:

添加示例 sn-p 而只共享 URL 有wiki有快速入门O_o github.com/reinforced/Reinforced.Typings/wiki 但是好的,我会在这里提供一个例子:) 不支持 .NET Core (link) :( @AlexKlaus .NET Core 支持已经到来【参考方案13】:

我喜欢 citykid 解决方案。我把它延长了一点。 因此,解决方案也是基于 T4 模板的代码生成技术。

它可以生成常见的 TypeScript 类型和环境声明。

它支持继承和接口实现。

支持泛型、数组和列表作为类型字段。

它还转换为配置中未明确提及的 TypeScript 类型(例如,我们导入类型 A,在 TS 输出中您可以找到一些其他类型:字段类型、基类型和接口)。

您还可以覆盖类型的名称。

也支持枚举。

使用示例(可以在项目仓库中找到):

// set extension of the generated TS file
<#@ output extension=".d.ts" #>

// choose the type of TS import TsMode.Ambient || TsMode.Class
<# var tsBuilder = new TsBuilder(TsMode.Ambient); #>

// reference assembly with the c# types to be transformed
<#@ assembly name="$(SolutionDir)artifacts\...\CsT4Ts.Tests.dll" #>

// reference namespaces
<#@ import namespace="CsT4Ts.Tests" #>
<#
    //add types to processing
    tsBuilder.ConsiderType(typeof(PresetDTOBase), "PresetBase");
    tsBuilder.ConsiderType(typeof(PresetDTO), "Preset");
    tsBuilder.ConsiderType(typeof(TestInterface<,>));
#>

// include file with transformation algorithms
<#@ include file="CsT4Ts.t4" #>

你会得到一个输出

//CsT4Ts.Tests.PresetDTOBase => PresetBase
// CsT4Ts.Tests.PresetDTO => Preset
// CsT4Ts.Tests.TestInterface`2 => TestInterface
// CsT4Ts.Tests.TestEnum => TestEnum

declare class PresetBase

    PresetId: string;
    Title: string;
    InterviewDate: string;


declare class Preset extends PresetBase

    QuestionsIds: string[];


declare interface TestInterface<TA, TB>

    A: string;
    B: number;
    C: TestEnum;
    D: TestEnum[];
    E: number[];
    F: TA;
    G: TB[];


declare enum TestEnum

    Foo = 10,
    Boo = 100

在这里查看完整的解决方案:https://bitbucket.org/chandrush/cst4ts

【讨论】:

【参考方案14】:

你也可以使用这个:https://github.com/pankleks/TypeScriptBuilder

这个小库基于 C# 类型生成 TypeScript 类型定义。 直接在您的后端 C# 项目中使用它来为您的前端 TypeScript 项目生成代码。 您还可以编写小控制台应用程序,通过预构建工具生成代码。

适用于完整和 NET Core 框架!

通过 nuget 安装: Install-Package TypeScriptBuilder

支持的功能

解决类型依赖性 泛型 类型继承 命名空间(模块) 枚举 可为空的类型 字典转换(到强类型 TS 索引对象) 代码生成控制属性集 any 用于无法转换的类型

更多说明:https://github.com/pankleks/TypeScriptBuilder/blob/master/README.md

【讨论】:

刚试过TypeScriptBuilder,到目前为止看起来很棒; .NET Core 支持万岁!【参考方案15】:

如果你使用 vscode,你可以使用我的扩展 csharp2ts,它就是这样做的。

您只需选择粘贴的 C# 代码并从命令面板运行 Convert C# to TypeScript 命令 转换示例:

public class Person

    /// <summary>
    /// Primary key
    /// </summary>
    public int Id  get; set; 

    /// <summary>
    /// Person name
    /// </summary>
    public string Name  get; set; 

export interface Person

    /**Primary key */
    Id : number;

    /**Person name */
    Name : string;

【讨论】:

我认为人们对完全自动化的解决方案感兴趣,其中使用 msbuild 编译会在使用之前输出 typescript 定义。有没有办法使用插件来获得它? 不,这是一个 VSCode 插件,只能在编辑器中使用,不能作为独立程序使用 如果你想通过 node.js 或 tytpescript,我已经将此 vscode 扩展转换为节点模块。你可以在这里查看 repo:github.com/YuvrajSagarRana/csharp-to-typescript 有没有办法将命令链接到键盘快捷键?例如,将选择 .net 代码,并设置 shift+ctrl+c 来执行“将 C# 转换为 TypeScript”,这将加快进程。我喜欢这个插件!【参考方案16】:

请看看这个库Typewriter

它不仅可以转换类、枚举、接口等,还可以转换 api-controllers,这简直太棒了..

另外,只要您保存源 .cs 文件,它就会执行此操作,因此我不必触发任何外部工具。保存 .cs 即可获得更新的 .ts

【讨论】:

很棒的是它在保存相应的CS文件时会生成TS文件。但是,设置输出文件夹非常脆弱(请参阅discussion here)并且需要一些手工编写的代码。 我确实为此使用了构建工具,而且这个工具比这里提到的大多数工具更强大 唯一的问题是,当我们使用容器时,容器中已经安装了打字机就成了依赖。【参考方案17】:

如果您需要为每个生成的 TypeScript 类/接口创建一个单独的文件(即以“每个文件一个类”的方式),您可以尝试TypeGen。它是一个工具,你可以从包管理器控制台使用它来根据你的 C# 类/枚举生成 TypeScript 文件。目前支持:

导出枚举;将 POCO 导出为 TS 类或接口 继承 泛型类型 集合/嵌套集合类型

加上一些额外的功能。它也是开源的(您可以在github 上查看)。

【讨论】:

【参考方案18】:

如果有兴趣,可以使用TypedRpc。它的目的不仅仅是在 TypeScript 中创建接口,而是在 .Net 中使用 JsonRpc 协议创建与服务的所有通信。

服务器中的类示例:

[TypedRpc.TypedRpcHandler]
public class RpcServerExample

    public String HelloWorld()
    
        return "Hello World!";
    

生成的 TypeScript 代码的使用:

/// <reference path="Scripts/TypedRpc.ts" />

let rpc: TypedRpc.RpcServerExample = new TypedRpc.RpcServerExample();

var callback = function(data, jsonResponse) 
    console.log(data);
;

rpc.HelloWorld().done(callback).fail(callback);

查看https://github.com/Rodris/TypedRpc 了解如何使用它的其他示例。

【讨论】:

【参考方案19】:

您也可以使用 Bridge.net。从 1.7 版开始,它支持生成 TypeScript C# 类型的定义。见http://bridge.net/docs/generate-typescript-definitions/

【讨论】:

主要问题是,当您进行谷歌搜索时,您可能会遇到原始问题,而不会注意到重复/关闭的答案中存在有用的信息 因此请确保规范具有最佳信息。不要发布多个问题。任何关于此的进一步讨论可以到Meta Stack Overflow【参考方案20】:

我也非常喜欢@citykid 的回答,因此我将其扩展为一次完成整个命名空间。只需将 POCO 类放入命名空间,然后重建 T4 模板。我希望我知道如何为每个文件生成单独的文件,但这不是世界末日。

您需要引用顶部的 .DLL 文件(您想要的类所在的位置),并且您需要提及命名空间。所有要编辑的行都标有 ACME。向@citykid 致敬,感谢!

<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".ts" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ assembly name="$(TargetDir)YOUR_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ assembly name="$(TargetDir)YOUR_OTHER_DLL_NAME_HERE_ACME.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>

<#= Process("My.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>
<#= Process("My.Other.Very.Special.Namespace.ACME") #>

<#+  

    List<Type> knownTypes = new List<Type>();

    string Process(string nameSpace) 
      var allass = AppDomain.CurrentDomain.GetAssemblies();
      var ss = "";
      foreach (var ass in allass)
      
         ss += ProcessAssembly(ass, nameSpace);
      
      return ss;
    

   string ProcessAssembly(Assembly asm, string nameSpace) 
      try 
            Type[] types;
            try
            
                types = asm.GetTypes();
            
            catch (ReflectionTypeLoadException e)
            
                types = e.Types;
            
            var s = "";
            foreach (var t in types.Where(t => t != null))
            
               try 

               if (String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal))
               
                    s += InterfaceOfType(t);
               

                catch (Exception e)
               
               
            
            return s;      
      
      catch (Exception ee2) 
        return "// ERROR LOADING TYPES: " + ee2;
      

   

    string InterfaceOfType(Type T)
       
        Type t = T;     
        var sb = new StringBuilder();
        sb.AppendFormat("interface 0 \r\n", t.Name);
        foreach (var mi in GetInterfaceMembers(t))
        
            sb.AppendFormat("  0: 1;\r\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
        
        sb.AppendLine("");
        knownTypes.Add(t);
        return sb.ToString();
    

    string Interface<T>()
       
        Type t = typeof(T);     
        var sb = new StringBuilder();
        sb.AppendFormat("interface 0 \n", t.Name);
        foreach (var mi in GetInterfaceMembers(t))
        
            sb.AppendFormat("  0: 1;\r\n", this.ToCamelCase(mi.Name), GetTypeName(mi));
        
        sb.AppendLine("");
        knownTypes.Add(t);
        return sb.ToString();
    

    IEnumerable<MemberInfo> GetInterfaceMembers(Type type)
    
        return type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
            .Where(mi => mi.MemberType == MemberTypes.Field || mi.MemberType == MemberTypes.Property);
    

    string ToCamelCase(string s)
    
        if (string.IsNullOrEmpty(s)) return s;
        if (s.Length < 2) return s.ToLowerInvariant();
        return char.ToLowerInvariant(s[0]) + s.Substring(1);
    

    string GetTypeName(MemberInfo mi)
    
        Type t = (mi is PropertyInfo) ? ((PropertyInfo)mi).PropertyType : ((FieldInfo)mi).FieldType;
        return this.GetTypeName(t);
    

    string GetTypeName(Type t)
    
        if(t.IsPrimitive)
        
            if (t == typeof(bool)) return "boolean";
            if (t == typeof(char)) return "string";
            return "number";
        
        if (t == typeof(decimal)) return "number";            
        if (t == typeof(string)) return "string";
        if (t.IsArray)
                    
            var at = t.GetElementType();
            return this.GetTypeName(at) + "[]";
        
        if(typeof (System.Collections.IEnumerable).IsAssignableFrom(t)) 
        
            var collectionType = t.GetGenericArguments()[0]; // all my enumerables are typed, so there is a generic argument
            return GetTypeName(collectionType) + "[]";
                    
        if (Nullable.GetUnderlyingType(t) != null)
        
            return this.GetTypeName(Nullable.GetUnderlyingType(t));
        
        if(t.IsEnum) return "number";
        if(knownTypes.Contains(t)) return t.Name;
        return "any";
    

    string Enums<T>() // Enums<>, since Enum<> is not allowed.
    
        Type t = typeof(T);        
        var sb = new StringBuilder();        
        int[] values = (int[])Enum.GetValues(t);
        sb.AppendLine("var " + t.Name + " = ");
        foreach(var val in values) 
        
            var name = Enum.GetName(typeof(T), val);
            sb.AppendFormat("0: 1,\r\n", name, val);
        
        sb.AppendLine("");
        return sb.ToString();
    
#>

【讨论】:

【参考方案21】:

ASP.NET Web API Client Generators 在 SDLC 期间可能比 swagger 工具链和其他工具更方便,开销更少。

虽然程序员通常使用 WebApiClientGen 来生成客户端 API 代码,但该项目还提供了 POCO2TS.exe,这是一个从 POCO 类生成 TypsScript 接口的命令行程序。您可以使用 Poco2ts.exe 或 poco2ts 组件将代码生成与构建管道集成。

【讨论】:

【参考方案22】:

如果你想通过 node.js 转换它,那么你可以使用这个包(csharp-to-typescript)。 https://www.npmjs.com/package/csharp-to-typescript

源代码:https://github.com/YuvrajSagarRana/csharp-to-typescript

eg: 
// After installation, import the package.
var  CsharpToTs, getConfiguration  = require("csharp-to-typescript");

// Use CsharpToTs to convert your source code a. Method one give your source code as string:
const sourceCodeInString =   `public class Address

  public int Id get; set;
  public string Street  get; set; 
  public string City  get; set; 
`

var outputTypescript = CsharpToTs(sourceCodeInString, getConfiguration());
console.log(outputTypescript);

// Output is

export class Address

  Id: number;
  Street: string;
  City: string;

【讨论】:

【参考方案23】:

如果你使用的是 VSCode,你可以看看那个扩展 C# to TypeScript

了解如何通过单击将其从 C# 代码转换。当然,并不是所有的类都会被转换,比如工厂,但对于客户端来说已经足够了。我们只需要数据的形状。有时如果我需要使用访问者模式,我会用我需要的其他方法来装饰。这样做只用了 5 秒钟。与上面的一分钟相比,我可以说这是一个很大的胜利。

在我的blog post中查看更多详细信息

【讨论】:

【参考方案24】:

我在开发者社区页面上写了一个关于此的功能请求:

https://developercommunity.visualstudio.com/idea/1153873/reuse-existing-net-classes-for-typescript-definiti.html

正如您在此处的答案中看到的那样,有许多项目试图解决这个问题,但不幸的是,其中许多项目都是单个开发人员项目,在此过程中不再受到支持。我认为如果 Microsoft 维护其中一个项目,创建一个 .NETTypeScript 生成器,那将是非常整洁的。

其中一些仍在维护,但严重依赖单个开发人员:

TypeLite:

https://bitbucket.org/LukasKabrt/typelite/src/default/

TypeScript 定义生成器:

https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TypeScriptDefinitionGenerator

打字机:

https://github.com/frhagn/Typewriter

类型生成:

https://github.com/jburzynski/TypeGen

强化。打字:

https://github.com/reinforced/Reinforced.Typings

我的建议是选择一个您从一开始就希望停止维护并准备切换到当前维护的项目的项目。我会选择使用注释并交叉手指的东西,如果该项目不再受支持,我可以简单地用其他东西替换该标签。

【讨论】:

【参考方案25】:

我使用一个小的自定义 DSL,它生成 TypeScript 和 C# DTO/POCO 类。 DSL 如下所示:

output CSharp
output TypeScript

namespace MyLibrary.Logic.DataModel.DTOs

class Something

property int ID
property string Name
property DateTime DateTime
property int ForeignKeyID

这样我就有了一个 DTO 模型,它有一个单一的源(DSL)并且位于两个 C# 和 TypeScript 逻辑模型之间。

然后我编写了一个 C# 和 TypeScript DTOFactory 类,该类具有负责在 DTO 和逻辑模型对象之间进行转换的方法。 (并且 DTO 生成的一部分是序列化方法,这些方法可以转换为适合于序列化或存储在 IndexedDB 中的匿名 JSON 类型。)

通过这样做,只要双方的模型发生变化,我就会得到编译错误,直到双方再次匹配。

【讨论】:

以上是关于如何在 TypeScript 项目中重用现有的 C# 类定义的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript:如何使现有的命名空间成为全局的?

如何在模式中重用现有的 HTML 元素?

如何使用位在javascript项目中创建打字稿组件?

如何在Vue项目中使用Typescript

如何让 Widget PendingIntend 重用现有的 MainActivity 实例?

如何为新的面板类重用现有的布局代码?