使用ASP.NET Web API和Web API Client Gen使Angular 2應用程序的開發更加高效

本文介紹「 爲ASP.NET Web API生成TypeScript客戶端API 」,重點介紹Angular 2+代碼示例和各自的SDLC。若是您正在開發.NET Core Web API後端,則可能須要閱讀爲ASP.NET Core Web API生成C#Client API。css

背景

WebApiClientGenAngular 2仍然在RC2時,2016年6月v1.9.0-beta 以來,對Angular2的支持已經可用而且在WebApiClientGenv2.0中提供了對Angular 2產品發佈的支持但願NG2的發展不會如此頻繁地破壞個人CodeGen和個人Web前端應用程序。:)html

在2016年9月底發佈Angular 2的第一個產品發佈幾周後,我碰巧啓動了一個使用Angular2的主要Web應用程序項目,所以我WebApiClientGen對NG2應用程序開發的使用方法幾乎相同前端

推定

  1. 您正在開發ASP.NET Web API 2.x應用程序,並將基於Angular 2+爲SPA開發TypeScript庫。
  2. 您和其餘開發人員喜歡在服務器端和客戶端都經過強類型數據和函數進行高度抽象。
  3. Web API和實體框架代碼優先使用POCO類,您可能不但願將全部數據類和成員發佈到客戶端程序源碼

而且可選地,若是您或您的團隊支持基於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; }
    }
}

 

步驟0:將NuGet包WebApiClientGen安裝到Web API項目

安裝還將安裝依賴的NuGet包Fonlow.TypeScriptCodeDOMFonlow.Poco2Ts項目引用。api

此外,用於觸發CodeGen的CodeGenController.cs被添加到Web API項目的Controllers文件夾中。數組

CodeGenController只在調試版本開發過程當中應該是可用的,由於客戶端API應該用於Web API的每一個版本生成一次。

提示

若是您正在使用@ angular / http中定義的Angular2的Http服務,那麼您應該使用WebApiClientGenv2.2.5。若是您使用的HttpClient是@ angular / common / http中定義的Angular 4.3中可用服務,而且在Angular 5中已棄用,那麼您應該使用WebApiClientGenv2.3.0。

第1步:準備JSON配置數據

下面的JSON配置數據是POSTCodeGen 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默認狀況下,WebApiClientGen2.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選擇加入。您可使用任何方法或組合方法。

第2步:運行Web API項目的DEBUG構建

步驟3:POST JSON配置數據以觸發客戶端API代碼的生成

在IIS Express上的IDE中運行Web項目。

而後使用CurlPoster或任何您喜歡的客戶端工具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配置文件以觸發代碼生成

使用ASP.NET Web API和Web API Client Gen使Angular 2應用程序的開發更加高效

基本上,您能夠製做Web API代碼,包括API控制器和數據模型,而後執行CreateClientApi.ps1而已!WebApiClientGenCreateClientApi.ps1將爲您完成剩下的工做。

發佈客戶端API庫

如今您在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大小寫,則能夠在WebApiConfigWeb 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']
})

 

使用ASP.NET Web API和Web API Client Gen使Angular 2應用程序的開發更加高效

使用ASP.NET Web API和Web API Client Gen使Angular 2應用程序的開發更加高效

經過IDE進行設計時類型檢查,並在生成的代碼之上進行編譯時類型檢查,能夠更輕鬆地提升客戶端編程的效率和產品質量。

不要作計算機能夠作的事情,讓計算機爲咱們努力工做。咱們的工做是爲客戶提供自動化解決方案,所以最好先自行完成本身的工做。

興趣點

在典型的角2個教程,包括官方的一個  這已經存檔,做者常常督促應用程序開發者製做一個服務類,如「 HeroService」,而黃金法則是:永遠委託給配套服務類的數據訪問

WebApiClientGen爲您生成此服務類DemoWebApi_Controllers_Client.Heroes,它將使用真正的Web API而不是內存中的Web API。在開發過程當中WebApiClientGen,我建立了一個演示項目DemoAngular2各自用於測試的Web API控制器

典型的教程還建議使用模擬服務進行單元測試。WebApiClientGen使用真正的Web API服務要便宜得多,所以您可能不須要建立模擬服務。您應該在開發期間平衡使用模擬或實際服務的成本/收益,具體取決於您的上下文。一般,若是您的團隊已經可以在每臺開發機器中使用持續集成環境,那麼使用真實服務運行測試可能很是無縫且快速。

在典型的SDLC中,在初始設置以後,如下是開發Web API和NG2應用程序的典型步驟:

  1. 升級Web API
  2. 運行CreateClientApi.ps1以更新TypeScript for NG2中的客戶端API。
  3. 使用生成的TypeScript客戶端API代碼或C#客戶端API代碼,在Web API更新時建立新的集成測試用例。
  4. 相應地修改NG2應用程序。
  5. 要進行測試,請運行StartWebApi.ps1以啓動Web API,並在VS IDE中運行NG2應用程序。

提示

對於第5步,有其餘選擇。例如,您可使用VS IDE同時以調試模式啓動Web API和NG2應用程序。一些開發人員可能更喜歡使用「 npm start」。

本文最初是爲Angular 2編寫的,具備Http服務。Angular 4.3中引入了WebApiClientGen2.3.0支持HttpClient而且生成的API在接口級別保持不變。這使得從過期的Http服務遷移到HttpClient服務至關容易或無縫,與Angular應用程序編程相比,不使用生成的API而是直接使用Http服務。

順便說一句,若是你沒有完成向Angular 5的遷移,那麼這篇文章可能有所幫助:  升級到Angular 5和HttpClient若是您使用的是Angular 6,則應使用WebApiClientGen2.4.0+。

相關文章
相關標籤/搜索