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

我只是开始在我的HTML客户端项目中使用TypeScript ,该项目属于已经存在entity framework域模型的MVC项目。 我希望我的两个项目(客户端和服务器端)完全分开,因为两个团队将在此工作… JSON和REST用于来回传递对象。

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

目前还没有任何将C#映射到TypeScript的东西。 如果你有很多的POCO,或者你认为他们可能会经常变化,你可以创build一个转换器 – 一些简单的…

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

 export class MyPoco { public Name: string; } 

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

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

http://type.litesolutions.net/

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

在这里输入图像说明

上面的TypeLite和T4TS都看起来不错,只是挑了一个TypeLite,分叉获得支持

  • ValueTypes
  • Nullables
  • camelCasing (TypeScript根文档使用骆驼,这与C#一起太好了)
  • 公共领域 (爱干净可读的POCO,也使得C#编译器变得简单)
  • 禁用模块生成

然后,我需要C# 接口,并认为是时候来烘烤我自己的东西,并写了一个简单的T4脚本,只是做我所需要的。 它还包括枚举 。 不需要回购,只有<100行的T4。

用法
没有图书馆,没有NuGet,只是这个简单的T4文件 – 在Visual Studio中使用“添加项目”,并select任何T4模板。 然后将其粘贴到文件中。 在每一行中都加上“ACME”。 为每个C#类添加一行

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

顺序很重要,任何已知的types将被用在follwing接口中。 如果只使用接口,文件扩展名可以是.d.ts ,对于需要.ts文件的枚举,因为variables是实例化的。

定制
破解脚本。

 <#@ 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类创build服务接口。

这是我解决这个问题的方法。 声明你的C#类有一个属性和.d.ts文件将被生成(使用T4变换)。 在nuget上有一个包,源文件在github上 。 我仍然在做这个项目,但是支持非常广泛。

如果您使用Visual Studio,请添加打字机扩展。

Visual Studio库

网站/文档

更新

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

我创build了一个小工具,可以从C#类生成TypeScript接口。 作为NuGet软件包提供 。 详细的文档可以在项目网页上find。

如果你使用vscode,你可以使用我的扩展csharp2ts ,这正是。

您只需select粘贴的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; } 

我有一个使用T4模板( 查看源代码 )的小解决scheme。

你从任何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[]; } 

试试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. 重build你的项目
  4. %Your_Project_Directory%/Scripts/project.ts文件中查找生成的TypeScript代码,并将其手动添加到项目中

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

在文档wiki中查看更多细节

反过来呢?

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

作为.NET控制台程序,NodeJS程序(对于不在Windows上的程序),或作为Visual Studio扩展 ,具有生成保存function。 并包括MSBuild的支持,只是为了让你的构build服务器开心。 🙂

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

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

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

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

在完整和NET核心框架上工作!

通过nuget Install-Package TypeScriptBuilderInstall-Package TypeScriptBuilder

支持的function

  • 解决types依赖关系
  • generics
  • typesinheritance
  • 命名空间(模块)
  • 枚举
  • 可空types
  • 词典转换(对强typesTS索引对象)
  • 代码生成控件属性的集合
  • any对于无法转换的types

更多描述: https : //github.com/pankleks/TypeScriptBuilder/blob/master/README.md

请看这个库打字机

它不仅可以转换类,枚举,接口等,而且可以转换简单真棒的api控制器。

加上它只要你保存源文件.cs文件,所以我不必使用任何外部工具来做到这一点..保存.cs和你得到更新.ts

你们看看https://github.com/reinforced/ Reinforced.Typings 。 最近几天我一直在使用typelite和t4模板,最后是这个项目。 它超级简单,像魅力一样工作。 只要得到包,修改configuration文件(这是10秒钟),并build立。 所有得到自动完成没有任何问题。 保佑作者!

T4模板的坏处是,一旦你从VS构build,被扫描的程序集被locking,你必须重新启动VS(这是多么的愚蠢?)。 在T4 Toolbox中有一些解决方法+一些VS清理指令,但这些都没有为我工作。

我喜欢citykid解决scheme。 我已经扩大了一点。 所以,解决scheme也是基于T4模板的代码生成技术。

它可以生成常见的TypeScripttypes和环境声明。

它支持inheritance和接口实现。

支持generics,数组和列表types字段。

它也转换为TypeScripttypes,在configuration中没有明确提到(例如,我们导入typesA,在TS输出中可以find一些其他types:字段types,基types和接口)。

您也可以覆盖types的名称。

枚举也支持。

用法示例(可以在项目库中find它):

 // 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 } 

查看完整的解决scheme: https : //bitbucket.org/chandrush/cst4ts

我的解决scheme是编写一个小的codegen实用程序,只需要一个项目程序集(和引用程序集),并开始扫描typecript参与打字稿和C#的交互。 这个util输出javascript作为d.ts …该工具在后生成事件中调用…就像一个魅力!

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

  • 输出枚举; 将POCO导出为TS类或接口
  • 遗产
  • genericstypes
  • 收集/嵌套收集types

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

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

服务器中类的示例:

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

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

我也很喜欢@ citykid的答案,所以我扩展它一次做一个完整的命名空间。 只需将POCO类放入命名空间,然后重buildT4模板。 我希望我知道如何为每个文件生成单独的文件,但不是世界末日。

您需要引用顶部的.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(); } #>