【譯】Visual Studio 2022 中的 Web API 開發

2023-07-24 06:00:33

  在 Visual Studio 2022 中,Web 開發人員的主要場景之一是使用 ASP.NET Core 建立 Web API。在 Visual Studio 2022 17.6 的最新預覽版中,我們新增了一些更新,以便在開發 API 時更高效。在這篇文章中,我們將介紹一個從頭開始的新的 API 開發的範例場景,並在此過程中指出新特性。

  我們將在這篇文章中介紹的一些新特性包括:

  * 帶有整合使用者端的 HTTP 編輯器

  * API Endpoint 資源管理器

  * 腳手架

  * Entity Framework 工具

  在這篇文章中,我們將展示從一個新專案開始開發一個完整的 Web API 的完整的端到端。下面是這篇文章的概要。

  * 建立一個新的 API 專案

  * 向專案中新增模型

  * 使用腳手架生成 API

  * 使用 API Endpoint 資源管理器

  * 使用 HTTP 編輯器

開始

  要在 Visual Studio 中開發 Web API,第一步是建立一個新專案。在 Visual Studio 2022 中,您可以使用「新建專案」對話方塊建立一個新專案。在這篇文章中,我們將要為一個虛構的外賣餐廳建立一個 ASP.NET Core Web API。按照教學,建立一個名為 MyRestaurantService 的專案,並在附加資訊頁面中選擇以下選項。

  在這個範例中,我們使用的是 Endpoint,而不是基於 Controller 的 API。API Endpoint 和基於 Controller 的 API 的步驟是相同的,只是適用場合不同。

  既然已經建立了專案,我們要做的第一件事就是為我們想要通過 API 公開的物件新增一些模型類。我們將需要向專案新增以下類/列舉。在下表中,您將看到要建立的物件列表。

名稱

描述

Contact

獲取正在下訂單的客戶

MenuItem

表示餐廳選單中的選單項

MenuItemCategory

表示選單項類別的列舉

MenuItemOrdered

表示已新增到客戶訂單中的專案

OrderStatus

表示已提交訂單的狀態

PaymentMethod

表示用於訂單的付款方式的列舉

TogoOrder

表示已下單的訂單

  新增的檔案要麼是標準 POCO 類,要麼是列舉。例如,Contact.cs:

namespace MyRestaurantApi; 
public class Contact {
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? Phone { get; set; }
}

  這些物件表示一組資料,可用於向終端使用者顯示選單以及提交 Togo 訂單。現在我們已經新增了這些模型類,下一步是在 Visual Studio 中使用「腳手架」來建立 API Endpoint 並連線 Entity Framework DbContext。我們需要建立4組 API Endpoint。

  * Contact

  * MenuItem

  * MenuItemsOrdered

  * TogoOrder

  對於每個模型類,我們將使用腳手架來處理生成初始 API Endpoint 的艱苦工作。讓我們開始吧。

Visual Studio 中的腳手架

  要開始使用腳手架,請右鍵單擊 ASP.NET Core Web 專案 MyRestaurantApi,然後選擇 Add > New Scaffolded Item。請參見下圖。

  呼叫此選單項後,將顯示腳手架對話方塊。在這個對話方塊中,我們將選擇要執行的腳手架型別然後設定它。對於這個專案,我們將使用 API with read/write endpoints, using Entity Framework 腳手架。

  選擇此選項後,您將看到一個新對話方塊,提示您設定腳手架選項。下表總結了主要備選方案。

選項

描述

Model class

腳手架將生成讀取/寫入所選模型類值的 API

Endpoint class

應該向其中寫入新終結點的類。如果選擇現有類,則終結點將新增到該類。您還可以使用 + 按鈕建立一個新類

DbContext class

Entity Framework DbContext,它將管理對資料庫的讀/寫操作。您可以使用 + 按鈕建立一個新的 DbContext

Database Provider

要使用的資料庫提供程式,這將由要儲存此資料的資料庫決定

  我們將從 Contact 模型開始。在這種情況下,我們將指定 Contact 類作為模型類,建立一個新的終結點類以及一個新 DbContext。我們將在此範例中使用 Sqlite,但您可以根據需要選擇列出的任何提供商。下面是設定了這些選項的對話方塊。

  使用+按鈕建立新的終節點類和 DbContext 類。可以使用在為這兩個選項單擊+按鈕時提供的預設值。下一步是單擊 Add 按鈕。單擊 Add 後,將使用 NuGet 安裝腳手架工具,然後呼叫它來生成新檔案。下面列出了新增或修改的檔案。

  Project 檔案——修改專案檔案以新增支援更改所需的包參照以及腳手架工具本身的包參照。

  Program.cs——修改 Program.cs 檔案以新增 EF DbContext 以及註冊新的終結點類。

  appSettings. json——新增連線字串。

  ContactEndpoints.cs——這是一個新新增的類,用於處理 API 請求/響應。

  下面是為 ContactEndpoints 類生成的程式碼。

using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.OpenApi;
using MyRestaurantApi.Data;
namespace MyRestaurantApi;

public static class ContactEndpoints
{
    public static void MapContactEndpoints (this IEndpointRouteBuilder routes)
    {
        var group = routes.MapGroup("/api/Contact").WithTags(nameof(Contact));

        group.MapGet("/", async (MyRestaurantApiContext db) =>
        {
            return await db.Contact.ToListAsync();
        })
        .WithName("GetAllContacts")
        .WithOpenApi();

        group.MapGet("/{id}", async Task<Results<Ok<Contact>, NotFound>> (int id, MyRestaurantApiContext db) =>
        {
            return await db.Contact.AsNoTracking()
                .FirstOrDefaultAsync(model => model.Id == id)
                is Contact model
                    ? TypedResults.Ok(model)
                    : TypedResults.NotFound();
        })
        .WithName("GetContactById")
        .WithOpenApi();

        group.MapPut("/{id}", async Task<Results<Ok, NotFound>> (int id, Contact contact, MyRestaurantApiContext db) =>
        {
            var affected = await db.Contact
                .Where(model => model.Id == id)
                .ExecuteUpdateAsync(setters => setters
                  .SetProperty(m => m.Id, contact.Id)
                  .SetProperty(m => m.Name, contact.Name)
                  .SetProperty(m => m.Email, contact.Email)
                  .SetProperty(m => m.Phone, contact.Phone)
                );

            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
        .WithName("UpdateContact")
        .WithOpenApi();

        group.MapPost("/", async (Contact contact, MyRestaurantApiContext db) =>
        {
            db.Contact.Add(contact);
            await db.SaveChangesAsync();
            return TypedResults.Created($"/api/Contact/{contact.Id}",contact);
        })
        .WithName("CreateContact")
        .WithOpenApi();

        group.MapDelete("/{id}", async Task<Results<Ok, NotFound>> (int id, MyRestaurantApiContext db) =>
        {
            var affected = await db.Contact
                .Where(model => model.Id == id)
                .ExecuteDeleteAsync();

            return affected == 1 ? TypedResults.Ok() : TypedResults.NotFound();
        })
        .WithName("DeleteContact")
        .WithOpenApi();
    }
}

  這裡使用 ASP. NET Core 最小化 API,如前所述,如果你更喜歡基於 Controller 的 API,一個新的 Controller 將被新增到 Controllers 資料夾中。

  現在我們已經為 Contact 類生成了 API 終結點,我們需要為剩下的三個模型類做同樣的事情。我們將重複相同的步驟,但有一點不同。我們不選擇建立新的 DbContext,而是選擇在此步驟中建立的 DbContext,以便為其他三個步驟重用。由於要選擇現有的 DbContext,因此資料庫提供程式選項將被禁用,因為它已經在 DbContext 中設定了。現在你可以為這些類搭建 API 終結點:MenuItem, MenuItemsOrdered 和TogoOrder。

  完成這三個檔案的搭建後,您的解決方案資源管理器應該與下圖類似。

  在這個時間點上,建議構建下解決方案以確保生成的程式碼沒有問題。在這一步中,我們使用 Visual Studio 腳手架建立了一組連線到實體框架資料庫的 API 終結點。下一步是進行資料庫設定。

  現在我們有了 DbContext,我們想要向資料庫中新增一些初始資料,這通常被稱為資料播種。在我們的範例中,我們需要填 MenuItems 以及 Contact 表。我們可以通過客製化搭建過程中生成的 DbContext 類來實現這一點。我們將新增兩個方法來返回資料,並且我們將重寫  OnModelCreating 方法來向資料庫註冊資料。我們將新增的返回初始資料的方法是 GetSeedDataMenuItems 和 GetSeedDataContacts。這些方法分別返回 MenuItems 或 Contacts 的陣列。然後我們在 OnModelCreating 方法中呼叫那些方法。程式碼現在看起來應該如下所示。

public class MyRestaurantApiContext : DbContext {
    public MyRestaurantApiContext(DbContextOptions<MyRestaurantApiContext> options)
        : base(options) {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.Entity<MenuItem>().HasData(GetSeedDataMenuItems());
        modelBuilder.Entity<Contact>().HasData(GetSeedDataContacts());
    }

    public DbSet<MyRestaurantApi.Contact> Contact { get; set; } = default!;

    public DbSet<MyRestaurantApi.MenuItem> MenuItem { get; set; } = default!;

    public DbSet<MyRestaurantApi.MenuItemOrdered> MenuItemOrdered { get; set; } = default!;

    public DbSet<MyRestaurantApi.TogoOrder> TogoOrder { get; set; } = default!;
    private MenuItem[] GetSeedDataMenuItems() => new MenuItem[] {
        new MenuItem {
            Id = 1,
            Name = "Hamburger",
            Price = (decimal)3.68,
            Description = "It's a cheese burger without the cheese",
            Category = MenuItemCategory.Lunch
        },
        new MenuItem {
            Id = 2,
            Name = "Hamburger - double",
            Price = (decimal)5.70,
            Description = "It's a cheese burger without the cheese, with two beef patties",
            Category = MenuItemCategory.Lunch
        },
        new MenuItem {
            Id = 3,
            Name = "Cheeseburger",
            Price = (decimal)4.09,
            Description = "A hamburger with cheese",
            Category = MenuItemCategory.Lunch
        },
        new MenuItem {
            Id = 4,
            Name = "Cheeseburger - double",
            Price = (decimal)5.09,
            Description = "A hamburger with cheese, with two beef patties",
            Category = MenuItemCategory.Lunch
        },
        new MenuItem {
            Id = 5,
            Name = "Mushroom & Swiss burger",
            Price = (decimal)4.59,
            Description = "Mushroom & Swiss burger",
            Category = MenuItemCategory.Lunch
        },
        new MenuItem {
            Id = 6,
            Name = "Mushroom & Swiss burger - double",
            Price = (decimal)6.09,
            Description = "Mushroom & Swiss burger, with two beef patties",
            Category = MenuItemCategory.Lunch
        }
    };
    private Contact[] GetSeedDataContacts() => new Contact[] {
        new Contact {
            Id = 1,
            Name = "Sayed Hashimi",
            Email = "[email protected]",
            Phone = "555-111-2222"
        },
        new Contact {
            Id=2,
            Name = "Mads Kristensen",
            Email = "[email protected]",
            Phone = "555-111-3333"
        },
        new Contact {
            Id=3,
            Name = "Eline Barstad",
            Email = "[email protected]",
            Phone = "555-111-4444"
        },
        new Contact {
            Id=4,
            Name = "Theodore Lamy",
            Email = "[email protected]",
            Phone = "555-111-5555"
        },
        new Contact {
            Id=5,
            Name = "María Zelaya",
            Email = "[email protected]",
            Phone = "555-111-6666"
        },
        new Contact {
            Id=6,
            Name = "Kubanychbek Sagynbek",
            Email = "[email protected]",
            Phone = "555-111-7777"
        },
        new Contact {
            Id=7,
            Name = "Denise Bourgeois",
            Email = "[email protected]",
            Phone = "555-111-8888"
        },
        new Contact {
            Id=8,
            Name = "Robin Danielsen",
            Email = "[email protected]",
            Phone = "555-111-9999"
        }
    };
}

  現在,我們已經完成了獲取 DbContext 和資料模型以支援此應用程式所需的所有工作。現在我們可以繼續準備資料庫本身,到目前為止,我們只定義了一些程式碼,但沒有將任何程式碼應用於任何資料庫。我們將在 Visual Studio 中使用一些新的實體框架支援來簡化這一點。

Visual Studio 中的實體框架支援

  在使用 API 端點之前,我們需要設定資料庫。在搭建步驟中,在程式碼中定義了 EF DbContext,但是資料庫仍然沒有設定。要做到這一點,我們需要執行兩個操作。

  * 新增EF遷移

  * 更新資料庫

  在過去,您必須使用 dotnet ef 命令列工具來執行這兩個步驟。在新版  Visual Studio 中,我們新增了支援,這樣您就不需要使用命令列來進行這些操作了。在 ASP. NET Core 的 Connected Services 索引標籤中新增了支援。雙擊解決方案資源管理器中 Web 專案 MyRestaurantApi 下的  Connected Services 節點。當您進入該頁面時,它看起來如下所示。

  由於我們之前設定的是使用 Sqlite,因此我們將使用 Sqlite(Local) 條目右側的…選單中的一些可用選項。當您單擊該選單時,您應該看到以下選項。

  首先,我們需要建立一個新的實體框架遷移。我們使用 Add migration  選項。當您單擊該選項時,將出現 Entity Frameworks Migration 對話方塊。在進入此選單之前,請確保專案未執行。對話方塊將啟動構建,如果專案正在執行,您將收到一個錯誤。當您進入這個對話方塊時,它看起來應該類似於下面的圖片。

  在這個頁面上有兩個輸入,一個是要建立的遷移的名稱,另一個是要使用的 DbContext 類。遷移總是有一個可以使用的預設名稱,或者如果您願意,也可以應用一個特定的名稱。這將是新增遷移時生成的檔案的名稱。對於 DbContext,它應該自動選擇在搭建步驟中建立的 DbContext。對於包含多個 DbContext 的專案,您應該選擇您所針對的特定 DbContext。現在我們可以單擊 Finish 來新增遷移。單擊 Finish 後,將建立遷移並將其新增到專案中。下一步是通過執行該遷移來更新資料庫。

  要更新資料庫,我們將使用 Update database 選單選項。當您單擊該條目時,將出現一個對話方塊,您可以在其中選擇目標 DbContext。在這個範例中,它應該預設為在前面步驟中建立的上下文。如下圖所示。

  從這裡開始,我們只需要單擊 Finish,這將在資料庫上執行前一步中的遷移。單擊 Finish 後,資料庫已經設定完畢,可以使用了。如果您正在使用原始碼管理,那麼現在是建立新提交的好時機。在對模型類進行任何進一步更改之後,您應該重複這兩個步驟來更新資料庫。讓我們繼續看看我們現在如何使用這些搭建起來的api。我們將使用新的終結點資源管理器開始。

Endpoints Explorer

  Endpoints Explorer 是我們正在開發的一個新的預覽特性,它使您能夠檢視解決方案中定義的 API 終結點並與之互動。由於這是一個預覽功能,因此需要啟用它。要啟用這個新的工具視窗,請進入 Tools >  Options >  Environment >  Preview Features,然後勾選 Web API Endpoints Explorer。您可以使用該對話方塊中的搜尋方塊來篩選。請看下面的圖片。

  現在你已經啟用了 Endpoints Explorer,你可以通過 View >  Other Windows >  Endpoints Explorer 進入。開啟該視窗後,您應該看到類似於下圖所示的內容。

  在這個視窗中,您可以看到在 Visual Studio 中使用腳手架生成的所有 API 終結點。它為我們在搭建過程中使用的每個模型類建立了5個終結點(Get/Post/Get specific/Put/Delete)。在此檢視中,您可以檢視解決方案包含的所有終結點。如果您將 API 終結點新增到專案中,您可能需要使用 Endpoints Explorer 頂部的 Refresh 按鈕來重新整理檢視。對於每個請求,您都可以檢視處理請求的程式碼以及生成到該終結點的新請求的程式碼。當您單擊某個條目時,您可以在上下文選單中看到這些選項。這將在下一個螢幕截圖中顯示。

  在這裡,如果您呼叫 Generate Request,HTTP 檔案將與請求一起生成。對於 /api/MenuItem/ 請求,這裡是呼叫上下文選單後的結果。

  點選後,出現以下情況:

  1 建立一個 HTTP 檔案並將其新增到專案中(如果在生成請求時開啟了 HTTP 檔案,則會將其新增到該檔案中)。

  2 在 HTTP 檔案中新增一個變數,包含 Web 專案的 Web 地址。

  3 將請求新增到檔案中。

  Endpoints Explorer 目前是一個預覽功能,因此它不會包含在 isual Studio 2022 17.6,它將出現在17.6和17.7的預覽版本中。我們希望能儘快將其新增到 GA 構建中。

  HTTP 檔案是新增到 Visual Studio 中的一種新檔案型別。現在讓我們回顧一下對 HTTP 檔案的支援。

HTTP 編輯器

  我們在 Visual Studio 2022 中增加了對.HTTP (.REST) 檔案的支援。這種支援的靈感來自於偉大的 VS Code Extension REST Client。這個擴充套件在 VS Code 中很流行,現在其他工具也支援這個檔案格式。當我們在 Visual Studio 中新增對 HTTP 檔案的支援時,我們最初想直接整合這個擴充套件,但是這個擴充套件是通過直接呼叫 VS Code 可延伸性 api 來實現的,所以這不是一個選擇。我們在 Visual Studio 中開發了一個新的 HTTP 編輯器來支援這種檔案型別。目前,REST 使用者端擴充套件的語法在 Visual Studio 中還不支援,但我們正在努力彌補這一差距。既然我們已經討論了這個新編輯器的起源,讓我們來探索一下它的功能。

  HTTP 編輯器的主要目的是使您能夠宣告一個或多個 HTTP 請求,並使您能夠檢視/檢查傳送請求時產生的響應。讓我們仔細看看為 /api/MenuItem/ 終結點生成的程式碼。程式碼如下。

@MyRestaurantApi_HostAddress = https://localhost:7020

Get {{MyRestaurantApi_HostAddress}}/api/MenuItem/

###

  我們看一下這裡的每一行。

@MyRestaurantApi_HostAddress = https://localhost:7020

  第一行宣告了一個名為 MyRestaurantApi_HostAddress 的變數,並將其值賦給 https://localhost:7020。URL 來自新增 HTTP 檔案的專案。宣告一個新變數的語法是:

@VariableName = value

  這些變數可以在 HTTP 檔案中的任何地方宣告。您可以在 HTTP 檔案的宣告之後使用這些值。讓我們看一下下一行,看看如何使用變數。

Get {{MyRestaurantApi_HostAddress}}/api/MenuItem/

  這表示一個 HTTP 請求。在定義 HTTP 請求時,第一個值是 HTTP 請求方法,在本例中是 GET。後面跟著 URL。在本例中,我們使用的是前一行宣告的變數。要獲取變數的值,請使用 HTTP 檔案中的語法{{VariableName}}。在這一行,如果需要指定要使用的特定 HTTP 版本,可以在 URL 後面新增它。例如,對於相同的請求,如果我們想使用 HTTP 1.1,程式碼行將如下所示。

Get {{MyRestaurantApi_HostAddress}}/api/MenuItem/ HTTP/1.1

  現在讓我們轉到檔案的最後一行。

###

  這表示 HTTP 請求的結束。之後,您可以向該檔案新增其他 HTTP 請求。現在讓我們看一個範例,該範例還包含請求的正文和一些報頭。

  我們檢視的範例是一個簡單的 GET 請求,請求中沒有新增報頭或正文。對於 HTTP 請求來說,正文和報頭是非常常見的,所以讓我們看看它們是什麼樣子的。在這種情況下,我們可以在 Endpoints Explorer 中生成一個請求,然後新增 body 和 header。

  前面我們看到,我們有一個 MenuItem API 終結點,用於 get/create/edit 可用於訂購的選單項。假設我們想要新增一個新的 MenuItem,為此我們需要建立一個 POST 請求來新增一個新的 MenuItem。我們可以使用 Endpoints Explorer 來幫助生成該請求。在 POST /api/MenuItem/上右鍵選擇 Generate Request。

  在當前版本的 Endpoints Explorer 中,這將為 HTTP 檔案生成以下內容。

Post {{MyRestaurantApi_HostAddress}}/api/MenuItem/

###

  在未來的版本中,我們希望為這個請求所需的 JSON 主體提供一個樁。從這裡開始,我們要新增 JSON 主體和頭,以便演示如何使用它們。我們需要新增到主體的 JSON 應該表示帶有相應設定值的 MenuItem 的 JSON,不包括將由資料庫設定的 id 欄位。更新後的請求如下所示。

Post {{MyRestaurantApi_HostAddress}}/api/MenuItem/
Content-Type: application/json
Accept: application/json

{
  "name": "All-beef hotdog and soda",
  "price": 1.50,
  "description": "An all-beef hotdog and soda with a permanent fixed price.",
  "category": 1
}

###

  讓我們更詳細地描述這個請求的語法。請求的語法是,第一行是 HTTP 方法,後面是 URL 和可選的 HTTP 版本,如前所述。在請求行之後,您可以在請求行之後的單獨行中指定每個檔頭(中間沒有空白行)。當指定一個報頭時,語法是 HeaderName: Value,其中 HeaderName 是傳入的報頭的名稱,Value 是您為它指定的值。在本例中,我們指定了兩個報頭, Content-Type 和 Accept。這個請求不需要這些檔頭,因為它們是預設的,但是這裡展示它們是為了演示如何傳入檔頭。在報頭或請求行(如果沒有報頭)之後,您應該新增一個空行,後面跟著請求正文。最後,我們在末尾新增###來表示請求的結束。

  在這個請求中,我們將新增一個新的 MenuItem,它將用於訂購。我們增加了一種新的「全牛肉熱狗加蘇打水套餐」。首先,我們希望通過從 Visual Studio 啟動 API 來確保它正在執行。在啟動 Web API 之後,可以使用請求行左側的綠色播放按鈕傳送請求。在 Visual Studio 中,結果應該與以下類似。

  在這裡,我們可以看到新增「全牛肉熱狗和蘇打水」的請求已成功新增到資料庫中。資料庫中為它分配了 id 值為7。現在我們可以建立一個請求來新增一個新的 Togo 訂單。在提交新的 TogoOrder 之前,我們需要修改處理 POST 方法的 TogoOrderEndpoints 類。當提交 TogoOrder 時,如果 OrderCreated 值為空,則應該將其設定為當前日期/時間,並且我們還希望從 MenuItem 值中填充 MenuItemOrdered 的詳細資訊。下面是更新後的 TogoOrder Post 處理程式程式碼。更新後的程式碼如下。

group.MapPost("/", async (TogoOrder togoOrder, MyRestaurantApiContext db) =>
{
    togoOrder.Customer = await db.Contact.FindAsync(togoOrder.Customer!.Id);

    if (togoOrder.OrderCreated == null) {
        togoOrder.OrderCreated = DateTime.Now;
    }
    if (togoOrder.ItemsOrdered != null && togoOrder.ItemsOrdered.Count > 0) {
        foreach (var item in togoOrder.ItemsOrdered) {
            var menuItem = await db.MenuItem.FindAsync(item.MenuItemId);
            item.Name = menuItem!.Name;
            if (item.Price is null || !item.Price.HasValue || item.Price.Value < 0) {
                item.Price = menuItem.Price!.Value;
            }
            if (item.Category is null || !item.Category.HasValue) {
                item.Category = menuItem.Category!.Value;
            }
        }
    }
    db.TogoOrder.Add(togoOrder);
    await db.SaveChangesAsync();
    return TypedResults.Created($"/api/TogoOrder/{togoOrder.Id}",togoOrder);
})
.WithName("CreateTogoOrder")
.WithOpenApi();

  需要新增的程式碼出現在 db.TogoOrder.Add(togoOrder) 之前。這是不言自明的。當提交請求時,如果 OrderCreated 為空,則填充它的值,然後從 MenuItem 中缺失的任何值將從相應的 MenuItemId 中填充。MenuItemId 是將要建立的每個 MenuItemOrdered 的唯一必需值。

  首先,我們將使用 Endpoints Explorer 生成樁請求,方法是右鍵單擊 POST /api/TogoOrder/並選擇 generate request。然後我們需要將 JSON 主體新增到請求中。新增 JSON 主體後,請求應該如下所示。

Post {{MyRestaurantApi_HostAddress}}/api/TogoOrder/
Content-Type: application/json

{
  "itemsOrdered": [
    {
      "menuItemId":1
    }
  ],
  "subtotal": 3.50,
  "tax": 0.8,
  "total": 4.30,
  "paymentMethod": 1,
  "customer": {
    "id": 2
  }
}
###

  在這個請求中,我們使用 POST 方法新增一個新的 TogoOrder 條目。如前所述,我們可以列出訂購的專案,唯一必需的欄位是 menuItemId。我們新增到 Post 方法中的程式碼將填充缺失的欄位。我們還在 TogoOrder 物件中分配了美元金額。我們可以通過按請求行左側的綠色播放按鈕傳送此請求。呼叫之後,響應應該類似於以下內容。

{
  "id": 1,
  "orderCreated": "2023-04-27T14:14:48.3785278-04:00",
  "itemsOrdered": [
    {
      "id": 1,
      "menuItemId": 1,
      "togoOrderId": 1,
      "name": "Hamburger",
      "price": 3.68,
      "category": 1
    }
  ],
  "subtotal": 3.5,
  "tax": 0.8,
  "total": 4.3,
  "paymentMethod": 1,
  "customer": {
    "id": 2,
    "name": "Mads Kristensen",
    "email": "[email protected]",
    "phone": "555-111-3333"
  }
}

  現在我們已經將 TogoOrder 新增到資料庫中,讓我們通過發出一個 GET 請求來列出資料庫中的所有 TogoOrder,從而驗證我們獲得了正確的結果。通過在 GET /api/TogoOrder/端點的上下文選單中選擇 generate request,我們可以使用 Endpoints Explorer 生成該請求。呼叫後,將向 HTTP 檔案中新增以下請求。

Get {{MyRestaurantApi_HostAddress}}/api/TogoOrder/

###

  要傳送此請求,請單擊綠色播放按鈕。這樣做之後,響應檢視中的結果應該類似於以下內容。

[
  {
    "id": 1,
    "orderCreated": "2023-04-27T14:22:53.2117889",
    "itemsOrdered": null,
    "subtotal": 3.5,
    "tax": 0.8,
    "total": 4.3,
    "paymentMethod": 1,
    "customer": null
  }
]

  我們已經成功地收到了結果,但是這裡 itemsOrdered 為 null。如果我們要檢查資料庫,我們將看到所有正確的條目已經建立,所有的關係也都是正確的。這裡的問題是實體框架沒有自動載入相關資料。有關EF如何載入相關資料的更多資訊,請參《 Loading Related Data – EF Core》。在本例中,當返回 TogoOrder 時,我們總是希望載入 ItemOrdered 欄位的資料。我們需要對生成的 GET 請求進行簡單的編輯以滿足這一需求。在本例中,當返回 TogoOrder 時,我們總是希望載入 ItemOrdered 欄位的資料。我們需要對生成的 GET 請求進行簡單的編輯以滿足這一需求。處理此請求的方法在 TogoOrderEndpoints 類中。我們需要為 EF 新增一個 include 語句來載入 ItemOrdered 屬性。修改後的方法如下:

group.MapGet("/", async (MyRestaurantApiContext db) =>
{
    return await db.TogoOrder
        .Include(order => order.ItemsOrdered)
        .ToListAsync();
})

  新增此內容後,我們將重新啟動 Web API 專案並重新傳送 GET 請求以列出所有訂單。結果應該類似於下面的程式碼片段。

[
  {
    "id": 1,
    "orderCreated": "2023-04-27T14:22:53.2117889",
    "itemsOrdered": [
      {
        "id": 1,
        "menuItemId": 1,
        "togoOrderId": 1,
        "name": "Hamburger",
        "price": 3.68,
        "category": 1
      }
    ],
    "subtotal": 3.5,
    "tax": 0.8,
    "total": 4.3,
    "paymentMethod": 1,
    "customer": null
  }
]

  現在我們可以看到,請求返回了 TogoOrder 物件,其中填充了專案的 Ordered 值。我們現在已經成功地構建了一個 ASP. NET Core Web API 來處理訂單請求。讓我們繼續討論 HTTP 檔案中支援的語法。

HTTP 檔案語法

  之前我們提到過 HTTP 編輯器及其相關的支援是受到 Visual Studio Code REST Client extension 的啟發。該擴充套件在 HTTP 檔案中支援的語法比 Visual Studio 現在支援的更廣泛。隨著我們的進展,我們將增加更多的支援來縮小差距。在本節中,我們將討論 Visual Studio 當前支援的語法。

  在本節中,請求將使用 httpbin.org,這是每個人都可以使用的免費第三方 Web API。您可以向它傳送請求,它會將請求回顯給您。讓我們來探索一下 Visual Studio 目前支援的語法。

註釋

  註釋是以#或//開頭的行。這些行將被忽略,並且不會包含在請求中。

變數

  如前所述,您可以定義可在後續請求中重用的變數。您可以定義所需的任意數量的變數。可以使用已經定義的其他變數的值來定義變數。例如,請參見下面的程式碼片段。

@searchTerm = some-search-term
@hostname = httpbin.org
# variable using another variable
@host = https://{{hostname}}
@name = Sayed
@phone = 111-222-3333

   這裡我們定義了幾個變數,包括主機變數,它使用另一個名為 hostname 的變數的值。下面是使用這些變數之一的請求範例。

GET {{host}}/anything HTTP/1.1
User-Agent: rest-client
Content-Type: application/json
###

請求行

  對於請求行,我們前面提到了請求行的格式是:

HTTPMethod URL HTTPVersion

  這裡的 HTTPMethod 是要使用的 HTTP 方法,例如 GET、POST、PUT、PATCH 等。URL 是將請求傳送到的 URL。此 URL 還可以包括查詢字串引數,如以下範例請求。可選的 HTTPVersion 是應該使用的 HTTP 版本。下面是一些範例請求。

GET https://httpbin.org/get
###

GET https://httpbin.org/get?name=Sayed?&phone=111-222-3333
###

GET https://httpbin.org/get?name=Sayed?&phone=111-222-3333 HTTP/1.1
###

  Visual Studio Code REST Client extension 支援一些我們現在不支援的功能。這些包括。

  * 可選HTTP方法 REST Client 支援不指定 HTTP 方法。在這些情況下,GET 是預設的 HTTP 方法。

  * 請求URL跨越多行 在 Visual Studio 的當前實現中,請求行必須在單行上。

  我們希望在未來的版本中增加對這些功能的支援。

同一檔案中的多個請求

  如上所示,一個 HTTP 檔案可以列出多個不同的請求。它們需要在每個請求的末尾用###分隔。

  要新增一個或多個 header,請在請求行之後(沒有空行)將每個 header 新增到自己的行中。下面是一些不同的例子。

GET https://httpbin.org/get?name=Sayed?&phone=111-222-3333 HTTP/1.1
Date: Wed, 27 Apr 2023 07:28:00 GMT
###

GET https://httpbin.org/get?name=Sayed?&phone=111-222-3333
Cache-Control: max-age=604800
Age: 100
###

POST https://httpbin.org/post HTTP/1.1
Content-Type: application/json
Accept-Language: en-US,en;q=0.5

{
    "name": "sample",
    "time": "Wed, 21 Oct 2015 18:27:50 GMT"
}
###

  如果你正在呼叫一個 API,它可以用報頭進行身份驗證,你也可以在報頭中指定那些。如果這樣做,請注意不要向儲存庫提交任何私密。我們將致力於以安全的方式支援私密。

  現在我們已經瞭解了一些支援的語法,在下一節中,我們將列出 REST Client 具有的一些 Visual Studio 目前不支援的特性。如果您希望看到對這些功能的支援,請在下面留下評論或向團隊傳送一些反饋(請參閱下面的結束部分)。這個列表不分先後。有關下面列出的任何專案的更多資訊,請參閱 Visual Studio Code REST Client extension.

目前在 Visual Studio HTTP 編輯器中不支援

  * Optional HTTP Method

  * Request line that spans more than one line

  * Named Requests

  * Dynamic variables

  * Environment files

  * Specify file path as body of the request

  * Mixed format for body when using multipart/form-data

  * GraphQL requests

  * cURL request

  * Copy/paste as cURL

  * Request history

  * Save response body to file

  * Certificate based authentication

  * Prompt variables

  * System variables

  * Customize response preview

  * Per-request settings

  現在我們已經介紹了 Visual Studio HTTP 編輯器支援什麼,不支援什麼,讓我們繼續討論接下來會發生什麼。

下一步

  在這篇文章中,我們討論了開發 ASP. NET Core Web API 的端到端流程,包括 Endpoints Explorer 和 HTTP 編輯器等新工具。這是我們漫長旅程的開始,我們計劃在相當長的一段時間內繼續在這一領域進行投資。我們正在研究新增支援的一些特性和功能包括。下面的列表不分先後。下面的列表是我們正在探索的一些想法,我們目前還沒有承諾交付所有這些功能。

  * 為請求體新增一個樁——對於需要請求體的請求,現在 Visual Studio 不會為請求體新增任何內容。我們正在尋找可以為主體新增樁的方法,以使完成該請求更容易。

  * 增加對 HTTP 檔案語法的更多支援——我們只支援 REST Client 支援的語法的一個子集。我們將努力縮小差距,我們可能無法支援100%的功能,但我們將盡可能接近。這裡想到的一些最重要的專案包括支援:命名請求、環境檔案、支援 multipart/form-data 請求和 copy/paste as cURL.

  * 改進的響應檢視——響應檢視目前是非常基本的。我們計劃製作一個更結構化的響應檢視,以便更容易地讀取來自 web 伺服器的結果。

  * 在 Endpoints Explorer 中顯示 HTTP 檔案——目前 Endpoints Explorer 只顯示解決方案中的 API 終結點。我們還希望顯示解決方案中或在 Visual Studio 中開啟的 HTTP 檔案。這將使您能夠以更簡單的方式檢查和瀏覽這些檔案。

  * 請求歷史——我們希望為您提供支援,以便能夠檢視以前傳送的請求,然後重新傳送這些請求。編輯以前的請求也是我們想要新增支援的功能。

  * 簡化請求的使用者體驗——目前建立請求必須手動輸入請求。有些使用者更喜歡使用圖形化使用者介面來編寫請求。

  * 將響應體儲存到檔案中——在我們改進響應檢視之後,我們希望為您新增將響應體儲存到檔案的功能。

  * 測試——我們希望找到一種方法來簡化 Web API 專案的測試。我們還沒有任何具體的計劃,但這對我們來說是一個重要的領域。

  在下一節中,我將描述針對這些場景提供反饋的不同方式。如果在上面的列表中有您認為非常重要的功能,請與我們分享您的反饋。只有在您的幫助下,我們才能創造出令人難以置信的功能,您的反饋對我們非常重要。

結語

  我們在這個版本中提供的大多數更新都是受到像您這樣的使用者的反饋的啟發。您可以通過開發者社群與我們分享反饋:通過問題報告反饋錯誤或問題,並分享您對新功能或改進現有功能的建議。你也可以在這裡留言,或者在推特上@SayedIHashimi 聯絡 Sayed。

 

程式碼位置:https://github.com/sayedihashimi/RestaurantService

原文連結:https://devblogs.microsoft.com/visualstudio/web-api-development-in-visual-studio-2022/