Catatan

Artikel ini telah dimuat Perakitan Web Blazor cakupan untuk memanggil API web. Cakupan Server Blazor membahas subjek berikut:

  • Penggunaan HttpClient infrastruktur pabrik untuk menyediakan HttpClient ke aplikasi.
  • Berbagi sumber daya lintas-asal (CORS) yang berkaitan dengan aplikasi Server Blazor.
  • Contoh komponen kerangka kerja Blazor untuk menguji akses API web.
  • Sumber daya tambahan untuk mengembangkan aplikasi Blazor Server yang memanggil API web.

Aplikasi Blazor WebAssembly memanggil API web menggunakan layanan HttpClient yang telah dikonfigurasi sebelumnya, yang difokuskan untuk membuat permintaan kembali ke server asal. Konfigurasi layanan HttpClient tambahan untuk API web lain dapat dibuat dalam kode pengembang. Permintaan disusun menggunakan pembantu Blazor JSON atau dengan HttpRequestMessage. Permintaan dapat menyertakan konfigurasi opsi Fetch API.

Contoh dalam artikel ini

Dalam contoh komponen artikel ini, API web daftar tugas hipotetis digunakan untuk membuat, membaca, memperbarui, dan menghapus (CRUD) item tugas di server. Contoh-contoh tersebut didasarkan pada TodoItem kelas yang menyimpan data item todo berikut:

  • PENGENAL (Id, long): ID unik item.
  • Nama (Name, string): Nama barang.
  • Status (IsComplete, bool): Indikasi jika item todo selesai.

Gunakan yang berikut ini TodoItem kelas dengan contoh artikel ini jika Anda membuat contoh ke dalam aplikasi pengujian:

public class TodoItem

    public long Id  get; set; 
    public string? Name  get; set; 
    public bool IsComplete  get; set; 

Untuk panduan tentang cara membuat API web sisi server, lihat Tutorial: Membuat API web dengan ASP.NET Core. Untuk informasi tentang berbagi sumber daya lintas-asal (CORS), lihat panduan CORS nanti di artikel ini.

Paket

Tambahkan referensi paket untuk System.Net.Http.Json.

Tambahkan HttpClient melayani

Di Program.cstambahkan layanan HttpClient jika belum ada dari template proyek Blazor yang digunakan untuk membuat aplikasi:

builder.Services.AddScoped(sp => 
    new HttpClient
    
        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
    );

HttpClient dan pembantu JSON

HttpClient tersedia sebagai layanan prakonfigurasi untuk membuat permintaan kembali ke server asal.

Helper HttpClient dan JSON (System.Net.Http.Json.HttpClientJsonExtensions) juga digunakan untuk memanggil endpoint web API pihak ketiga. HttpClient diimplementasikan menggunakan API Ambil browser dan tunduk pada batasannya, termasuk penegakan kebijakan asal yang sama (dibahas nanti dalam artikel ini).

Alamat dasar klien diatur ke alamat server asal. Suntikkan instance HttpClient ke dalam komponen menggunakan @inject pengarahan:

@using System.Net.Http
@inject HttpClient Http

Gunakan namespace System.Net.Http.Json untuk akses ke HttpClientJsonExtensions, termasuk GetFromJsonAsync, PutAsJsonAsync, dan PostAsJsonAsync:

@using System.Net.Http.Json

DAPATKAN dari JSON (GetFromJsonAsync)

GetFromJsonAsync mengirimkan permintaan HTTP GET dan mem-parsing badan respons JSON untuk membuat objek.

Dalam kode komponen berikut, todoItems ditampilkan oleh komponen. GetFromJsonAsync dipanggil ketika komponen selesai diinisialisasi (OnInitializedAsync).

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

@if (todoItems == null)

    <p>No Todo Items found.</p>

else

    <ul>
        @foreach (var item in todoItems)
        
            <li>@item.Name</li>
        
    </ul>


@code 
    private TodoItem[]? todoItems;

    protected override async Task OnInitializedAsync() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>("api/TodoItems");

POSTING sebagai JSON (PostAsJsonAsync)

PostAsJsonAsync mengirimkan permintaan POST ke URI tertentu yang berisi nilai yang diserialisasikan sebagai JSON di badan permintaan.

Dalam kode komponen berikut, newItemName disediakan oleh elemen terikat dari komponen. Itu AddItem metode dipicu dengan memilih a <button> elemen.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="newItemName" placeholder="New Todo Item" />
<button @onclick="AddItem">Add</button>

@code 
    private string? newItemName;

    private async Task AddItem()
    
        var addItem = new TodoItem  Name = newItemName, IsComplete = false ;
        await Http.PostAsJsonAsync("api/TodoItems", addItem);
    

Panggilan ke PostAsJsonAsync mengembalikan HttpResponseMessage. Untuk membatalkan serialisasi konten JSON dari pesan respons, gunakan metode ekstensi ReadFromJsonAsync. Contoh berikut membaca data cuaca JSON:

var content = await response.Content.ReadFromJsonAsync<WeatherForecast>();

PUT sebagai JSON (PutAsJsonAsync)

PutAsJsonAsync mengirimkan permintaan HTTP PUT dengan konten yang disandikan JSON.

Dalam kode komponen berikut, editItem nilai untuk Name dan IsCompleted disediakan oleh elemen terikat dari komponen. Barang-barang Id diatur ketika item dipilih di bagian lain dari UI (tidak ditampilkan) dan EditItem disebut. Itu SaveItem metode dipicu dengan memilih <button> elemen. Perhatikan bahwa contoh berikut tidak menunjukkan pemuatan todoItems untuk singkatnya (lihat GET dari JSON (GetFromJsonAsync) bagian untuk contoh memuat item).

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input type="checkbox" @bind="editItem.IsComplete" />
<input @bind="editItem.Name" />
<button @onclick="SaveItem">Save</button>

@code 
    private string? id;
    private TodoItem editItem = new TodoItem();

    private void EditItem(long id)
    
        editItem = todoItems.Single(i => i.Id == id);
    

    private async Task SaveItem() =>
        await Http.PutAsJsonAsync($"api/TodoItems/editItem.Id", editItem);

Panggilan ke PutAsJsonAsync mengembalikan HttpResponseMessage. Untuk membatalkan serialisasi konten JSON dari pesan respons, gunakan metode ekstensi ReadFromJsonAsync. Contoh berikut membaca data cuaca JSON:

var content = await response.Content.ReadFromJsonAsync<WeatherForecast>();

Metode ekstensi tambahan

System.Net.Http menyertakan metode ekstensi tambahan untuk mengirim permintaan HTTP dan menerima tanggapan HTTP. HttpClient.DeleteAsync digunakan untuk mengirim permintaan HTTP DELETE ke web API.

Dalam kode komponen berikut, <button> elemen memanggil DeleteItem metode. terikat <input> elemen memasok id dari item yang akan dihapus.

@using System.Net.Http
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="id" />
<button @onclick="DeleteItem">Delete</button>

@code 
    private long id;

    private async Task DeleteItem() =>
        await Http.DeleteAsync($"api/TodoItems/id");

Bernama HttpClient dengan IHttpClientFactory

Layanan IHttpClientFactory dan konfigurasi bernama HttpClient didukung.

Tambahkan Microsoft.Extensions.Http paket NuGet ke aplikasi.

Di Program.cs:

builder.Services.AddHttpClient("WebAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

Dalam kode komponen berikut:

Pages/FetchDataViaFactory.razor:

@page "/fetch-data-via-factory"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject IHttpClientFactory ClientFactory

<h1>Fetch data via <code>IHttpClientFactory</code></h1>

@if (forecasts == null)

    <p><em>Loading...</em></p>

else

    <h2>Temperatures by Date</h2>

    <ul>
        @foreach (var forecast in forecasts)
        
            <li>
                @forecast.Date.ToShortDateString():
                @forecast.TemperatureC &#8451;
                @forecast.TemperatureF &#8457;
            </li>
        
    </ul>


@code 
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    
        var client = ClientFactory.CreateClient("WebAPI");

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecast");
    

Diketik HttpClient

HttpClient yang diketik menggunakan satu atau beberapa instance HttpClient aplikasi, default atau bernama, untuk mengembalikan data dari satu atau beberapa titik akhir API web.

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public class WeatherForecastHttpClient

    private readonly HttpClient http;

    public WeatherForecastHttpClient(HttpClient http)
    
        this.http = http;
    

    public async Task<WeatherForecast[]> GetForecastAsync()
    
        try
        
            return await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        
        catch
        
            ...

            return new WeatherForecast[0];
        
    

Di Program.cs:

builder.Services.AddHttpClient<WeatherForecastHttpClient>(client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

Komponen menyuntikkan HttpClient yang diketik untuk memanggil API web.

Dalam kode komponen berikut:

  • Sebuah contoh dari sebelumnya WeatherForecastHttpClient disuntikkan, yang membuat HttpClient yang diketik.
  • HttpClient yang diketik digunakan untuk mengeluarkan permintaan GET untuk data prakiraan cuaca JSON dari API web.

Pages/FetchDataViaTypedHttpClient.razor:

@page "/fetch-data-via-typed-httpclient"
@using System.Threading.Tasks
@inject WeatherForecastHttpClient Http

<h1>Fetch data via typed <code>HttpClient</code></h1>

@if (forecasts == null)

    <p><em>Loading...</em></p>

else

    <h2>Temperatures by Date</h2>

    <ul>
        @foreach (var forecast in forecasts)
        
            <li>
                @forecast.Date.ToShortDateString():
                @forecast.TemperatureC &#8451;
                @forecast.TemperatureF &#8457;
            </li>
        
    </ul>


@code 
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    
        forecasts = await Http.GetForecastAsync();
    

HttpClient dan HttpRequestMessage dengan opsi permintaan Fetch API

HttpClient (dokumentasi API) dan HttpRequestMessage dapat digunakan untuk menyesuaikan permintaan. Misalnya, Anda dapat menentukan metode HTTP dan header permintaan. Komponen berikut membuat POST permintaan ke titik akhir API web dan menunjukkan isi respons.

Pages/TodoRequest.razor:

@page "/todo-request"
@using System.Net.Http
@using System.Net.Http.Headers
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http
@inject IAccessTokenProvider TokenProvider

<h1>ToDo Request</h1>

<button @onclick="PostRequest">Submit POST request</button>

<p>Response body returned by the server:</p>

<p>@responseBody</p>

@code 
    private string? responseBody;

    private async Task PostRequest()
    
        var requestMessage = new HttpRequestMessage()
        
            Method = new HttpMethod("POST"),
            RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
            Content =
                JsonContent.Create(new TodoItem
                
                    Name = "My New Todo Item",
                    IsComplete = false
                )
        ;

        var tokenResult = await TokenProvider.RequestAccessToken();

        if (tokenResult.TryGetToken(out var token))
        
            requestMessage.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", token.Value);

            requestMessage.Content.Headers.TryAddWithoutValidation(
                "x-custom-header", "value");

            var response = await Http.SendAsync(requestMessage);
            var responseStatusCode = response.StatusCode;

            responseBody = await response.Content.ReadAsStringAsync();
        
    

    public class TodoItem
    
        public long Id  get; set; 
        public string? Name  get; set; 
        public bool IsComplete  get; set; 
    

Implementasi HttpClient Blazor WebAssembly menggunakan Fetch API. Fetch API memungkinkan konfigurasi beberapa opsi khusus permintaan. Opsi dapat dikonfigurasi dengan metode ekstensi HttpRequestMessage yang ditunjukkan pada tabel berikut.

Tetapkan opsi tambahan menggunakan metode ekstensi SetBrowserRequestOption generik.

Respons HTTP biasanya disangga untuk mengaktifkan dukungan untuk pembacaan sinkron pada konten respons. Untuk mengaktifkan dukungan untuk streaming respons, gunakan metode ekstensi SetBrowserResponseStreamingEnabled berdasarkan permintaan.

Untuk menyertakan kredensial dalam permintaan lintas-asal, gunakan metode ekstensi SetBrowserRequestCredentials:

requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

Untuk informasi selengkapnya tentang opsi Fetch API, lihat dokumen web MDN: WindowOrWorkerGlobalScope.fetch(): Parameters.

Panggil contoh API web

Contoh berikut memanggil API web. Contoh ini memerlukan API web yang berjalan berdasarkan aplikasi contoh yang dijelaskan oleh Tutorial: Membuat API web dengan artikel ASP.NET Core. Contoh ini membuat permintaan ke API web di https://localhost:10000/api/TodoItems. Jika alamat API web yang berbeda digunakan, perbarui ServiceEndpoint nilai konstan dalam komponen @code memblokir.

Contoh berikut membuat permintaan berbagi sumber daya lintas asal (CORS) dari: http://localhost:5000 atau https://localhost:5001 ke API web. Tambahkan konfigurasi middleware CORS berikut ke layanan web API Program.cs mengajukan:

app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType));

Sesuaikan domain dan port dari WithOrigins sesuai kebutuhan untuk aplikasi Blazor. Untuk informasi selengkapnya, lihat Mengaktifkan Permintaan Lintas Asal (CORS) di ASP.NET Core.

Secara default, aplikasi ASP.NET Core menggunakan port 5000 (HTTP) dan 5001 (HTTPS). Untuk menjalankan kedua aplikasi pada mesin yang sama secara bersamaan untuk pengujian, gunakan port yang berbeda untuk aplikasi API web (misalnya, port 10.000). Untuk informasi selengkapnya tentang pengaturan port, lihat Mengonfigurasi titik akhir untuk server web ASP.NET Core Kestrel.

Pages/CallWebAPI.razor:

@page "/call-web-api"
@inject HttpClient Http

<h1>Todo Items</h1>

@if (todoItems == null)

    <p>No Todo Items found.</p>

else

    <table class="table">
        <thead>
            <tr>
                <th class="text-center">Complete</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr id="editRow" style="display:@editRowStyle">
                <td class="text-center">
                    <input type="checkbox" @bind="editItem.IsComplete" />
                </td>
                <td>
                    <input @bind="editItem.Name" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="SaveItem">
                        Save
                    </button>
                    <button class="btn btn-danger" 
                            @onclick="@(() => editRowStyle = "none")">
                        Cancel
                    </button>
                </td>
            </tr>
            @foreach (var item in todoItems)
            
                <tr>
                    <td class="text-center">
                        @if (item.IsComplete)
                        
                            <span>✔</span>
                        
                    </td>
                    <td>@item.Name</td>
                    <td class="text-center">
                        <button class="btn btn-warning" 
                                @onclick="@(() => EditItem(item.Id))">
                            Edit
                        </button>
                        <button class="btn btn-danger" 
                                @onclick="@(async () => await DeleteItem(item.Id))">
                            Delete
                        </button>
                    </td>
                </tr>
            
            <tr id="addRow">
                <td></td>
                <td>
                    <input @bind="newItemName" placeholder="New Todo Item" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="AddItem">Add</button>
                </td>
            </tr>
        </tbody>
    </table>


@code 
    private const string ServiceEndpoint = "https://localhost:10000/api/TodoItems";
    private TodoItem[]? todoItems;
    private TodoItem editItem = new();
    private string editRowStyle = "none";
    private string? newItemName;

    protected override async Task OnInitializedAsync() => await GetTodoItems();

    private async Task GetTodoItems() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>(ServiceEndpoint);

    private void EditItem(long id)
    
        if (todoItems is not null)
        
            editItem = todoItems.Single(i => i.Id == id);
            editRowStyle = "table-row";
        
    

    private async Task AddItem()
    
        var addItem = new TodoItem  Name = newItemName, IsComplete = false ;
        await Http.PostAsJsonAsync(ServiceEndpoint, addItem);
        newItemName = string.Empty;
        await GetTodoItems();
        editRowStyle = "none";
    

    private async Task SaveItem()
    
        if (editItem is not null)
        
            await Http.PutAsJsonAsync($"ServiceEndpoint/editItem.Id", 
                editItem);
        
        await GetTodoItems();
        editRowStyle = "none";
    

    private async Task DeleteItem(long id)
    
        await Http.DeleteAsync($"ServiceEndpoint/id");
        await GetTodoItems();
        editRowStyle = "none";
    

    private class TodoItem
    
        public long Id  get; set; 
        public string? Name  get; set; 
        public bool IsComplete  get; set; 
    

Menangani kesalahan

Menangani kesalahan respons API web dalam kode pengembang saat terjadi. Misalnya, GetFromJsonAsync mengharapkan respons JSON dari web API dengan a Content-Type dari application/json. Jika respons tidak dalam format JSON, validasi konten akan menampilkan NotSupportedException.

Dalam contoh berikut, titik akhir URI untuk permintaan data prakiraan cuaca salah eja. URI harus ke WeatherForecast tetapi muncul dalam panggilan sebagai WeatherForcastyang tidak ada hurufnya e di Forecast.

Panggilan GetFromJsonAsync mengharapkan JSON dikembalikan, tetapi web API mengembalikan HTML untuk pengecualian yang tidak tertangani dengan Content-Type dari text/html. Pengecualian yang tidak tertangani terjadi karena jalur ke /WeatherForcast tidak ditemukan dan middleware tidak dapat menyajikan halaman atau tampilan untuk permintaan tersebut.

Di OnInitializedAsync pada klien, NotSupportedException ditampilkan saat konten respons divalidasi sebagai non-JSON. Pengecualian tertangkap dalam catch blok, di mana logika khusus dapat mencatat kesalahan atau menyajikan pesan kesalahan ramah kepada pengguna.

Pages/FetchDataReturnsHTMLOnException.razor:

@page "/fetch-data-returns-html-on-exception"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<h1>Fetch data but receive HTML on unhandled exception</h1>

@if (forecasts == null)

    <p><em>Loading...</em></p>

else

    <h2>Temperatures by Date</h2>

    <ul>
        @foreach (var forecast in forecasts)
        
            <li>
                @forecast.Date.ToShortDateString():
                @forecast.TemperatureC &#8451;
                @forecast.TemperatureF &#8457;
            </li>
        
    </ul>


<p>
    @exceptionMessage
</p>

@code 
    private WeatherForecast[]? forecasts;
    private string? exceptionMessage;

    protected override async Task OnInitializedAsync()
    
        try
        
            // The URI endpoint "WeatherForecast" is misspelled on purpose on the 
            // next line. See the preceding text for more information.
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForcast");
        
        catch (NotSupportedException exception)
        
            exceptionMessage = exception.Message;
        
    

Catatan

Contoh sebelumnya adalah untuk tujuan demonstrasi. Web API dapat dikonfigurasi untuk mengembalikan JSON bahkan ketika titik akhir tidak ada atau pengecualian yang tidak tertangani terjadi di server.

Untuk informasi selengkapnya, lihat Menangani kesalahan di aplikasi ASP.NET Core Blazor.

Keamanan browser membatasi halaman web dari membuat permintaan ke domain yang berbeda dari domain yang melayani halaman web. Pembatasan ini disebut kebijakan asal yang sama. Kebijakan asal yang sama membatasi (tetapi tidak mencegah) situs berbahaya membaca data sensitif dari situs lain. Untuk membuat permintaan dari browser ke titik akhir dengan asal yang berbeda, titik akhir harus mengaktifkan berbagi sumber daya lintas asal (CORS).

Berbagai alat jaringan tersedia untuk umum untuk menguji aplikasi backend API web secara langsung, seperti Pengembang Peramban Firefox dan Tukang Pos. Sumber referensi kerangka kerja Blazor mencakup aset pengujian HttpClient yang berguna untuk pengujian:

By AKDSEO