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

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

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

82621 次浏览

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

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

export class MyPoco {
public Name: string;
}

还有一个 关于 C # 自动生成技术的讨论

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

Http://type.litesolutions.net/

我有一个小的解决方案,使用 T4模板(视图来源)。

你从任何 CLR POCO:

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

输入脚本界面:

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

我的解决方案是编写一个小的代码段,直到只接受一个项目程序集(和引用程序集) ,并开始扫描参与类型脚本和 c # 之间交互的类型。这个 util 将两个 javascript 输出为 d.ts..。 该工具在后期构建事件中被调用... ... 工作起来非常有效!

这是我解决问题的方法。用一个属性和。将生成 d.ts-files (使用 T4转换)。有一个 Nuget 的包裹和来源是 可在 github 上下载。我还在做这个项目,但是支持范围相当广泛。

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

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

  • 值类型 ,
  • 空的
  • CamelCasing (TypeScript root doc 使用了 camels,这与 C # 配合得太好了)
  • Public fields (喜欢干净和可读的 POCO,这也使 C # 编译器更容易)
  • 禁用模块生成

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

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

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

顺序问题,任何已知的类型将在以下接口中使用。如果只使用接口,那么文件扩展名可以是 DTS,对于枚举,您需要一个 . 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 类创建服务接口。

反过来怎么样?

看看 英文字体翻译器。它提供了 C # 支持,但实际上是基于模板的(使用双节棍进行渲染) ,这意味着它可以生成任何东西-VB.NET,F # ,C + + ,XML,SQL-任何你可以用模板进行编码的东西。

作为一个。NET 控制台程序,NodeJS 程序(对于那些不在 Windows 上的) ,或者作为一个 Visual Studio 扩展,完成生成保存功能。并且包含 MSBuild 支持,这只是为了让您的构建服务器满意。:-)

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

enter image description here

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

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

大家看看 https://github.com/reinforced/强化,打字。过去几天我一直在使用打字机和 t4模板,最终得到了这个项目。它超级简单,而且非常有效。只需获取包,修改配置文件(大约10秒钟)并构建。一切都自动完成,没有任何问题。保佑作者!

T4模板的缺点是,一旦从 VS 构建,扫描的程序集就会被锁定,必须重新启动 VS (这有多傻?).在 T4 Toolbox + 一些 VS 清洁指令中有一些变通方法,但是没有一个对我有效。

如果使用 VisualStudio,请添加打字机扩展。

视觉工作室画廊

网站/文件

更新

在 VS 2015中安装了 网页基本资料之后,您可以右键单击类文件,然后从上下文菜单中选择 > Web 要素 > 创建类型脚本 Intellisense 文件。

尝试加强。类型框架。似乎它解决了你的问题。

  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. Rebuild your project
  4. Find out generated TypeScript code in %Your_Project_Directory%/Scripts/project.ts file and add it to project manually

    module YourNamespace {
    export interface IYourPoco
    {
    YourNumber: number;
    YourString: string;
    YourArray: string[];
    YourDictionary: { [key: int]: any };
    }
    }
    
  5. Do the same for all your POCOs and reference project.ts in your other TypeScript code.

See more details in documentation wiki

我喜欢城市孩子的解决方案,我把它扩展了一点。 因此,解决方案也是基于 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

您还可以使用以下命令: https://github.com/pankleks/TypeScriptBuilder

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

可以在 Full & NET 核心框架下工作!

安装: Install-Package TypeScriptBuilder

支持特性

  • 解析类型依赖
  • 非专利药
  • 类型继承
  • 名称空间(模块)
  • 枚举
  • 可空类型
  • 字典转换(到强类型 TS 索引对象)
  • 代码生成控件属性集
  • any用于无法转换的类型

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

如果你使用 vscode,你可以使用我的扩展 CSharp 2ts,它正是这样做的。

您只需选择粘贴的 C # 代码并从命令面板中运行 Convert C# to TypeScript命令即可 enter image description here 一个转换例子:

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

请看一下这个库 打字机

它不仅可以转换类、枚举、接口等,还可以转换 api 控制器,这简直太棒了。

而且只要你保存了源代码,它就会这么做。Cs 文件,所以我不必触发任何外部工具。保存。你得到更新。这个

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

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

还有一些额外的特性。它也是开源的(你可以在 Github上查看)。

如果感兴趣,可以使用 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

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

我也非常喜欢@citychild 的回答,所以我将它扩展为一次执行一个完整的名称空间。只需将 POCO 类放入名称空间,并重新构建 T4模板。我希望我知道如何为每个文件生成单独的文件,但这不是世界末日。

您需要引用。DLL 文件位于顶部(您想要的类位于其中) ,并且需要提到名称空间。所有要编辑的行都用 ACME 标记。谢谢@citychild!

<#@ 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();
}
#>

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

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

如果您想通过 node.js 转换它,那么您可以使用这个包(cSharp-to-typecript)。 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;
}

如果您正在使用 VSCode,那么您可以看一下这个扩展 C # 到 TypeScript

Convert C# class to TypeScript interface

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

在我的 博客文章中可以看到更多的细节

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

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

正如您在这里的答案中所看到的,有许多项目试图解决这个问题,但不幸的是,其中许多项目是单个开发人员项目,在这个过程中不再受到支持。我认为,如果微软能够维护其中的一个项目,创建一个从 .NETTypeScript的生成器,那将会非常棒。

其中一些项目仍在维护,但严重依赖单一开发商:

打字机:

Https://bitbucket.org/lukaskabrt/typelite/src/default/

定义生成器:

Https://marketplace.visualstudio.com/items?itemname=madskristensen

打字机:

Https://github.com/frhagn/typewriter

类型:

Https://github.com/jburzynski/typegen

强化,输入:

Https://github.com/reinforced/reinforced 打字

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

我使用一个小的自定义 DSL 来生成 TypeScript 和 C # DTO/POCO 类:

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 和 Logic Model 对象之间进行转换的方法。(DTO 生成的一部分是序列化方法,这些方法转换为适合序列化或存储在 IndexedDB 中的匿名 JSON 类型。)

通过这样做,一旦两边的模型发生更改,就会出现编译错误,直到两边再次匹配。