WebApiClientGen vs Swashbuckle 加上 NSwag
比较 WebApiClientGen 和 NSwagStudio 支持的功能。
- GitHub 上的代码示例,SwaggerDemo 分支用于比较 WebApiClientGen 和 Swagger。
引言
本文将 强类型客户端 API 生成器 与 .NET 生态系统中的 Swagger 工具链 进行比较,以便您可以为不同的场景选择合适的工具。假定您拥有 Swagger 工具链的经验,并且至少阅读过以下文章之一。
- 生成 ASP.NET Web API 的 C# 客户端 API
- 为 ASP.NET Core Web API 生成 C# 客户端 API
- 为 ASP.NET Web API 生成 TypeScript 客户端 API
- ASP.NET Web API、Angular2、TypeScript 和 WebApiClientGen
虽然 Swagger 工具链主要用于“先元信息”方法,但也有支持“先代码”方法的工具,即服务器端工具生成 Swagger 定义文件,客户端工具根据定义生成代码。而 WebApiClientGen
则在服务开发过程中直接在服务器端生成客户端代码。本文将重点介绍“先代码”方法,特别是 Swashbuckle.AspNetCore
和 NSwagStudio
,因为这两种工具在 Microsoft Docs 中得到了推广。
C# 客户端
正如其名称所示,强类型客户端 API 生成器 尽可能精确地提供服务器与 C# 客户端之间的精确数据类型映射。
Swashbuckle+NSwag 不支持
- 用户定义的结构体
- 对象
- dynamic
- Generic
- 命名空间
- 枚举
备注
- Swashbuckle 将服务器端的
struct System.Drawing.Point
转换为客户端的Point
类。 - Open API 和 NSwag 支持继承,但
Swashbuckle.AspNetCore
5.0 及更高版本对继承的支持较差。 - Open API 和 NSwag 对
enum
提供有限支持,而 Swashbuckle 的支持更少。 - NSwag 支持命名空间和枚举,但与 Swashbuckle.AspNet Core 5.0 生成的 Swagger 定义文件配合不佳。
Swashbuckle+NSwag 为以下类型提供不精确的数据类型映射
Decimal
==>double
Nullable<T>
==>T
float
==>double
uint
,short
,byte
==>int
ulong
==>long
char
==>string
Tuple
==> 生成结构与Tuple
类似的自定义类型int[,]
==>ICollection<object>
int[][]
==>ICollection<int>
KeyValuePair
==> 生成结构与KeyValuePair
类似的自定义类型
NSwag 生成冗长、庞大且复杂的代码
在 SwaggerDemo
的解决方案中,Core3WebApi
使用 WebApiClientGen
,而 SwaggerDemo
使用 Swashbuckle.AspNetCore
创建 Open API 定义。仅生成异步函数时,WebApiClientGen
生成的代码为 97KB,调试版本为 166KB,发布版本为 117KB。而 Swagger 的 NSwagStudio
则为 489KB-495KB,调试版本为 340KB-343KB,发布版本为 263KB-283KB。NSwag 生成复杂代码可能是有其原因的,您可以进行检查和比较,以确定此类复杂性是否在您的项目内容和场景中是必需的。
NSwag 生成冗余的 GeneratedCodeAttribute
根据 此,GeneratedCodeAttribute
类可用于代码分析工具来识别计算机生成的代码,并根据生成代码的工具及其版本提供分析。
最佳实践是将生成的代码放入一个只包含生成代码的专用程序集中。这样,应用程序程序员就可以简单地将该程序集排除在代码分析工具之外。因此,在生成的代码中没有必要使用 GeneratedCodeAttribute
。
JavaScript 库或框架的 TypeScript 客户端
WebApiClientGen
- jQuery with callbacks
- Angular 2+
- Axios
- Aurelia
- Fetch API
NSwag
- 带回调的 JQuery
- 带 Promises 的 JQuery
- 使用 http 服务的 Angular (v2+)
window.fetch
API 和 ES6 Promises- 使用
aurelia-fetch-client
中的HttpClient
的 Aurelia Axios
(预览)
WebApiClientGen 比 Swagger 优越之处?
对于生成 C# 客户端,WebApiClientGen
支持更多的 .NET 内置数据类型,并提供更精确的数据类型映射。精确的类型映射使客户端编程更容易获得高质量,因为集成测试会因为正确的类型约束而轻松发现超出范围的数据。
更小的代码和更小的编译镜像总是受欢迎的。
生成客户端代码的手动步骤更少,速度更快。
备注
Swashbuckle.AspNetCore
不支持名称相同但位于不同命名空间中的类型。在复杂的业务应用程序中,可能存在名称相同但位于不同命名空间中的自定义数据类型。
Swagger 比 WebApiClientGen 优越之处?
这里的 Swagger 指的是 Open API 标准及相关工具链。
Swagger 是一种开放标准,平台无关,得到主要软件供应商的支持,并由全球数百名开发人员开发。Microsoft Docs 有一个专门介绍 Swagger 的部分 在这里,微软也在自己的 Web API 产品中使用 Swagger。
Swagger 支持对 HTTP 标头进行细粒度控制,而 WebApiClientGen
则忽略了这一领域。
在线帮助怎么样?
Swashbuckle.AspNetCore
提供了“丰富、可定制的体验来描述 Web API 功能”。
WebApiClientGen
将已发布数据类型和控制器操作的源代码内文档复制到客户端代码中,而像 Visual Studio 这样的优秀 IDE 可以在客户端代码中显示智能提示以及源代码内文档。这最大限度地减少了对在线帮助的需求。
如果您确实需要在线帮助,可以为 C# 客户端代码使用 Sandcastle ,为 Angular 2+ 客户端代码使用 Compodoc ,为其他 JavaScript 框架使用 TypeDoc 。
偏好差异
Swagger/Open API 是为 RESTful 服务设计的,而 ASP.NET Web API 是为 RPC 设计的,RPC 涵盖了 RESTful 服务。WebApiClientGen
的设计偏好基于 RPC,而不是 REST。从某种角度来看,REST 是一种构建 RPC 的约束性或规范性方法。对于构建复杂的业务应用程序,REST 可能有利于整体开发,或者可能过于技术化,迫使开发人员将高级业务逻辑翻译成 REST,而不是专注于业务领域建模。
“考虑一下我们看到软件项目有多少次开始时采用了最新的架构设计潮流,而直到后来才发现系统需求是否需要这种架构。”
参考文献
WebApiClientGen 和 Swagger 能共存吗?
答案是可以。
Swagger 工具链和 WebApiClientGen
在 .NET 生态系统中存在很大的重叠,而 Swagger 覆盖的范围更广、更深入,WebApiClientGen 则针对 .NET Framework 和 .NET Core 的 SDLC 以及强类型进行了优化。
如果您正在开发 ASP.NET (Core) Web API,并且期望所有客户端仅用 C# 和 TypeScript 编写,WebApiClientGen
会给您带来更多优势。
当您需要支持 C# 和 TypeScript 以外语言编写的客户端时,您可以将 Swashbuckle 引入您的 Web API,并生成 JSON 或 YAML 格式的 Open API 定义文件,然后使用 NSwag 或其他 Swagger/Open API 工具来生成客户端。
使用 WebApiClientGen 和 Swagger 实现完美的 SDLC
当您作为后端开发人员更新 Web API 后,您只需运行 WebApiClientGen
批处理文件,即可为客户端应用程序开发人员生成 C# 客户端代码和 TypeScript 客户端代码。Web API 的 Swagger 端点提供 Open API 定义文件,以便使用其他语言开发的客户端应用程序开发人员可以生成其他语言的客户端 API 代码。
这样您就可以兼顾 WebApiClientGen
和 Swagger/Open API
的优点。
关注点
在撰写本文时,我曾对 Swagger/Open API 规范进行了详细研究,因为在 2015 年 WebApiClientGen
项目启动时,我也做过类似的研究。从 Swagger 生成代码的生态系统已经发生了很大变化,涌现出适用于广泛服务器平台和客户端平台的全面而成熟的工具链。然而,现有的 C# 和 TypeScript 客户端代码生成工具并不能令我满意,如果我需要使用一个第三方服务,但该服务不提供客户端库,只提供一些 Swagger/Open API 规范的定义文件。基于 WebApiClientGen
的核心组件,编写一个 NSwag 或 Autorest 的替代品应该不难。看这里:OpenApiClientGen。该项目的 Wiki 上有 页面比较了 NSwag 和 OpenApiClientGen 基于相同 Swagger/Open API 定义集生成的代码。
附录
当您考虑您的 SDLC 和 SDLC 的上下文时,附录将为您提供 Swagger 和 WebApiClientGen
生成代码的一些基本比较。
服务中的 Person 类型
/// <summary>
/// Base class of company and person
/// </summary>
[DataContract(Namespace = Constants.DataNamespace)]
public class Entity
{
public Entity()
{
Addresses = new List<Address>();
}
[DataMember]
public Guid Id { get; set; }
/// <summary>
/// Name of the entity.
/// </summary>
[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; }
/// <summary>
/// Multiple addresses
/// </summary>
[DataMember]
public IList<Address> Addresses { get; set; }
[DataMember]
public virtual ObservableCollection<PhoneNumber> PhoneNumbers { get; set; }
public override string ToString()
{
return Name;
}
[DataMember]
public Uri Web { get; set; }
}
[DataContract(Namespace = Constants.DataNamespace)]
public class Person : Entity
{
[DataMember]
public string Surname { get; set; }
[DataMember]
public string GivenName { get; set; }
/// <summary>
/// Date of Birth.
/// This is optional.
/// </summary>
[DataMember]
public DateTime? DOB { get; set; }
public override string ToString()
{
return Surname + ", " + GivenName;
}
}
NSwagStudio 生成的 C# 客户端代码
[System.CodeDom.Compiler.GeneratedCode
("NJsonSchema", "10.1.4.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Entity
{
[Newtonsoft.Json.JsonProperty("id",
Required = Newtonsoft.Json.Required.DisallowNull,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Guid Id { get; set; }
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("addresses",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.ICollection<Address> Addresses { get; set; }
[Newtonsoft.Json.JsonProperty("phoneNumbers",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.ICollection<PhoneNumber> PhoneNumbers { get; set; }
[Newtonsoft.Json.JsonProperty("web",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Uri Web { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode
("NJsonSchema", "10.1.4.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Person
{
[Newtonsoft.Json.JsonProperty("surname",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Surname { get; set; }
[Newtonsoft.Json.JsonProperty("givenName",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string GivenName { get; set; }
[Newtonsoft.Json.JsonProperty("dob",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.DateTimeOffset? Dob { get; set; }
[Newtonsoft.Json.JsonProperty("id",
Required = Newtonsoft.Json.Required.DisallowNull,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Guid Id { get; set; }
[Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("addresses",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.ICollection<Address> Addresses { get; set; }
[Newtonsoft.Json.JsonProperty("phoneNumbers",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.ICollection<PhoneNumber> PhoneNumbers { get; set; }
[Newtonsoft.Json.JsonProperty("web",
Required = Newtonsoft.Json.Required.Default,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Uri Web { get; set; }
}
WebApiClientGen 生成的 C# 客户端代码
/// <summary>
/// Base class of company and person
/// </summary>
public class Entity : object
{
/// <summary>
/// Multiple addresses
/// </summary>
public DemoWebApi.DemoData.Client.Address[] Addresses { get; set; }
public System.Guid Id { get; set; }
/// <summary>
/// Name of the entity.
/// </summary>
[System.ComponentModel.DataAnnotations.RequiredAttribute()]
public string Name { get; set; }
public DemoWebApi.DemoData.Client.PhoneNumber[] PhoneNumbers { get; set; }
}
public class Person : DemoWebApi.DemoData.Client.Entity
{
/// <summary>
/// Date of Birth.
/// This is optional.
/// </summary>
public System.Nullable<System.DateTime> DOB { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
}
NSwagStudio 生成的 TypeScript 客户端代码
export interface IEntity {
id?: string;
name: string;
addresses?: Address[] | undefined;
phoneNumbers?: PhoneNumber[] | undefined;
web?: string | undefined;
}
interface IPerson {
surname?: string | undefined;
givenName?: string | undefined;
dob?: Date | undefined;
id?: string;
name: string;
addresses?: Address[] | undefined;
phoneNumbers?: PhoneNumber[] | undefined;
web?: string | undefined;
}
WebApiClientGen 生成的 TypeScript 客户端代码
/**
* Base class of company and person
*/
export interface Entity {
/**
* Multiple addresses
*/
addresses?: Array<DemoWebApi_DemoData_Client.Address>;
id?: string;
/**
* Name of the entity.
*/
name: string;
phoneNumbers?: Array<DemoWebApi_DemoData_Client.PhoneNumber>;
web?: string;
}
export interface Person extends DemoWebApi_DemoData_Client.Entity {
/**
* Date of Birth.
* This is optional.
*/
dob?: Date;
givenName?: string;
surname?: string;
}
枚举类型 PhoneType 和 Days
/// <summary>
/// Phone type
/// Tel, Mobile, Skyp and Fax
///
/// </summary>
[DataContract(Namespace = Constants.DataNamespace)]
public enum PhoneType
{
/// <summary>
/// Land line
/// </summary>
[EnumMember]
Tel = 0,
/// <summary>
/// Mobile phone
/// </summary>
[EnumMember]
Mobile = 1,
[EnumMember]
Skype = 2,
[EnumMember]
Fax = 3,
}
[DataContract(Namespace = Constants.DataNamespace)]
public enum Days
{
[EnumMember]
Sat = 1,
[EnumMember]
Sun,
[EnumMember]
Mon,
[EnumMember]
Tue,
[EnumMember]
Wed,
/// <summary>
/// Thursday
/// </summary>
[EnumMember]
Thu,
[EnumMember]
Fri
};
NSwagStudio 生成的 C# 客户端代码
[System.CodeDom.Compiler.GeneratedCode
("NJsonSchema", "10.1.4.0 (Newtonsoft.Json v12.0.0.0)")]
public enum PhoneType
{
_0 = 0,
_1 = 1,
_2 = 2,
_3 = 3,
}
[System.CodeDom.Compiler.GeneratedCode
("NJsonSchema", "10.1.4.0 (Newtonsoft.Json v12.0.0.0)")]
public enum Days
{
_1 = 1,
_2 = 2,
_3 = 3,
_4 = 4,
_5 = 5,
_6 = 6,
_7 = 7,
}
WebApiClientGen 生成的 C# 客户端代码
/// <summary>
/// Phone type
/// Tel, Mobile, Skyp and Fax
///
/// </summary>
public enum PhoneType
{
/// <summary>
/// Land line
/// </summary>
Tel,
/// <summary>
/// Mobile phone
/// </summary>
Mobile,
Skype,
Fax,
}
public enum Days
{
Sat = 1,
Sun = 2,
Mon = 3,
Tue = 4,
Wed = 5,
/// <summary>
/// Thursday
/// </summary>
Thu = 6,
Fri = 7,
}
NSwagStudio 生成的 TypeScript 客户端代码
enum PhoneType {
_0 = 0,
_1 = 1,
_2 = 2,
_3 = 3,
}
enum Days {
_1 = 1,
_2 = 2,
_3 = 3,
_4 = 4,
_5 = 5,
_6 = 6,
_7 = 7,
}
WebApiClientGen 生成的 TypeScript 客户端
/**
* Phone type
* Tel, Mobile, Skyp and Fax
*/
export enum PhoneType {
/**
* Land line
*/
Tel,
/**
* Mobile phone
*/
Mobile,
Skype,
Fax
}
export enum Days {
Sat = 1,
Sun = 2,
Mon = 3,
Tue = 4,
Wed = 5,
/**
* Thursday
*/
Thu = 6,
Fri = 7
}
控制器操作 HeroesController.Get(id)
/// <summary>
/// Get a hero.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
public Hero Get(long id)
{...
NSwagStudio 生成的 C# 客户端代码
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<Hero> HeroesGetAsync(long id)
{
return HeroesGetAsync(id, System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that
/// can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>Success</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<Hero>
HeroesGetAsync(long id, System.Threading.CancellationToken cancellationToken)
{
if (id == null)
throw new System.ArgumentNullException("id");
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') :
"").Append("/api/Heroes/{id}");
urlBuilder_.Replace("{id}", System.Uri.EscapeDataString
(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
var client_ = _httpClient;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add
(System.Net.Http.Headers.
MediaTypeWithQualityHeaderValue.Parse("text/plain"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri =
new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_,
System.Net.Http.HttpCompletionOption.ResponseHeadersRead,
cancellationToken).ConfigureAwait(false);
try
{
var headers_ = System.Linq.Enumerable.ToDictionary
(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = ((int)response_.StatusCode).ToString();
if (status_ == "200")
{
var objectResponse_ =
await ReadObjectResponseAsync<Hero>(response_, headers_).
ConfigureAwait(false);
return objectResponse_.Object;
}
else
if (status_ != "200" && status_ != "204")
{
var responseData_ = response_.Content == null ?
null : await response_.Content.ReadAsStringAsync().
ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response
was not expected (" + (int)response_.StatusCode + ").",
(int)response_.StatusCode, responseData_, headers_, null);
}
return default(Hero);
}
finally
{
if (response_ != null)
response_.Dispose();
}
}
}
finally
{
}
}
WebApiClientGen 生成的 C# 客户端代码
/// <summary>
/// Get a hero.
/// GET api/Heroes/{id}
/// </summary>
public async Task<DemoWebApi.Controllers.Client.Hero> GetHeroAsync(long id)
{
var requestUri = new Uri(this.baseUri, "api/Heroes/"+id);
var responseMessage = await client.GetAsync(requestUri);
try
{
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.Controllers.Client.Hero>(jsonReader);
}
}
finally
{
responseMessage.Dispose();
}
}
NSwagStudio 生成的 TypeScript Fetch API 代码
/**
* @return Success
*/
heroesGet(id: number): Promise<Hero> {
let url_ = this.baseUrl + "/api/Heroes/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "text/plain"
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processHeroesGet(_response);
});
}
protected processHeroesGet(response: Response): Promise<Hero> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach)
{ response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ?
null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = Hero.fromJS(resultData200);
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException
("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Hero>(<any>null);
}
WebApiClientGen 生成的 TypeScript Fetch API 代码
/**
* Get a hero.
* GET api/Heroes/{id}
*/
getHero(id: number): Promise<DemoWebApi_Controllers_Client.Hero> {
return fetch(this.baseUri + 'api/Heroes/' + id,
{method: 'get'}).then(d => d.json());
}
NSwagStudio 生成的 TypeScript Angular API 代码
/**
* @return Success
*/
heroesGet(id: number): Observable<Hero> {
let url_ = this.baseUrl + "/api/Heroes/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_ : any = {
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Accept": "text/plain"
})
};
return this.http.request("get", url_, options_).pipe
(_observableMergeMap((response_ : any) => {
return this.processHeroesGet(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processHeroesGet(<any>response_);
} catch (e) {
return <Observable<Hero>><any>_observableThrow(e);
}
} else
return <Observable<Hero>><any>_observableThrow(response_);
}));
}
protected processHeroesGet(response: HttpResponseBase): Observable<Hero> {
const status = response.status;
const responseBlob =
response instanceof HttpResponse ? response.body :
(<any>response).error instanceof Blob ? (<any>response).error : undefined;
let _headers: any = {};
if (response.headers) { for (let key of response.headers.keys())
{ _headers[key] = response.headers.get(key); }};
if (status === 200) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result200: any = null;
let resultData200 = _responseText === "" ?
null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = Hero.fromJS(resultData200);
return _observableOf(result200);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException("An unexpected server error occurred.",
status, _responseText, _headers);
}));
}
return _observableOf<Hero>(<any>null);
}
WebApiClientGen 生成的 TypeScript Angular API 代码
/**
* Get a hero.
* GET api/Heroes/{id}
*/
getById(id: number): Observable<DemoWebApi_Controllers_Client.Hero> {
return this.http.get<DemoWebApi_Controllers_Client.Hero>
(this.baseUri + 'api/Heroes/' + id);
}
历史
- 2020 年 2 月 24 日:初始版本