65.9K
CodeProject 正在变化。 阅读更多。
Home

为 ASP.NET Web API 生成 TypeScript 客户端 API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (56投票s)

2015 年 11 月 10 日

CPOL

11分钟阅读

viewsIcon

126536

为 ASP.NET Web API 生成强类型 TypeScript 客户端 API

引言

为了开发 ASP.NET Web APIASP. NET Core Web API 的客户端程序,强类型客户端 API 生成器生成 C# 代码和 TypeScript 代码中的强类型客户端 API,以最大限度地减少重复性任务并提高应用程序开发人员的生产力和产品的质量。然后,您可以提供或发布生成的源代码或编译后的客户端 API 库给您自己、团队中的其他开发人员或 B2B 合作伙伴。

本项目提供以下产品

  1. 支持桌面、通用 Windows、Android 和 iOS 的 C# 强类型客户端 API 的代码生成器。
  2. 用于 jQuery、Angular 2+ 和 Aurelia 的强类型客户端 API 的 TypeScript 代码生成器,以及使用 Axios 的 TypeScript/JavaScript 应用程序。
  3. TypeScript CodeDOM,一个 TypeScript 的 CodeDOM 组件,派生自 .NET Framework 的 CodeDOM。
  4. POCO2TS.exe,一个从 POCO 类生成 TypeScript 接口的命令行程序。
  5. Fonlow.Poco2Ts,一个组件,用于从 POCO 类生成 TypeScript 接口

本文重点介绍为 jQuery 生成 TypeScript 客户端 API。

备注

自 2016 年 6 月 WebApiClientGen v1.9.0-beta 发布以来,Angular 2 的支持就已经可用,当时 Angular 2 仍处于 RC1 版本。而 Angular 2 正式版本的支持自 WebApiClientGen v2.0 起即可使用。请参阅文章“ASP.NET Web API、Angular2、TypeScript 和 WebApiClientGen”。

即使您正在进行 JavaScript 编程,也可以使用 WebApiClientGen,因为生成的 TypeScript 文件可以编译成 JavaScript 文件。虽然您无法获得设计时类型检查和编译时类型检查,但如果您的 JS 编辑器支持此类功能,您仍然可以享受具有内联文档的设计时智能感知。

背景

如果您曾经使用 WCF 开发过基于 SOAP 的 Web 服务,您可能已经享受了SvcUtil.exe 生成的客户端 API 代码或 Visual Studio IDE 的 Web 服务引用。迁移到 Web API 时,我感觉自己回到了石器时代,因为我不得不在设计时花费宝贵的脑力进行大量数据类型检查,而这些工作本应由计算机来完成。

2010 年,我在 IHttpHandler/IHttpModule 的基础上开发了一些 RESTful Web 服务,用于处理不包含强类型数据而是任意数据(如文档和流)的 Web 服务。然而,随着越来越多的 Web 项目包含复杂的业务逻辑和数据类型,我希望在整个 SDLC 中利用高度抽象和语义化的数据类型。

我看到 ASP.NET Web API 通过 ApiController 类确实支持高度抽象和强类型的函数原型,并且 ASP.NET MVC 框架可选地提供 nicely 生成的帮助页来描述 API 函数。然而,在开发完 Web API 后,我不得不手工编写一些非常基础且重复的客户端代码来调用 Web 服务。如果 Web API 是由其他人开发的,我必须阅读在线帮助页然后手工编写。

因此,我搜索并尝试寻找一些解决方案,以使我摆脱手工编写基础且重复的代码,从而能够专注于在客户端构建更高级别的技术抽象的业务逻辑。以下是协助开发客户端程序的开源项目列表:

  1. WADL
  2. RAML with .NET
  3. WebApiProxy
  4. Swashbuckle
  5. AutoRest
  6. OData
  7. TypeLITE
  8. TypeWriter

虽然这些解决方案可以在一定程度上生成强类型的客户端代码并减少重复任务,但我发现没有一个能提供我所期望的所有高效编程体验。

  1. 强类型的客户端数据模型映射到服务的数据模型。
  2. 强类型的函数原型映射到 ApiController 派生类的函数。
  3. 代码以整体方式生成,就像 WCF 编程一样。
  4. 通过数据注释(使用 DataContractAttributeJsonObjectAttribute 等流行属性)来选择性地获取数据模型。
  5. 设计时和编译时的类型检查。
  6. 客户端数据模型、函数原型和文档注释的智能感知。

现在隆重推出 WebApiClientGen

假设

  1. 您正在开发 ASP.NET Web API 2.x 应用程序,并且将基于 AJAX 开发 Web 前端的 JavaScript 库,使用 jQuery 或 Angular2 的 SPA。
  2. 您和您的同事倾向于在服务器端和客户端都通过强类型函数进行高层抽象,并使用 TypeScript。
  3. POCO 类被 Web API 和 Entity Framework Code First 使用,并且您可能不想将所有数据类和成员发布到客户端程序。

此外,如果您或您的团队推崇 Trunk Based Development,那就更好了,因为 WebApiClientGen 的设计和使用 WebApiClientGen 的工作流程都考虑到了 Trunk Based Development,它比 Feature Branching 和 Gitflow 等其他分支策略更高效地支持持续集成。

为了跟进这种新的客户端程序开发方式,最好有一个 ASP.NET Web API 项目,或者一个包含 Web API 的 MVC 项目。您可以使用现有项目,或者创建一个演示项目。

Using the Code

本文以 jQuery 的代码示例为重点。有关 Angular 2+ 的类似代码示例,请参阅“ASP.NET Web API、Angular2、TypeScript 和 WebApiClientGen”。

步骤 0:将 NuGet 包 WebApiClientGenWebApiClientGen.jQuery 安装到 Web API 项目

安装过程还将把依赖的 NuGet 包 Fonlow.TypeScriptCodeDOMFonlow.Poco2Ts 添加到项目引用中。

一个 HttpClient 辅助库应与生成的代码一起复制到 Scripts 文件夹,该库会在每次执行 CodeGen 时更新。

此外,CodeGenController.cs 用于触发 CodeGen 的文件已添加到项目的Controllers 文件夹。

CodeGenController 应仅在调试版本开发期间可用,因为客户端 API 应为每个 Web API 版本生成一次。

#if DEBUG  //This controller is not needed in production release, 
	// since the client API should be generated during development of the Web Api.
...

namespace Fonlow.WebApiClientGen
{
    [System.Web.Http.Description.ApiExplorerSettings(IgnoreApi = true)]//this controller is a 
			//dev backdoor during development, no need to be visible in ApiExplorer.
    public class CodeGenController : ApiController
    {
        /// <summary>
        /// Trigger the API to generate WebApiClientAuto.cs for an established client API project.
        /// POST to  https://:10965/api/CodeGen with json object CodeGenParameters
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns>OK if OK</returns>
        [HttpPost]
        public string TriggerCodeGen(CodeGenParameters parameters)
        {
...
        }
    }

备注

  1. CodeGenController 安装在YourMvcOrWebApiProject/Controllers 中,即使 MVC 项目的脚手架可能有一个用于 ApiController 派生类的 API 文件夹。然而,通常将 Web API 实现为一个独立的 Web API 项目是比较好的。如果您希望 MVC 项目和 Web API 项目在同一个网站中运行,您可以将 Web API 作为 MVC 网站的应用程序安装。
  2. WebApiClientGenCore 不安装 CodeGenController,您应该 复制该文件

启用 Web API 的文档注释

C:\YourWebSlnPath\Your.WebApi\Areas\HelpPage\App_Start\HelpPageConfig.cs 中,有这样一行:

//config.SetDocumentationProvider(new XmlDocumentationProvider
(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));

取消注释该行,使其如下所示:

config.SetDocumentationProvider(new XmlDocumentationProvider
(HttpContext.Current.Server.MapPath("~/bin/Your.WebApi.xml")));

在项目属性页的“生成”选项卡中,勾选“输出/XML 文档文件”并设置为“bin\Your.WebApi.xml”,而输出路径默认是“bin”。

如果您有其他用于数据模型的程序集,您可以做同样的事情,以确保文档注释被生成并复制到客户端 API。

步骤 1:准备 JSON 配置数据

您的 Web API 项目可能包含如下的 POCO 类和 API 函数。[有关 数据模型ApiController 的完整代码示例]

namespace DemoWebApi.DemoData
{
    public sealed class Constants
    {
        public const string DataNamespace = "http://fonlow.com/DemoData/2014/02";
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum AddressType
    {
        [EnumMember]
        Postal,
        [EnumMember]
        Residential,
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum Days
    {
        [EnumMember]
        Sat = 1,
        [EnumMember]
        Sun,
        [EnumMember]
        Mon,
        [EnumMember]
        Tue,
        [EnumMember]
        Wed,
        [EnumMember]
        Thu,
        [EnumMember]
        Fri
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Address
    {
        [DataMember]
        public Guid Id { get; set; }

        public Entity Entity { get; set; }

        /// <summary>
        /// Foreign key to Entity
        /// </summary>
        public Guid EntityId { get; set; }

        [DataMember]
        public string Street1 { get; set; }

        [DataMember]
        public string Street2 { get; set; }

        [DataMember]
        public string City { get; set; }

        [DataMember]
        public string State { get; set; }

        [DataMember]
        public string PostalCode { get; set; }

        [DataMember]
        public string Country { get; set; }

        [DataMember]
        public AddressType Type { get; set; }

        [DataMember]
        public DemoWebApi.DemoData.Another.MyPoint Location;
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Entity
    {
        public Entity()
        {
            Addresses = new List<Address>();
        }

        [DataMember]
        public Guid Id { get; set; }

        
        [DataMember(IsRequired =true)]//MVC and Web API does not care
        [System.ComponentModel.DataAnnotations.Required]//MVC and Web API care about only this
        public string Name { get; set; }

        [DataMember]
        public IList<Address> Addresses { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Person : Entity
    {
        [DataMember]
        public string Surname { get; set; }
        [DataMember]
        public string GivenName { get; set; }
        [DataMember]
        public DateTime? BirthDate { get; set; }

        public override string ToString()
        {
            return Surname + ", " + GivenName;
        }

    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Company : Entity
    {
        [DataMember]
        public string BusinessNumber { get; set; }

        [DataMember]
        public string BusinessNumberType { get; set; }

        [DataMember]
        public string[][] TextMatrix
        { get; set; }

        [DataMember]
        public int[][] Int2DJagged;

        [DataMember]
        public int[,] Int2D;

        [DataMember]
        public IEnumerable<string> Lines;
    }

...
...

namespace DemoWebApi.Controllers
{
    [RoutePrefix("api/SuperDemo")]
    public class EntitiesController : ApiController
    {
        /// <summary>
        /// Get a person
        /// </summary>
        /// <param name="id">unique id of that guy</param>
        /// <returns>person in db</returns>
        [HttpGet]
        public Person GetPerson(long id)
        {
            return new Person()
            {
                Surname = "Huang",
                GivenName = "Z",
                Name = "Z Huang",
                BirthDate = DateTime.Now.AddYears(-20),
            };
        }

        [HttpPost]
        public long CreatePerson(Person p)
        {
            Debug.WriteLine("CreatePerson: " + p.Name);

            if (p.Name == "Exception")
                throw new InvalidOperationException("It is exception");

            Debug.WriteLine("Create " + p);
            return 1000;
        }

        [HttpPut]
        public void UpdatePerson(Person person)
        {
            Debug.WriteLine("Update " + person);
        }

        [HttpPut]
        [Route("link")]
        public bool LinkPerson(long id, string relationship, [FromBody] Person person)
        {
            return person != null && !String.IsNullOrEmpty(relationship);
        }

        [HttpDelete]
        public void Delete(long id)
        {
            Debug.WriteLine("Delete " + id);
        }

        [Route("Company")]
        [HttpGet]
        public Company GetCompany(long id)
        {

下面的 JSON 配置数据将 `POST` 到 CodeGen Web API

{
    "ApiSelections": {
        "ExcludedControllerNames": [
            "DemoWebApi.Controllers.Account",
            "DemoWebApi.Controllers.FileUpload"
        ],

        "DataModelAssemblyNames": [
            "DemoWebApi.DemoData",
            "DemoWebApi"
        ],

        "CherryPickingMethods": 3
    },

    "ClientApiOutputs": {
        "ClientLibraryProjectFolderName": "..\\DemoWebApi.ClientApi",
        "GenerateBothAsyncAndSync": true,
        "CamelCase": true,

        "Plugins": [
            {
                "AssemblyName": "Fonlow.WebApiClientGen.jQuery",
                "TargetDir": "Scripts\\ClientApi",
                "TSFile": "WebApiJQClientAuto.ts",
                "AsModule": false,
                "ContentType": "application/json;charset=UTF-8"
            },

            {
                "AssemblyName": "Fonlow.WebApiClientGen.NG2",
                "TargetDir": "..\\DemoNGCli\\NGSource\\src\\ClientApi",
                "TSFile": "WebApiNG2ClientAuto.ts",
                "AsModule": true,
                "ContentType": "application/json;charset=UTF-8"
            }
        ]


    }
}

建议将 JSON 配置数据保存在一个类似 这个文件中,该文件位于 Web API 项目文件夹下。

如果所有 POCO 类都定义在 Web API 项目中,您应该将 Web API 项目的程序集名称添加到“DataModelAssemblyNames”数组中。如果您有用于良好关注点分离的专用数据模型程序集,您应该将相应的程序集名称添加到该数组中。

"TypeScriptNG2Folder" 是指向 Angular2 项目的绝对路径或相对路径。例如,“..\\DemoAngular2\\ClientApi”表示一个与 Web API 项目同级的 Angular 2 项目。

CodeGen 根据“CherryPickingMethods”(在下面的文档注释中进行了描述)从 POCO 类生成强类型的 TypeScript 接口。

/// <summary>
/// Flagged options for cherry picking in various development processes.
/// </summary>
[Flags]
public enum CherryPickingMethods
{
    /// <summary>
    /// Include all public classes, properties and properties.
    /// </summary>
    All = 0,

    /// <summary>
    /// Include all public classes decorated by DataContractAttribute,
    /// and public properties or fields decorated by DataMemberAttribute.
    /// And use DataMemberAttribute.IsRequired
    /// </summary>
    DataContract =1,

    /// <summary>
    /// Include all public classes decorated by JsonObjectAttribute,
    /// and public properties or fields decorated by JsonPropertyAttribute.
    /// And use JsonPropertyAttribute.Required
    /// </summary>
    NewtonsoftJson = 2,

    /// <summary>
    /// Include all public classes decorated by SerializableAttribute,
    /// and all public properties or fields
    /// but excluding those decorated by NonSerializedAttribute.
    /// And use System.ComponentModel.DataAnnotations.RequiredAttribute.
    /// </summary>
    Serializable = 4,

    /// <summary>
    /// Include all public classes, properties and properties.
    /// And use System.ComponentModel.DataAnnotations.RequiredAttribute.
    /// </summary>
    AspNet = 8,
}

默认值为 DataContract,表示选择加入。您可以使用任何一种或组合方法。

步骤 2:运行 Web API 项目的 DEBUG 生成,并 POST JSON 配置数据以触发客户端 API 代码的生成

在 IDE 中使用 IIS Express 运行 Web 项目。

然后,您可以使用 CurlPoster 或任何您喜欢的客户端工具,以 POST 请求发送到 https://:10965/api/CodeGen,设置 content-type=application/json

提示

因此,您只需要步骤 2 即可在 Web API 更新时生成客户端 API,因为您无需每次都安装 NuGet 包或编写新的 JSON 配置数据。

编写批处理脚本来启动 Web API 并 POST JSON 配置数据应该不难。实际上,我已为您起草了一个:一个 Powershell 脚本文件,该文件在 IIS Express 上启动 Web (API) 项目,然后 POST JSON 配置文件以触发代码生成

发布客户端 API 库

现在您已生成了 TypeScript 客户端 API,类似于此示例

/// <reference path="../typings/jquery/jquery.d.ts" />
/// <reference path="HttpClient.ts" />
namespace DemoWebApi_DemoData_Client {
    export enum AddressType {Postal, Residential}

    export enum Days {Sat=1, Sun=2, Mon=3, Tue=4, Wed=5, Thu=6, Fri=7}

    export interface Address {
        Id?: string;
        Street1?: string;
        Street2?: string;
        City?: string;
        State?: string;
        PostalCode?: string;
        Country?: string;
        Type?: DemoWebApi_DemoData_Client.AddressType;
        Location?: DemoWebApi_DemoData_Another_Client.MyPoint;
    }

    export interface Entity {
        Id?: string;
        Name: string;
        Addresses?: Array<DemoWebApi_DemoData_Client.Address>;
    }

    export interface Person extends DemoWebApi_DemoData_Client.Entity {
        Surname?: string;
        GivenName?: string;
        BirthDate?: Date;
    }

    export interface Company extends DemoWebApi_DemoData_Client.Entity {
        BusinessNumber?: string;
        BusinessNumberType?: string;
        TextMatrix?: Array<Array<string>>;
        Int3D?: Array<Array<Array<number>>>;
        Lines?: Array<string>;
    }
}

namespace DemoWebApi_DemoData_Another_Client {
    export interface MyPoint {
        X?: number;
        Y?: number;
    }
}

namespace DemoWebApi_Controllers_Client {

    export class Entities {
        httpClient: HttpClient;
        constructor(public baseUri?: string, public error?: 
        (xhr: JQueryXHR, ajaxOptions: string, thrown: string) => 
        any, public statusCode?: { [key: string]: any; }){
            this.httpClient = new HttpClient();
        }

        /**
         * Get a person
         * GET api/Entities/{id}
         * @param {number} id unique id of that guy
         * @return {DemoWebApi_DemoData_Client.Person} person in db
         */
        GetPerson(id: number, callback: (data : DemoWebApi_DemoData_Client.Person) => any){
            this.httpClient.get(encodeURI(this.baseUri + 
            'api/Entities/'+id), callback, this.error, this.statusCode);
        }

        /**
         * POST api/Entities
         * @param {DemoWebApi_DemoData_Client.Person} person
         * @return {number}
         */
        CreatePerson(person: DemoWebApi_DemoData_Client.Person, 
        	callback: (data : number) => any){
            this.httpClient.post(encodeURI(this.baseUri + 
            'api/Entities'), person, callback, this.error, this.statusCode);
        }

        /**
         * PUT api/Entities
         * @param {DemoWebApi_DemoData_Client.Person} person
         * @return {void}
         */
        UpdatePerson(person: DemoWebApi_DemoData_Client.Person, callback: (data : void) => any){
            this.httpClient.put(encodeURI(this.baseUri + 
            'api/Entities'), person, callback, this.error, this.statusCode);
        }

        /**
         * DELETE api/Entities/{id}
         * @param {number} id
         * @return {void}
         */
        Delete(id: number, callback: (data : void) => any){
            this.httpClient.delete(encodeURI(this.baseUri + 
            'api/Entities/'+id), callback, this.error, this.statusCode);
        }
    }

    export class Values {
        httpClient: HttpClient;
        constructor(public baseUri?: string, public error?: 
        (xhr: JQueryXHR, ajaxOptions: string, thrown: string) => any, 
        public statusCode?: { [key: string]: any; }){
            this.httpClient = new HttpClient();
        }

        /**
         * GET api/Values
         * @return {Array<string>}
         */
        Get(callback: (data : Array<string>) => any){
            this.httpClient.get(encodeURI(this.baseUri + 
            'api/Values'), callback, this.error, this.statusCode);
        }

        /**
         * GET api/Values/{id}?name={name}
         * @param {number} id
         * @param {string} name
         * @return {string}
         */
        GetByIdAndName(id: number, name: string, callback: (data : string) => any){
            this.httpClient.get(encodeURI(this.baseUri + 
            'api/Values/'+id+'?name='+name), 
            callback, this.error, this.statusCode);
        }

        /**
         * POST api/Values
         * @param {string} value
         * @return {string}
         */
        Post(value: {'':string}, callback: (data : string) => any){
            this.httpClient.post(encodeURI(this.baseUri + 
            'api/Values'), value, callback, this.error, this.statusCode);
        }

        /**
         * PUT api/Values/{id}
         * @param {number} id
         * @param {string} value
         * @return {void}
         */
        Put(id: number, value: {'':string}, callback: (data : void) => any){
            this.httpClient.put(encodeURI(this.baseUri + 
            'api/Values/'+id), value, callback, this.error, this.statusCode);
        }

        /**
         * DELETE api/Values/{id}
         * @param {number} id
         * @return {void}
         */
        Delete(id: number, callback: (data : void) => any){
            this.httpClient.delete(encodeURI(this.baseUri + 
            'api/Values/'+id), callback, this.error, this.statusCode);
        }
    }
}

提示

1. 如果您希望生成的 TypeScript 代码符合 JavaScript 和 JSON 的驼峰式命名约定,您可以在 Web API 的脚手架代码的 WebApiConfig 类中添加以下行:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = 
     new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();

然后,属性名和函数名将采用驼峰式命名,前提是 C# 中相应的名称采用 Pascal 命名。更多详情,请查阅 camelCasing 或 PascalCasing

2. PowerShell 脚本还会将 TS 文件编译为 JS 文件。

内部使用

在使用 Visual Studio 等不错的文本编辑器编写客户端代码时,您可能会获得很好的智能提示。

外部使用

如果您希望外部开发人员通过 JavaScript 使用您的 Web API,您可以发布生成的 TypeScript 客户端 API 文件或编译后的 JavaScript 文件,以及 ASP.NET MVC 框架生成的帮助页面。

兴趣点

虽然 ASP.NET MVC 和 Web API 对 JSON 应用程序使用 NewtonSoft.Json,但 NewtonSoft.Json 可以 很好地处理带有 DataContractAttribute 装饰的 POCO 类

CLR 命名空间将通过将点替换为下划线并将“Client”作为后缀来转换为 TypeScript 命名空间。例如,namespace My.Name.space 将被转换为 My_Name_space_Client

从某种意义上说,服务命名空间/函数名与客户端命名空间/函数名的一对一映射暴露了服务的实现细节,这通常是不推荐的。然而,传统的 RESTful 客户端编程要求程序员了解服务函数的 URL 查询模板,而查询模板是服务的实现细节。因此,这两种方法在某种程度上都暴露了服务的实现细节,但后果不同。

对于客户端开发人员来说,经典的函数原型如

ReturnType DoSomething(Type1 t1, Type2 t2 ...)

是 API 函数,其余的是传输的技术实现细节:TCP/IP、HTTP、SOAP、面向资源、基于 CRUD 的 URI、RESTful、XML 和 JSON 等。函数原型和一段 API 文档应该足以调用 API 函数。客户端开发人员不应该关心这些传输的实现细节,至少在操作成功时是这样。只有在出现错误时,开发人员才需要关心技术细节。例如,在 SOAP 风格的 Web 服务中,您必须了解 SOAP 故障;而在 RESTful Web 服务中,您可能需要处理 HTTP 状态码和响应。

而查询模板几乎没有 API 函数的语义含义。相比之下,WebApiClientGen 使用服务函数来命名客户端函数,就像 WCF 中的SvcUtil.exe 默认做的那样,因此只要您作为服务开发人员为服务函数命名了良好的语义名称,生成的客户端函数就具有良好的语义含义。

在涵盖服务开发和客户端开发的整个 SDLC 的大局中,服务开发人员掌握服务函数的语义含义,并且通常将函数命名为描述性名称是一种良好的编程实践。面向资源的 CRUD 可能具有语义含义,也可能仅仅是功能描述的技术转换。

WebApiClientGen 将您的 Web API 的文档注释复制到生成的 TypeScript 代码的 JsDoc3 注释中,因此您几乎不需要阅读 MVC 生成的帮助页面,并且您对服务的客户端编程将变得更加无缝。

许多 JavaScript 框架,如 ReactVue.js,都没有内置的 HTTP 请求库,而是依赖于 Axios 等第三方库。由于 Axios 近年来在 JavaScript 程序员中显然是最受欢迎的,并且得到了 React 和 Vue.js 的推荐,因此支持 Axios 可能更可行。

提示

并且编写脚本来自动化某些步骤以进行持续集成应该不难。您可以在此处找到示例:

  1. WebApiClientGen
  2. WebApiClientGen 示例
  3. .NET Core 演示

并在根文件夹中找到那些“Create*ClientApi.ps1”文件。

备注

Web 服务和客户端的开发格局一直在快速变化。自 2015 年 9 月 WebApiClientGen 发布以来,由 Open API Initiative 主导的 Open API Definition Format 已经出现,并于 2015 年 11 月 成立。希望该倡议能够解决 Swagger 规范 2.0 的一些不足之处,特别是在处理十进制/货币类型方面。尽管如此,使用 WebApiClientGen 的 SDLC 已经针对开发基于 ASP.NET Web API 和 ASP.NET Core Web API 的客户端程序进行了优化。

参考文献

 

© . All rights reserved.