本文介紹「 爲ASP.NET Web API生成TypeScript客戶端API 」,重點介紹Angular 2+代碼示例和各自的SDLC。若是您正在開發.NET Core Web API後端,則可能須要閱讀爲ASP.NET Core Web API生成C#Client API。css
自WebApiClientGen
Angular 2仍然在RC2時,自2016年6月v1.9.0-beta 以來,對Angular2的支持已經可用。而且在WebApiClientGen
v2.0中提供了對Angular 2產品發佈的支持。但願NG2的發展不會如此頻繁地破壞個人CodeGen和個人Web前端應用程序。:)html
在2016年9月底發佈Angular 2的第一個產品發佈幾周後,我碰巧啓動了一個使用Angular2的主要Web應用程序項目,所以我WebApiClientGen
對NG2應用程序開發的使用方法幾乎相同。前端
而且可選地,若是您或您的團隊支持基於Trunk的開發,那麼更好,由於使用的設計WebApiClientGen
和工做流程WebApiClientGen
假設基於Trunk的開發,這比其餘分支策略(如Feature Branching和GitFlow等)更有效。對於熟練掌握TDD的團隊。shell
爲了跟進這種開發客戶端程序的新方法,最好有一個ASP.NET Web API項目。您可使用現有項目,也能夠建立演示項目。npm
本文重點介紹Angular 2+的代碼示例。假設您有一個ASP.NET Web API項目和一個Angular2項目做爲VS解決方案中的兄弟項目。若是你將它們分開,那麼爲了使開發步驟無縫地編寫腳本應該不難。編程
我認爲您已閱讀「 爲ASP.NET Web API生成TypeScript客戶端API 」。爲jQuery生成客戶端API的步驟幾乎與爲Angular 2生成客戶端API的步驟相同。演示TypeScript代碼基於TUTORIAL:TOUR OF HEROES,許多人從中學習了Angular2。所以,您將可以看到如何WebApiClientGen
適應並改進Angular2應用程序的典型開發週期。json
這是Web API代碼:後端
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; using System.Runtime.Serialization; using System.Collections.Concurrent; namespace DemoWebApi.Controllers { [RoutePrefix("api/Heroes")] public class HeroesController : ApiController { public Hero[] Get() { return HeroesData.Instance.Dic.Values.ToArray(); } public Hero Get(long id) { Hero r; HeroesData.Instance.Dic.TryGetValue(id, out r); return r; } public void Delete(long id) { Hero r; HeroesData.Instance.Dic.TryRemove(id, out r); } public Hero Post(string name) { var max = HeroesData.Instance.Dic.Keys.Max(); var hero = new Hero { Id = max + 1, Name = name }; HeroesData.Instance.Dic.TryAdd(max + 1, hero); return hero; } public Hero Put(Hero hero) { HeroesData.Instance.Dic[hero.Id] = hero; return hero; } [HttpGet] public Hero[] Search(string name) { return HeroesData.Instance.Dic.Values.Where(d => d.Name.Contains(name)).ToArray(); } } [DataContract(Namespace = DemoWebApi.DemoData.Constants.DataNamespace)] public class Hero { [DataMember] public long Id { get; set; } [DataMember] public string Name { get; set; } } public sealed class HeroesData { private static readonly Lazy<HeroesData> lazy = new Lazy<HeroesData>(() => new HeroesData()); public static HeroesData Instance { get { return lazy.Value; } } private HeroesData() { Dic = new ConcurrentDictionary<long, Hero>(new KeyValuePair<long, Hero>[] { new KeyValuePair<long, Hero>(11, new Hero {Id=11, Name="Mr. Nice" }), new KeyValuePair<long, Hero>(12, new Hero {Id=12, Name="Narco" }), new KeyValuePair<long, Hero>(13, new Hero {Id=13, Name="Bombasto" }), new KeyValuePair<long, Hero>(14, new Hero {Id=14, Name="Celeritas" }), new KeyValuePair<long, Hero>(15, new Hero {Id=15, Name="Magneta" }), new KeyValuePair<long, Hero>(16, new Hero {Id=16, Name="RubberMan" }), new KeyValuePair<long, Hero>(17, new Hero {Id=17, Name="Dynama" }), new KeyValuePair<long, Hero>(18, new Hero {Id=18, Name="Dr IQ" }), new KeyValuePair<long, Hero>(19, new Hero {Id=19, Name="Magma" }), new KeyValuePair<long, Hero>(20, new Hero {Id=29, Name="Tornado" }), }); } public ConcurrentDictionary<long, Hero> Dic { get; private set; } } }
安裝還將安裝依賴的NuGet包Fonlow.TypeScriptCodeDOM
和Fonlow.Poco2Ts
項目引用。api
此外,用於觸發CodeGen的CodeGenController.cs被添加到Web API項目的Controllers文件夾中。數組
將CodeGenController
只在調試版本開發過程當中應該是可用的,由於客戶端API應該用於Web API的每一個版本生成一次。
若是您正在使用@ angular / http中定義的Angular2的Http服務,那麼您應該使用WebApiClientGen
v2.2.5。若是您使用的HttpClient
是@ angular / common / http中定義的Angular 4.3中可用的服務,而且在Angular 5中已棄用,那麼您應該使用WebApiClientGen
v2.3.0。
下面的JSON配置數據是POST
CodeGen Web API:
{ "ApiSelections": { "ExcludedControllerNames": [ "DemoWebApi.Controllers.Account" ], "DataModelAssemblyNames": [ "DemoWebApi.DemoData", "DemoWebApi" ], "CherryPickingMethods": 1 }, "ClientApiOutputs": { "ClientLibraryProjectFolderName": "DemoWebApi.ClientApi", "GenerateBothAsyncAndSync": true, "CamelCase": true, "TypeScriptNG2Folder": "..\\DemoAngular2\\clientapi", "NGVersion" : 5 } }
Angular 6正在使用RxJS v6,它引入了一些重大變化,特別是對於導入Observable
。默認狀況下,WebApiClientGen
2.4和更高版本默認將導入聲明爲import { Observable } from 'rxjs';
。若是您仍在使用Angular 5.x,則須要"NGVersion" : 5
在JSON配置中聲明,所以生成的代碼中的導入將是更多詳細信息,import { Observable } from 'rxjs/Observable'; .
請參閱RxJS v5.x至v6更新指南和RxJS:版本6的TSLint規則。
您應確保「 TypeScriptNG2Folder
」存在的文件夾存在,由於WebApiClientGen
不會爲您建立此文件夾,這是設計使然。
建議到JSON配置數據保存到與文件相似的這一個位於Web API項目文件夾。
若是您在Web API項目中定義了全部POCO類,則應將Web API項目的程序集名稱放在「 DataModelAssemblyNames
」 數組中。若是您有一些專用的數據模型程序集能夠很好地分離關注點,那麼您應該將相應的程序集名稱放入數組中。您能夠選擇爲jQuery或NG2或C#客戶端API代碼生成TypeScript客戶端API代碼,或者所有三種。
「 TypeScriptNG2Folder
」是Angular2項目的絕對路徑或相對路徑。例如,「 .. \\ DemoAngular2 \\ ClientApi 」表示DemoAngular2
做爲Web API項目的兄弟項目建立的Angular 2項目「 」。
在CodeGen
根據「從POCO類生成強類型打字稿接口CherryPickingMethods
,其在下面的文檔註釋描述」:
/// <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
。
基本上,每當Web API更新時,您只須要步驟2來生成客戶端API,由於您不須要每次都安裝NuGet包或建立新的JSON配置數據。
編寫一些批處理腳原本啓動Web API和POST JSON配置數據應該不難。爲了您的方便,我實際起草了一個:Powershell腳本文件CreateClientApi.ps1,它在IIS Express上啓動Web(API)項目,而後發佈JSON配置文件以觸發代碼生成。
基本上,您能夠製做Web API代碼,包括API控制器和數據模型,而後執行CreateClientApi.ps1。而已!WebApiClientGen
和CreateClientApi.ps1將爲您完成剩下的工做。
如今您在TypeScript中生成了客戶端API,相似於如下示例:
import { Injectable, Inject } from '@angular/core'; import { Http, Headers, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; export 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 PhoneNumber { fullNumber?: string; phoneType?: DemoWebApi_DemoData_Client.PhoneType; } export enum PhoneType {Tel, Mobile, Skype, Fax} 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>; phoneNumbers?: Array<DemoWebApi_DemoData_Client.PhoneNumber>; } export interface Person extends DemoWebApi_DemoData_Client.Entity { surname?: string; givenName?: string; dob?: Date; } export interface Company extends DemoWebApi_DemoData_Client.Entity { businessNumber?: string; businessNumberType?: string; textMatrix?: Array<Array<string>>; int2DJagged?: Array<Array<number>>; int2D?: number[][]; lines?: Array<string>; } export interface MyPeopleDic { dic?: {[id: string]: DemoWebApi_DemoData_Client.Person }; anotherDic?: {[id: string]: string }; intDic?: {[id: number]: string }; } } export namespace DemoWebApi_DemoData_Another_Client { export interface MyPoint { x: number; y: number; } } export namespace DemoWebApi_Controllers_Client { export interface FileResult { fileNames?: Array<string>; submitter?: string; } export interface Hero { id?: number; name?: string; } } @Injectable() export class Heroes { constructor(@Inject('baseUri') private baseUri: string = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + '/', private http: Http){ } /** * Get all heroes. * GET api/Heroes * @return {Array<DemoWebApi_Controllers_Client.Hero>} */ get(): Observable<Array<DemoWebApi_Controllers_Client.Hero>>{ return this.http.get(this.baseUri + 'api/Heroes').map(response=> response.json()); } /** * Get a hero. * GET api/Heroes/{id} * @param {number} id * @return {DemoWebApi_Controllers_Client.Hero} */ getById(id: number): Observable<DemoWebApi_Controllers_Client.Hero>{ return this.http.get(this.baseUri + 'api/Heroes/'+id).map(response=> response.json()); } /** * DELETE api/Heroes/{id} * @param {number} id * @return {void} */ delete(id: number): Observable<Response>{ return this.http.delete(this.baseUri + 'api/Heroes/'+id); } /** * Add a hero * POST api/Heroes?name={name} * @param {string} name * @return {DemoWebApi_Controllers_Client.Hero} */ post(name: string): Observable<DemoWebApi_Controllers_Client.Hero>{ return this.http.post(this.baseUri + 'api/Heroes?name='+encodeURIComponent(name), JSON.stringify(null), { headers: new Headers({ 'Content-Type': 'text/plain;charset=UTF-8' }) }).map(response=> response.json()); } /** * Update hero. * PUT api/Heroes * @param {DemoWebApi_Controllers_Client.Hero} hero * @return {DemoWebApi_Controllers_Client.Hero} */ put(hero: DemoWebApi_Controllers_Client.Hero): Observable<DemoWebApi_Controllers_Client.Hero>{ return this.http.put(this.baseUri + 'api/Heroes', JSON.stringify(hero), { headers: new Headers({ 'Content-Type': 'text/plain;charset=UTF-8' }) }).map(response=> response.json()); } /** * Search heroes * GET api/Heroes?name={name} * @param {string} name keyword contained in hero name. * @return {Array<DemoWebApi_Controllers_Client.Hero>} Hero array matching the keyword. */ search(name: string): Observable<Array<DemoWebApi_Controllers_Client.Hero>>{ return this.http.get(this.baseUri + 'api/Heroes?name='+ encodeURIComponent(name)).map(response=> response.json()); } }
若是您但願生成的TypeScript代碼符合JavaScript和JSON的camel大小寫,則能夠在WebApiConfig
Web API的腳手架代碼類中添加如下行:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
而後屬性名稱和函數名稱將在camel大小寫中,前提是C#中的相應名稱都在Pascal大小寫中。有關詳細信息,請查看camelCasing或PascalCasing。
在像Visual Studio這樣的正常文本編輯器中編寫客戶端代碼時,您可能會得到很好的智能感知。
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import * as namespaces from '../clientapi/WebApiNG2ClientAuto'; import DemoWebApi_Controllers_Client = namespaces.DemoWebApi_Controllers_Client; @Component({ moduleId: module.id, selector: 'my-heroes', templateUrl: 'heroes.component.html', styleUrls: ['heroes.component.css'] })
經過IDE進行設計時類型檢查,並在生成的代碼之上進行編譯時類型檢查,能夠更輕鬆地提升客戶端編程的效率和產品質量。
不要作計算機能夠作的事情,讓計算機爲咱們努力工做。咱們的工做是爲客戶提供自動化解決方案,所以最好先自行完成本身的工做。
在典型的角2個教程,包括官方的一個 這已經存檔,做者常常督促應用程序開發者製做一個服務類,如「 HeroService
」,而黃金法則是:永遠委託給配套服務類的數據訪問。
WebApiClientGen
爲您生成此服務類DemoWebApi_Controllers_Client.Heroes
,它將使用真正的Web API而不是內存中的Web API。在開發過程當中WebApiClientGen
,我建立了一個演示項目DemoAngular2和各自用於測試的Web API控制器。
典型的教程還建議使用模擬服務進行單元測試。WebApiClientGen
使用真正的Web API服務要便宜得多,所以您可能不須要建立模擬服務。您應該在開發期間平衡使用模擬或實際服務的成本/收益,具體取決於您的上下文。一般,若是您的團隊已經可以在每臺開發機器中使用持續集成環境,那麼使用真實服務運行測試可能很是無縫且快速。
在典型的SDLC中,在初始設置以後,如下是開發Web API和NG2應用程序的典型步驟:
對於第5步,有其餘選擇。例如,您可使用VS IDE同時以調試模式啓動Web API和NG2應用程序。一些開發人員可能更喜歡使用「 npm start
」。
本文最初是爲Angular 2編寫的,具備Http服務。Angular 4.3中引入了WebApiClientGen
2.3.0支持HttpClient
。而且生成的API在接口級別保持不變。這使得從過期的Http服務遷移到HttpClient服務至關容易或無縫,與Angular應用程序編程相比,不使用生成的API而是直接使用Http服務。
順便說一句,若是你沒有完成向Angular 5的遷移,那麼這篇文章可能有所幫助: 升級到Angular 5和HttpClient。若是您使用的是Angular 6,則應使用WebApiClientGen
2.4.0+。