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

来源:https://www.cnblogs.com/langda/archive/2019/06/24/11079802.html
-Advertisement-
Play Games

本文介紹“ 為ASP.NET Web API生成TypeScript客戶端API ”,重點介紹Angular 2+代碼示例和各自的SDLC。如果您正在開發.NET Core Web API後端,則可能需要閱讀為ASP.NET Core Web API生成C#Client API。 背景 自WebAp ...


本文介紹“ 為ASP.NET Web API生成TypeScript客戶端API ”,重點介紹Angular 2+代碼示例和各自的SDLC。如果您正在開發.NET Core Web API後端,則可能需要閱讀為ASP.NET Core Web API生成C#Client API。

背景

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

在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的團隊。

為了跟進這種開發客戶端程式的新方法,最好有一個ASP.NET Web API項目。您可以使用現有項目,也可以創建演示項目

使用代碼

本文重點介紹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應用程式的典型開發周期。

這是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項目引用。

此外,用於觸發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+。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、模板常用語法 1.1 變數 1. 符號:{{ }} 表示變數,在模板渲染的時候替換成值 使用方式:{{ 變數名 }}:變數名由字母數字和下劃線組成 點(.)在模板語言中有特殊的含義,用來獲取對象的相應屬性值 註意:當模板系統遇到一個(.)時,會按照如下的順序去查詢: 在字典中查詢 屬性或者方法 ...
  • 6.9 time 模塊 1、時間戳(以秒計算) 2、格式化的字元串 3、struct_time()對象 4、 mktime( t ) : 將一個struct_time轉化為時間戳 5、time.strptime() 6、time.asctime() 7、time.ctime() 6.10 datet ...
  • 一、希爾排序的介紹 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。 希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的記錄越來越多,當增量減至1時,整個文件恰被分 ...
  • 本篇內容說說Spring對切麵的支持,如何把普通類聲明為一個切麵,以及如何使用註解創建切麵,主要有以下幾點內容: 什麼是面向切麵編程 選擇連接點 使用註解創建切麵 在XML中聲明切麵 ...
  • 緩存要解決的問題是速度的問題,使用緩存的目的是為了減少對物理資源的訪問,緩存大量的應用在軟硬體的方方面面 ...
  • 1.面試要看資料下菜,人家不會的不要問 2.要調整自己做為面試官的心態,不是讓你去打敗面試者 3.要看薪酬調整面試提問的難易程度。 ...
  • 指定某個屬性使用特定的時間格式 Intro 是 .NET 下最受歡迎 JSON 操作庫,原為 後改名為 ,之前一直推薦大家使用,除了性能好之外,主要是功能豐富,基本滿足所有的可能用到的場景(不區分小寫,現在還不行,,)。 遇到這樣一個需求,全局使用一種時間格式,某些屬性使用特殊的時間格式,這裡以一個 ...
  • 上面的是父類,然後子類實現父類的構造函數,另外建一個類起名為SocketServer,繼承上一個類SocketHelper(此時SocketHelper為父類): OK,這樣就是子類繼承父類的構造函數 子類繼承父類的時候,其基類的構造函數,子類也要必須實現,不然會報錯 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...