强类型客户端API生成器在C#和TypeScript中为jQuery和Angular 2+生成强类型客户端API。该工具包旨在最大限度地减少重复性任务,简化后端开发与前端开发之间的协调,并通过减少工作量,工作量和压力来提高开发团队的工作效率和产品质量。
这个开源项目提供以下产品:
本文重点介绍如何生成C#Client API库。对于TypeScript中的客户端API库,请查看其他文章。如果您正在开发.NET Core应用程序,您可能有兴趣检查“ 为ASP.NET Core Web API生成C#客户端API ”。
WebApiClientGen
在RAD或敏捷软件开发期间,与ASP.NET Web API无缝集成,只需很少的步骤/开销即可在Web API和客户端API之间进行设置,维护和同步。DataTime
,DataTimeOffset
,Array
,Tuple
,动态对象,Dictionary
并KeyValuePair
基本上,您可以制作Web API代码,包括API控制器和数据模型,然后执行CreateClientApi.ps1。而已。WebApiClientGen
和CreateClientApi.ps1将为您完成剩下的工作。
本节介绍团队合作的一些基本方案。不同公司和团队的情况和背景可能会有所不同,因此您应相应地调整您的团队实践。
您的团队有一个后端开发人员Brenda在Web API上工作,还有一个前端开发人员Frank在前端工作。每台开发机器都具有正确设置的集成测试环境,因此大多数CI工作可以在没有团队CI服务器的情况下在每台开发机器上完成。Trunk base开发是默认的分支实践。
Brenda调整了CodeGen.json,它将生成的代码定向到前端存储库的工作文件夹中的客户端API文件夹。
如果您曾使用WCF开发基于SOAP的Web服务,则可能喜欢使用由SvcUtil.exe或Visual Studio IDE的服务引用生成的客户端API代码。转移到Web API时,我觉得自己已经回到了石器时代,因为我不得不花费大量时间在设计时检查数据类型和函数原型,这会耗费太多宝贵的脑力而在计算时应该做这样的检查。
我已经开发了一些RESTful Web服务之上IHttpHandler
/ IHttpModule
2010年回来了,这也没必要强类型的数据,但像证件任意数据和溪流。但是,我一直在开发更多需要高抽象和语义数据类型的Web项目。
我看到ASP.NET Web API确实通过类支持高抽象和强类型函数原型ApiController
,ASP.NET MVC框架可选地提供了很好地生成的描述API函数的帮助页面。但是,在开发Web API之后,我必须手工制作一些非常原始和重复的客户端代码来使用Web服务。如果Web API是由其他人开发的,我将不得不阅读在线帮助页面。
我怀念WCF过去的美好时光。:)应减少客户端编程的开销。
因此,我搜索并尝试找到一些解决方案,可以让我从制作原始和重复的代码中解放出来,这样我就可以专注于在客户端构建业务逻辑。这是一个帮助开发客户端程序的开源项目列表:
虽然这些解决方案可以生成强类型的客户端代码并在某种程度上减少重复性任务,但我发现它们都没有能够为我提供我所期望的所有流畅和高效的编程体验:
ApiController
。DataContractAttribute
和JsonObjectAttribute
等。这里谈到WebApiClientGen。
为了跟进这种开发客户端程序的新方法,最好有一个ASP.NET Web API项目或包含Web API的MVC项目。您可以使用现有项目,也可以创建演示项目。
安装还将安装依赖的NuGet包Fonlow.TypeScriptCodeDOM
和Fonlow.Poco2Ts
项目引用。
NuGet包将2个TS文件添加到项目的〜/ Scripts / ClientApi文件夹中,一个是HttpClient.ts,另一个是WebApiClientAuto.ts,每次执行CodeGen时都会替换它们。
此外,用于触发CodeGen的CodeGenController.cs被添加到项目的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 http://localhost:10965/api/CodeGen with json object CodeGenParameters
/// </summary>
/// <param name="parameters"></param>
/// <returns>OK if OK</returns>
[HttpPost]
public string TriggerCodeGen(CodeGenParameters parameters)
{
...
}
}
CodeGenController
安装在YourMvcOrWebApiProject / Controllers中,即使MVC项目的脚手架具有派生类的文件夹API ApiController
。
确保引用以下包:
System.Runtime.Serialization
System.ServiceModel
System.ComponentModel.DataAnnotations
如此截图所示:
您的Web API项目可能具有POCO类和API函数,如下所示:
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)
{
v1.9.0中的JSON配置数据如下所示:
{
"ApiSelections": {
"ExcludedControllerNames": [
"DemoWebApi.Controllers.Account"
],
"DataModelAssemblyNames": [
"DemoWebApi.DemoData",
"DemoWebApi"
],
"CherryPickingMethods": 1
},
"ClientApiOutputs": {
"ClientLibraryProjectFolderName": "DemoWebApi.ClientApi",
"GenerateBothAsyncAndSync": true,
}
}
配置数据示例使用v1.8.0,此处提供了早期版本。
建议将JSON配置数据保存到像这样的文件中。
如果您在Web API项目中定义了所有POCO类,则应将Web API项目的程序集名称放在“ DataModelAssemblyNames
” 数组中。如果您有一些专用的数据模型程序集可以很好地分离关注点,那么您应该将相应的程序集名称放入数组中。您可以选择生成TypeScript客户端API代码或C#客户端API代码,或两者。
CodeGen根据“ CherryPickingMethods
” 在POCO类中生成C#客户端代理类,在下面的doc注释中对此进行了描述:
/// <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
选择加入。您可以使用任何方法或组合方法。
在IIS Express上的IDE中运行Web项目。
然后使用Curl或Poster或任何您喜欢的客户端工具POST到http:// localhost:10965 / api / CodeGen,with content-type=application/json
。
基本上,您只需要一步就可以生成客户端API,因为您不需要每次都安装NuGet包。
编写一些批处理脚本来启动Web API和POST JSON配置数据应该不难。我实际上已经起草了一个供您参考:CreateClientApi.ps1,它在IIS Express上启动Web(API)项目,然后POST JSON配置文件。所以基本上,大多数情况下,为了不断更新/同步Web API和客户端API,您只需要通过运行Powershell脚本来完成第3步。这减少了持续集成的大量开销。
此序列图说明了开发周期:
完成这些步骤后,现在您已在C#中生成客户端API,类似于此示例:
namespace DemoWebApi.DemoData.Client
{
public enum AddressType
{
Postal,
Residential,
}
public enum Days
{
Sat = 1,
Sun = 2,
Mon = 3,
Tue = 4,
Wed = 5,
Thu = 6,
Fri = 7,
}
public class Address : object
{
private System.Guid _Id;
private string _Street1;
private string _Street2;
private string _City;
private string _State;
private string _PostalCode;
private string _Country;
private DemoWebApi.DemoData.Client.AddressType _Type;
private DemoWebApi.DemoData.Another.Client.MyPoint _Location;
public System.Guid Id
{
get
{
return _Id;
}
set
{
_Id = value;
}
}
public string Street1
{
get
{
return _Street1;
}
set
{
_Street1 = value;
}
}
public string Street2
{
get
{
return _Street2;
}
set
{
_Street2 = value;
}
}
public string City
{
get
{
return _City;
}
set
{
_City = value;
}
}
public string State
{
get
{
return _State;
}
set
{
_State = value;
}
}
public string PostalCode
{
get
{
return _PostalCode;
}
set
{
_PostalCode = value;
}
}
public string Country
{
get
{
return _Country;
}
set
{
_Country = value;
}
}
public DemoWebApi.DemoData.Client.AddressType Type
{
get
{
return _Type;
}
set
{
_Type = value;
}
}
public DemoWebApi.DemoData.Another.Client.MyPoint Location
{
get
{
return _Location;
}
set
{
_Location = value;
}
}
}
public class Entity : object
{
private System.Guid _Id;
private string _Name;
private DemoWebApi.DemoData.Client.Address[] _Addresses;
public System.Guid Id
{
get
{
return _Id;
}
set
{
_Id = value;
}
}
[System.ComponentModel.DataAnnotations.RequiredAttribute()]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
public DemoWebApi.DemoData.Client.Address[] Addresses
{
get
{
return _Addresses;
}
set
{
_Addresses = value;
}
}
}
public class Person : DemoWebApi.DemoData.Client.Entity
{
private string _Surname;
private string _GivenName;
private System.Nullable<System.DateTime> _BirthDate;
public string Surname
{
get
{
return _Surname;
}
set
{
_Surname = value;
}
}
public string GivenName
{
get
{
return _GivenName;
}
set
{
_GivenName = value;
}
}
public System.Nullable<System.DateTime> BirthDate
{
get
{
return _BirthDate;
}
set
{
_BirthDate = value;
}
}
}
public class Company : DemoWebApi.DemoData.Client.Entity
{
private string _BusinessNumber;
private string _BusinessNumberType;
private string[][] _TextMatrix;
private int[][] _Int2DJagged;
private int[,] _Int2D;
private string[] _Lines;
public string BusinessNumber
{
get
{
return _BusinessNumber;
}
set
{
_BusinessNumber = value;
}
}
public string BusinessNumberType
{
get
{
return _BusinessNumberType;
}
set
{
_BusinessNumberType = value;
}
}
public string[][] TextMatrix
{
get
{
return _TextMatrix;
}
set
{
_TextMatrix = value;
}
}
public int[][] Int2DJagged
{
get
{
return _Int2DJagged;
}
set
{
_Int2DJagged = value;
}
}
public int[,] Int2D
{
get
{
return _Int2D;
}
set
{
_Int2D = value;
}
}
public string[] Lines
{
get
{
return _Lines;
}
set
{
_Lines = value;
}
}
}
public class MyPeopleDic : object
{
private System.Collections.Generic.Dictionary<string, DemoWebApi.DemoData.Client.Person> _Dic;
private System.Collections.Generic.Dictionary<string, string> _AnotherDic;
private System.Collections.Generic.Dictionary<int, string> _IntDic;
public System.Collections.Generic.Dictionary<string, DemoWebApi.DemoData.Client.Person> Dic
{
get
{
return _Dic;
}
set
{
_Dic = value;
}
}
public System.Collections.Generic.Dictionary<string, string> AnotherDic
{
get
{
return _AnotherDic;
}
set
{
_AnotherDic = value;
}
}
public System.Collections.Generic.Dictionary<int, string> IntDic
{
get
{
return _IntDic;
}
set
{
_IntDic = value;
}
}
}
}
namespace DemoWebApi.DemoData.Another.Client
{
public struct MyPoint
{
public double X;
public double Y;
}
}
public partial class Entities
{
private System.Net.Http.HttpClient client;
private System.Uri baseUri;
public Entities(System.Net.Http.HttpClient client, System.Uri baseUri)
{
if (client == null)
throw new ArgumentNullException("client", "Null HttpClient.");
if (baseUri == null)
throw new ArgumentNullException("baseUri", "Null baseUri");
this.client = client;
this.baseUri = baseUri;
}
/// <summary>
///
/// PUT api/SuperDemo/link?id={id}&relationship={relationship}
/// </summary>
public async Task<bool> LinkPersonAsync
(long id, string relationship, DemoWebApi.DemoData.Client.Person person)
{
var requestUri = this.baseUri + "api/SuperDemo/link?id="+id+"&relationship="+relationship;
using (var requestWriter = new System.IO.StringWriter())
{
var requestSerializer = JsonSerializer.Create();
requestSerializer.Serialize(requestWriter, person);
var content = new StringContent(requestWriter.ToString(),
System.Text.Encoding.UTF8, "application/json");
var responseMessage = await client.PutAsync(requestUri, content);
responseMessage.EnsureSuccessStatusCode();
var stream = await responseMessage.Content.ReadAsStreamAsync();
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return System.Boolean.Parse(jsonReader.ReadAsString());
}
}
}
/// <summary>
///
/// PUT api/SuperDemo/link?id={id}&relationship={relationship}
/// </summary>
public bool LinkPerson(long id, string relationship, DemoWebApi.DemoData.Client.Person person)
{
var requestUri = this.baseUri + "api/SuperDemo/link?id="+id+"&relationship="+relationship;
using (var requestWriter = new System.IO.StringWriter())
{
var requestSerializer = JsonSerializer.Create();
requestSerializer.Serialize(requestWriter, person);
var content = new StringContent(requestWriter.ToString(),
System.Text.Encoding.UTF8, "application/json");
var responseMessage = this.client.PutAsync(requestUri, content).Result;
responseMessage.EnsureSuccessStatusCode();
var stream = responseMessage.Content.ReadAsStreamAsync().Result;
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return System.Boolean.Parse(jsonReader.ReadAsString());
}
}
}
/// <summary>
///
/// GET api/SuperDemo/Company?id={id}
/// </summary>
public async Task<DemoWebApi.DemoData.Client.Company> GetCompanyAsync(long id)
{
var requestUri = this.baseUri + "api/SuperDemo/Company?id="+id;
var responseMessage = await client.GetAsync(Uri.EscapeUriString(requestUri));
responseMessage.EnsureSuccessStatusCode();
var stream = await responseMessage.Content.ReadAsStreamAsync();
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<DemoWebApi.DemoData.Client.Company>(jsonReader);
}
}
/// <summary>
///
/// GET api/SuperDemo/Company?id={id}
/// </summary>
public DemoWebApi.DemoData.Client.Company GetCompany(long id)
{
var requestUri = this.baseUri + "api/SuperDemo/Company?id="+id;
var responseMessage = this.client.GetAsync(Uri.EscapeUriString(requestUri)).Result;
responseMessage.EnsureSuccessStatusCode();
var stream = responseMessage.Content.ReadAsStreamAsync().Result;
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<DemoWebApi.DemoData.Client.Company>(jsonReader);
}
}
如果您希望某些外部开发人员使用您的Web API,您可以发布针对各种平台的C#客户端API代码或编译库以及ASP.NET MVC框架生成的帮助页面。
这是一个简单的例子:
var httpclient = new system.net.http.httpclient();
var api = new demowebapi.controllers.client.entities(httpclient, baseuri);
person person = new person()
{
name = "some one",
surname = "one",
givenname = "some",
birthdate = datetime.now.addyears(-20),
addresses = new address[]{new address(){
city="brisbane",
state="qld",
street1="somewhere",
street2="over the rainbow",
postalcode="4000",
country="australia",
type= addresstype.postal,
location = new demowebapi.demodata.another.client.mypoint() {x=4, y=9 },
}},
};
var id = api.createperson(person);
在像Visual Studio这样的正常文本编辑器中编写客户端代码时,您可能会获得良好的智能感知,因此您很少需要阅读Web API帮助页面。
对于通用Windows应用程序,您可以创建如下的客户端API库:
对于Android应用,您可能有一个这样的客户端API项目Mono.Android
:
对于iOS应用程序,您可以使用以下命令创建这样的客户端API项目Xamarin.iOS
:
如果要为具有相同代码库的各种平台提供已编译的库,则可以创建一个符号链接,以指定在DemoWebApi.ClientApi文件夹中生成的WebApiClientAuto.cs文件。
如屏幕截图所示,单击“ 添加为链接 ”,您将在项目中创建指向cs文件的符号链接DemoWebApi.iOSClientApi
。或者,您可以使用Shared Project,或者更好的是.NET Standard项目。
DataTime
,DataTimeOffset
,Array
,Tuple
,动态对象,Dictionary
并KeyValuePair
热门源码