Web API
.NET .NET Core ASP.NET Core Blazor C# HTTP Razor Visual Studio Web API

How to Execute HTTP PUT Requests to a Web API from a .NET Core Blazor WebAssembly

Welcome to today’s post.

In today’s post I will be showing how to execute HTTP PUT Web API requests from a .NET Core Blazor WebAssembly.

In previous posts I showed how to execute HTTP GET Web API requests and HTTP POST Web API requests from a .NET Core Blazor WebAssembly. I also explained why the disconnected client architecture of a Blazor WebAssembly application requires calls to a Web API service to retrieve and process server data and resource.

Given that we know how to post data to a web API, the next step in our understanding of HTTP REST calls to Web API services is to determine how we can update an existing resource in a server.

Understanding the HTTP PUT REST Call

To update a server resource using Web API calls requires us to execute a HTTP PUT call.

A HTTP PUT call requires two parameters to successfully apply an update to the server resource:

  1. A payload.
  2. A resource identifier.

With HTTP PUT REST calls, the payload is in JSON format.

The payload consists of key and value pairs that correspond to the properties of the resource that we wish to change. In this case, the payload corresponds to fields within an existing record in a backend SQL database.

The resource identifier, which is usually an integer, corresponds to an existing record key in a table.

For the update to be successful, the backend API method must first retrieve an existing record with the matching resource identifier. Then the backend API method must run a SQL UPDATE on the record, updating the fields that correspond to the payload keys with values within the payload. If the update is successful, a response status code of 204 would be returned to the client without any content.

If no record (or records) is retrieved by the resource identifier, the response from the Web API method will be an exception.

Overview of the HTTP PUT Web API Method

To understand how we make the call to the HTTP PUT web API method will require an understanding of the URI pattern of the web API method.

When I run the web API service and expand the HTTP PUT method for the API method that I would like to execute, we can see the URI in the method description header and the required and optional parameters that are required for execution.

There is also a default request body shown in JSON format. 

If we already know which record identifier we would like to update and are certain it is still corresponds to a record within the table, then the next step isn’t required. If we are not sure, then we can execute a HTTP GET with the identifier to retrieve the record.

Below is the response when we execute the HTTP GET with the identifier:

Now that we know that a record with that identifier exists, we can run the HTTP PUT on the web API with the payload of keys and values we want to have updated in the underlying record.

Below is the request of the HTTP PUT execution that we will run with the identifier and payload:

Below is the result of the HTTP PUT execution that we have run with the identifier and payload:

We can see, as expected, the response code is 204 with no content, which means the update was successful.

We can confirm that within the record, the ISBN of “654321” replaced the previous value of “123456”:

We can see that the CURL command used to run the HTTP PUT REST call with the JSON payload is shown below:

curl -X 'PUT' \
  'http://localhost:5013/bookitems/232' \
  -H 'accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 232,
  "title": "Sample Book 0",
  "author": "Test Author",
  "yearPublished": 2000,
  "genre": "Fiction",
  "edition": "1",
  "isbn": "654321",
  "location": "Sydney",
  "mediaType": "Book",
  "dateCreated": "2023-12-23T08:45:34.999",
  "dateUpdated": "2023-12-23T08:45:34.999"
}'

Given the above executions and observations from within the Web API Service Swagger interface, we will do likewise from within the Blazor WebAssembly, which is what I will show in the next section.

Execution of HTTP PUT Web API Calls from the Blazor WebAssembly

In this section I will implement an additional method, UpdateBook() within the BookService class to execute the web API call to the HTTP PUT web API method. The EditBook.razor component executes the call to UpdateBook() with the entered data within the form.

The implementation for the EditBook.razor component for editing an existing record, and allow it to be updated is shown below:

EditBook.razor:

@page "/editbook"
@using Microsoft.AspNetCore.Components
@using BookLoanBlazorWASMApp.Services
@using BookLoanBlazorWASMApp.Models
@using BookLoanBlazorWASMApp.Validations
@inject IBookService LibraryBookService
@inject NavigationManager PageNavigation
@inject IJSRuntime JS

<PageTitle>Edit Book</PageTitle>

<h3>Edit Book</h3>

@if (book == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@editbook" OnSubmit="@Submit">
        <CustomBookValidation @ref="customValidation" />
        <ValidationSummary />

        <div class="form-field">
            <label>ID:</label>
            <div>
                <label>@editbook!.ID</label>
            </div>
        </div>

        <div class="form-field">
            <label>Title:</label>
            <div>
                <InputText @bind-Value=editbook.Title></InputText>    
            </div>
        </div>

        <div class="form-field">
            <label>Author:</label>
            <div>
                <InputText @bind-Value=editbook.Author></InputText>    
            </div>
        </div>

        <div class="form-field">
            <label>Year Published:</label>
            <div>
                <InputNumber @bind-Value=editbook.YearPublished></InputNumber>
            </div>
        </div>

        <div class="form-field">
            <label>Genre:</label>
            <div>
                <InputSelect @bind-Value="editbook.Genre">
                    <option value="">Select genre ...</option>
                    @foreach (var genres in @editbook.Genres)
                    {
                        <option value="@genres.Value.ToString()">@genres.Value.ToString()</option>
                    }
                </InputSelect>
            </div>
        </div>

        <div class="form-field">
            <label>Edition:</label>
            <div>
                <InputText @bind-Value=editbook.Edition></InputText>
            </div>
        </div>

        <div class="form-field">
            <label>ISBN:</label>
            <div>
                <InputText @bind-Value=editbook.ISBN></InputText>
            </div>
        </div>

        <div class="form-field">
            <label>Location:</label>
            <div>
                <InputText @bind-Value=editbook.Location></InputText>
            </div>
        </div>

        <div class="form-field">
            <label>Media Type:</label>
            <div>
                <InputSelect @bind-Value="editbook.MediaType">
                    <option value="">Select media type ...</option>
                    @foreach (var mediatypes in @editbook.MediaTypes)
                    {
                        <option value="@mediatypes.Value.ToString()">@mediatypes.Value.ToString()</option>
                    }
                </InputSelect>
            </div>
        </div>

        <br />

        <button class="btn btn-primary" type="submit">
            Update Changes
        </button>
    </EditForm>

    <br />
}

@code {
    [Parameter]
    [SupplyParameterFromQuery]
    public int ID { get; set; }

    private CustomBookValidation? customValidation;

    private BookViewModel? book;
    private BookEditModel? editbook;

    private string? message = ""; 

    protected override async Task OnInitializedAsync()
    {
        book = await LibraryBookService.GetBook(ID);
        editbook = new BookEditModel(book);
    }

    private string Message = String.Empty;

    private async void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (string.IsNullOrEmpty(editbook!.Title))
        {
            errors.Add(
                nameof(editbook.Title),
                new() { "Book 'Title' is required." }
            );
        }

        if (string.IsNullOrEmpty(editbook!.Author))
        {
            errors.Add(
                nameof(editbook.Author),
                new() { "Book 'Author' is required." }
            );
        }

        if (string.IsNullOrEmpty(editbook!.MediaType))
        {
            errors.Add(
                nameof(editbook.MediaType),
                new() { "Book 'MediaType' is required." }
            );
        }

        if (string.IsNullOrEmpty(editbook!.Genre))
        {
            errors.Add(
                nameof(editbook.Genre),
                new() { "Book 'Genre' is required." }
            );
        }

        if (editbook!.YearPublished < 1000 || editbook!.YearPublished > 3000)
        {
            errors.Add(
                nameof(editbook.YearPublished),
                new() { "Book 'YearPublished' range must be between 1000 and 3000." }
            );
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            this.UpdateBook();
        }
    }

    // Update entered book properties.
    private async void UpdateBook()
    {
        var result = await LibraryBookService.UpdateBook(ID, editbook! as BookViewModel);
        if (result != null)
            message = "Record has been successfully updated!";
        else
            message = "Unable to update record.";
        await JS.InvokeVoidAsync("alert", message);
    }
}

The model I am using to store the updated data from the form is shown below:

BookEditModel.cs

namespace BookLoanBlazorWASMApp.Models
{
    [Serializable]
    public class BookEditModel: BookViewModel
    {
        Dictionary<int, string> _media_types;

        Dictionary<int, string> _genres;

        public Dictionary<int, string> Genres
        {
            get { return _genres; }                
        }

        public Dictionary<int, string> MediaTypes
        {
            get { return _media_types; }
        }

        public BookEditModel(BookViewModel vm)
        {            
            ID = vm.ID;
            ISBN = vm.ISBN;
            Author = vm.Author;
            Edition = vm.Edition;
            Genre = vm.Genre;
            Location = vm.Location;
            MediaType = vm.MediaType;
            Title = vm.Title;
            YearPublished = vm.YearPublished;
            DateCreated = vm.DateCreated;
            DateUpdated = vm.DateUpdated;
            CreateLookups();
        }

        public BookEditModel()
        {
            CreateLookups();
        }

        private void CreateLookups()
        {
            this._genres = new Dictionary<int, string>()
            {
                { 1, "Fiction" },
                { 2, "Non-Fiction" },
                { 3, "Educational" },
                { 4, "Childrens" },
                { 5, "Family" },
                { 6, "Fantasy" },
                { 7, "Finance" },
                { 8, "Cooking" },
                { 9, "Technology" },
                { 10, "Gardening" }
            };

            this._media_types = new Dictionary<int, string>()
            {
                { 1, "Book" },
                { 2, "DVD" },
                { 3, "CD" },
                { 4, "Magazine" }
            };
        }
    }
}

The above code for the razor component and the model is essentially unchanged (aside from some namespaces) from the implementation I showed in a previous post where I showed how to use Entity Framework within Server Blazor Applications.

Using PutAsJsonAsync() to Execute Calls to a Web API

To make HTTP PUT calls to Web API methods requires us to include the following namespace in our code:

System.Net.Http.Json

The parameters required to use the PutAsJsonAsync() library method include the URI for the API method and the payload, which is an instance of the object containing the key and value pairs.

As I showed in the API web service in the previous section, the URI request pattern is of the form:

bookitems/{Id}

Where {id} is the resource identifier.

With the Book Service class that I implemented in the previous posts on HTTP GET and HTTP POST calls, I add the following method, UpdateBook() to the service class:

public async Task<int> UpdateBook(int Id, BookViewModel vm)
{
        try
        {
                await _http.PutAsJsonAsync(_apiUri + $"bookitems/{Id}", vm);
                return 1;
        }
        catch
        {
            return 0;
        }
}

For reference, the BookViewModel model class, which contains the form field properties is defined in the previous post.

When the above method UpdateBook() is included in the BookService class, it will look as follows:

BookService.cs:

using BookLoanBlazorWASMApp.Models;
using System.Net.Http.Json;

namespace BookLoanBlazorWASMApp.Services
{
    public class BookService : IBookService
    {
        private readonly ILogger _logger;
        private readonly HttpClient _http;
        private readonly string _apiUri = "http://localhost:5013/"; 

        public BookService(
            ILogger<BookService> logger, HttpClient http)
        {
            _logger = logger;
            _http = http;
        }

        /// <summary>
        /// GetBooks()
        /// </summary>
        /// <returns></returns>
        public async Task<List<BookViewModel>> GetBooks()
        {
            var books = await _http.GetFromJsonAsync<List<BookViewModel>>(_apiUri + "bookitems");
            return books!.ToList();
        }

        /// <summary>
        /// GetBook()
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<BookViewModel> GetBook(int id)
        {
            var book = await _http.GetFromJsonAsync<BookViewModel>(_apiUri + "bookitem/" + id);
            return book!;
        }

        /// <summary>
        /// SaveBook()
        /// </summary>
        /// <param name="vm"></param>
        /// <returns></returns>
        public async Task<int> SaveBook(BookViewModel vm)
        {
            try
            {
                await _http.PostAsJsonAsync(_apiUri + "bookitems", vm);
                return 1;
            }
            catch
            {
                return 0;
            }
        }

        /// <summary>
        /// UpdateBook()
        /// </summary>
        /// <param name="Id"></param>
        /// <param name="vm"></param>
        /// <returns></returns>
        public async Task<int> UpdateBook(int Id, BookViewModel vm)
        {
            try
            {
                await _http.PutAsJsonAsync(_apiUri + $"bookitems/{Id}", vm);
                return 1;
            }
            catch
            {
                return 0;
            }
        }
    }
}

In the next section, we will see how to test an HTTP PUT request from a Blazor WebAssembly.

Testing the HTTP PUT Call from the Blazor WebAssembly

To test the call to the Web API from our service within the WebAssembly web client, we run the application.

In the landing page, we select the List Books menu item.

In the list books screen, I scroll to the record I want to edit. In this case it is the same record I updated from within the Swagger interface of the Web API. I then select the record with the Edit hyperlink.

The edit form opens, from where we can see the form fields have populated with the values from the record that we selected from within the previous list screen. To retrieve the data, we made an HTTP GET request to the Web API.

Next, we notice the value of the ISBN field, it is 654321.

I then change the ISBN value to 112233.

Before I perform the update action, I first select the browser Developer Tools option so that we can view the HTTP request and response.

I then hit the Update Changes button.

Once the update has been completed, the alert will popup.

In the Developer Tools, select the Network tab. You will then see the status code of 204. 

As I mentioned in the first section, a response status code of 204 means No Content, which means the request is successful.

To verify the update to the record has been successful, we can re-run the Book listing and review the records. The ISBN will show the value has been changed to 112233 as shown:

We have seen how to apply updates to data using HTTP PUT calls to a Web API service from within a .NET Core Blazor WebAssembly web application. In a future post I will show how to use the HTTP DELETE call from the WebAssembly web application.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial