129

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

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

4

25 回答 25

57

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

public class MyPoco {
    public string Name { get; set; }
}

export class MyPoco {
    public Name: string;
}

Codeplex 上也有关于从 C# 自动生成的讨论。

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

http://type.litesolutions.net/

于 2012-10-18T16:27:06.960 回答
39

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

在此处输入图像描述

于 2014-06-18T15:33:01.630 回答
28

如果你使用 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;
}
于 2016-10-04T19:30:24.153 回答
27

上面的 TypeLite 和 T4TS 看起来都不错,刚刚选了一个,TypeLite,分叉了它以获得支持

  • 值类型
  • 可空值
  • camelCasing(TypeScript 根文档使用骆驼,这与 C# 一起使用太好了)
  • 公共字段(喜欢干净和可读的 POCO,也使 C# 编译器变得容易)
  • 禁用模块生成

然后我需要 C#接口,并认为是时候烘焙我自己的东西并编写了一个简单的 T4 脚本来满足我的需要。它还包括Enums。无需回购,只需 < 100 行 T4。

用法
没有库,没有 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 类创建服务接口。

于 2013-04-10T21:36:38.590 回答
19

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

于 2013-03-12T09:36:15.497 回答
19

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

Visual Studio 画廊

网站/文档

更新

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

于 2015-12-31T09:57:45.483 回答
17

试试 Reinforced.Typings 框架。似乎它解决了你的问题。

  1. 从NuGet安装它
  2. 导航到您的 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; }
        }
    }
    
  3. 重建你的项目
  4. 在文件中找出生成的 TypeScript 代码%Your_Project_Directory%/Scripts/project.ts并将其手动添加到项目中

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

在文档 wiki中查看更多详细信息

于 2016-01-14T11:00:30.590 回答
10

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

于 2013-03-19T19:59:24.227 回答
9

请看看这个图书馆打字机

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

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

于 2016-10-21T20:06:32.663 回答
6

I have a little solution which uses T4 templates (view source).

You go from any CLR POCO:

public class Parent : Person
{
    public string Name { get; set; }
    public bool? IsGoodParent { get; set; }
    public virtual ICollection<Child> Children { get; set; }
}

To a TypeScript interface:

///<reference path="Child.d.ts" />
///<reference path="Person.d.ts" />
interface Parent extends Person {
    Name : string;
    IsGoodParent? : bool;
    Children : Child[];
}
于 2013-02-01T01:11:53.473 回答
3

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

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

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

于 2016-11-01T17:38:43.483 回答
3

你也可以使用这个: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

于 2016-07-28T11:05:31.120 回答
3

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

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

于 2015-11-03T08:47:47.660 回答
2

反过来呢?

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

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

于 2014-06-11T01:42:44.777 回答
2

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

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

于 2015-12-18T15:56:35.233 回答
1

我喜欢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

于 2016-02-25T11:15:52.203 回答
1

我也很喜欢@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();
    }
#>
于 2017-05-01T14:32:44.550 回答
1

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

于 2017-01-10T11:50:33.763 回答
0

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

于 2013-02-01T02:47:18.923 回答
0

如果你想通过 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;
}
于 2019-08-25T08:22:51.657 回答
0

在 SDLC 期间, ASP.NET Web API 客户端生成器可能比 swagger 工具链和其他工具更方便、开销更少。

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

于 2019-01-29T06:27:55.980 回答
0

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

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

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

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

精简版:

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/stressed/Reinforced.Typings

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

于 2020-08-18T12:58:13.377 回答
0

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

服务器中的类示例:

[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了解如何使用它的其他示例。

于 2016-12-21T13:53:12.260 回答
0

如果您使用的是 VSCode,您可以查看C# 到 TypeScript的扩展

将 C# 类转换为 TypeScript 接口

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

在我的博客文章中查看更多详细信息

于 2020-06-12T09:29:35.530 回答
0

我使用一个小的自定义 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 生成的一部分是序列化方法,这些方法转换为匿名 JSON 适合类型以进行序列化或存储在 IndexedDB 中。)

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

于 2021-05-28T16:54:15.953 回答