Up until now, we have covered a lot of ground related to our Blazor WebAssembly application. But, we still have to cover the handling of PUT and DELETE actions in the same application. Ideally, we want to ask our users if they are sure about the Delete action. For that, we are going to show you how to call JavaScript functions in Blazor WebAssembly and implement a confirmation window.

To download the source code for this article, you can visit the Calling JS Functions in Blazor WebAssembly repository.

For the complete navigation for this series, you can visit the Blazor Series page.

We are going to divide this article into the following topics:

Implementing the GetProduct Action in Web API

Before we start with the PUT and DELETE actions, we have to implement an additional action on the Web API’s side. So, let’s start with the IProductRepository interface modification:

public interface IProductRepository
{
    Task<PagedList<Product>> GetProducts(ProductParameters productParameters);
    Task CreateProduct(Product product);
    Task<Product> GetProduct(Guid id);
}

Right after that, we are going to modify the ProductRepository class:

public async Task<Product> GetProduct(Guid id) => 
    await _context.Products.FirstOrDefaultAsync(p => p.Id.Equals(id));

Now, let’s create the GetProduct action in the Products controller:

[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(Guid id)
{
    var product = await _repo.GetProduct(id);
    return Ok(product);
}

That’s it. Feel free to test this to make sure it’s working as expected.

Handling the PUT Request in Blazor WebAssembly and Web API

Let’s start with the server-side implementation.

First, we need to modify the IProductRepository interface:

Task UpdateProduct(Product product, Product dbProduct);

Then, let’s modify the ProductRepository class:

public async Task UpdateProduct(Product product, Product dbProduct)
{
    dbProduct.Name = product.Name;
    dbProduct.Price = product.Price;
    dbProduct.ImageUrl = product.ImageUrl;
    dbProduct.Supplier = product.Supplier;

    await _context.SaveChangesAsync();
}
Take note that we didn’t want to make the logic more complicated by using AutoMapper, but we strongly recommend using it for the mapping actions. To read more on how to use the AutoMapper library in the ASP.NET Core application, you can read our Getting Started with AutoMapper article.

Additionally, if you are confused why don’t we use the Update method from the _context object, we strongly recommend reading our EntityFramework Core series or just Modifying Data with EF Core article that is strictly related to this topic.

The last step for the server-side implementation is to create a new UpdateProduct action in the Products controller:

[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(Guid id, [FromBody]Product product)
{
    //additional product and model validation checks

    var dbProduct = await _repo.GetProduct(id);
    if (dbProduct == null)
        return NotFound();

    await _repo.UpdateProduct(product, dbProduct);

    return NoContent();
}

For additional information and a deep-dive guide about how to handle the PUT action in ASP.NET Core Web API, please read our article on this topic.
These articles will provide a lot more insights about handling the PUT and all the other requests in ASP.NET Core Web API.

Now, let’s test this:

Update postman

We can see the update is successful. And if we check the logs, we can see that only the price was updated because we changed only that property:

Update EF Core log

Excellent. Now let’s continue with the Blazor WebAssembly part.

The Client-Side implementation

The first thing we are going to do is to add a new method signature in the IProductHttpRepository interface:

Task<Product> GetProduct(string id);
Task UpdateProduct(Product product, string id);

Next, let’s modify the ProductHttpRepository class:

public async Task<Product> GetProduct(string id)
{
    var url = Path.Combine("https://localhost:5011/api/products/", id);

    var response = await _client.GetAsync(url);
    var content = await response.Content.ReadAsStringAsync();
    if (!response.IsSuccessStatusCode)
    {
        throw new ApplicationException(content);
    }

    var product = JsonSerializer.Deserialize<Product>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
    return product;
}
public async Task UpdateProduct(Product product, string id)
{
    var content = JsonSerializer.Serialize(product);
    var bodyContent = new StringContent(content, Encoding.UTF8, "application/json");
    var url = Path.Combine("https://localhost:5011/api/products/", id);

    var putResult = await _client.PutAsync(url, bodyContent);
    var putContent = await putResult.Content.ReadAsStringAsync();

    if (!putResult.IsSuccessStatusCode)
    {
        throw new ApplicationException(putContent);
    }
}

As you can see, these are pretty straight forward methods and there is nothing we haven’t seen in the previous articles of this series.

Now, let’s create the UpdateProduct component files in the Pages folder.

We are going to modify the class file first:

public partial class UpdateProduct
{
    private Product _product;
    private SuccessNotification _notification;

    [Inject]
    IProductHttpRepository ProductRepo { get; set; }
    [Parameter]
    public string Id { get; set; }

    protected async override Task OnInitializedAsync()
    {
        _product = await ProductRepo.GetProduct(Id);
    }

    private async Task Update()
    {
        await ProductRepo.UpdateProduct(_product, Id);
        _notification.Show();
    }

    private void AssignImageUrl(string imgUrl) => _product.ImageUrl = imgUrl;
}

We can see one important difference from the CreateProduct component and that’s the OnInitializedAsync method. By calling it, as soon as our component initializes, we call the GetProduct method from the repository class and populate our _product variable. We have to populate the _product object to assign values to our controls on the Update form. The Update method will trigger as soon as we click the submit button on the form and the AssignImageUrl will do exactly that if we change the product’s picture.

After the class modification, let’s modify the razor file:

@page "/updateProduct/{id}"

<h2>Update Product Form</h2>

@if(_product != null)
{
    <EditForm Model="_product" OnValidSubmit="Update" class="card card-body bg-light mt-5">
        <DataAnnotationsValidator />
        <div class="form-group row">
            <label for="name" class="col-md-2 col-form-label">Name:</label>
            <div class="col-md-10">
                <InputText id="name" class="form-control" @bind-Value="_product.Name" />
                <ValidationMessage For="@(() => _product.Name)" />
            </div>
        </div>

        <div class="form-group row">
            <label for="supplier" class="col-md-2 col-form-label">Supplier:</label>
            <div class="col-md-10">
                <InputText id="supplier" class="form-control" @bind-Value="_product.Supplier" />
                <ValidationMessage For="@(() => _product.Supplier)" />
            </div>
        </div>

        <div class="form-group row">
            <label for="price" class="col-md-2 col-form-label">Price:</label>
            <div class="col-md-10">
                <InputNumber id="price" class="form-control" @bind-Value="_product.Price" />
                <ValidationMessage For="@(() => _product.Price)" />
            </div>
        </div>

        <div class="form-group row">
            <label for="image" class="col-md-2 col-form-label">Image:</label>
            <div class="col-md-10">
                <ImageUpload ImgUrl="@_product.ImageUrl" OnChange="AssignImageUrl" />
            </div>
        </div>

        <div class="row">
            <div class="col-md-12 text-right">
                <button type="submit" class="btn btn-success">Update</button>
            </div>
        </div>
    </EditForm>
    <SuccessNotification @ref="_notification" />
}

So, we use the @page directive to create a route to this page. We can see that this route has an id parameter. This parameter will be automatically assigned to the Id parameter in the class file. Once the _product parameter is populated, we generate the form controls. We use again the EditForm component but this time we pass the Update method for the OnValidSubmit event emitter. The controls are the same as in the CreateProduct form except for the ImageUpload component, where we pass the image URL as well because we already have that information.

Finally, to be able to navigate to this page, we have to modify the ProductTable component file:

<td class="align-middle">
    <button type="button" class="btn btn-info" @onclick="() => RedirectToUpdate(product.Id)">Update</button>
</td>

And the class file:

public partial class ProductTable
{
    [Parameter]
    public List<Product> Products { get; set; }
    [Inject]
    public NavigationManager NavigationManager { get; set; }

    private void RedirectToUpdate(Guid id)
    {
        var url = Path.Combine("/updateProduct/", id.ToString());
        NavigationManager.NavigateTo(url);
    }
}

We just inject the NavigationManager service to be able to navigate to the required page. We’ve learned about this service in the Routing Part of this series.

Testing the Update Actions

Now, let’s start both server and client applications and navigate to the Products page. Then, we can click the update button and see all the fields populated:

Blazor WebAssembly Update form

Let’s change the price field to 36 and click the Update button:

Update success

Excellent.

Once we click the close button, we can see our product with the updated price:

Product updated price

So, as we can see, everything is working like a charm.

Now, we can continue with the Delete action.

Delete Action – Web API

Let’s start with the IProductRepository interface modification:

Task DeleteProduct(Product product);

Then, let’s implement this method in the repository class:

public async Task DeleteProduct(Product product)
{
    _context.Remove(product);
    await _context.SaveChangesAsync();
}

And finally, let’s modify the controller:

public async Task<IActionResult> DeleteProduct(Guid id)
{
    var product = await _repo.GetProduct(id);
    if (product == null)
        return NotFound();

    await _repo.DeleteProduct(product);

    return NoContent();
}

That’s all it takes. Let’s move on to the client-side.

Delete Action – Blazor WebAssembly

Let’s start with the IProductHttpRepository interface modification:

Task DeleteProduct(Guid id);

And let’s continue with the ProductHttpRepository class:

public async Task DeleteProduct(Guid id)
{
    var url = Path.Combine("https://localhost:5011/api/products", id.ToString());
            
    var deleteResult = await _client.DeleteAsync(url);
    var deleteContent = await deleteResult.Content.ReadAsStringAsync();

    if (!deleteResult.IsSuccessStatusCode)
    {
        throw new ApplicationException(deleteContent);
    }
}

Now, in the ProductTable class file, we are going to add a new parameter:

[Parameter]
public EventCallback<Guid> OnDeleted { get; set; }

And to invoke it in the ProductTable component file:

<td class="align-middle">
    <button type="button" class="btn btn-danger" @onclick="() => OnDeleted.InvokeAsync(product.Id)">Delete</button>
</td>

This means that in the parent component, we need to have a method connected to this event callback parameter. So, let’s create it in the Products class file:

private async Task DeleteProduct(Guid id)
{
    await ProductRepo.DeleteProduct(id);
    _productParameters.PageNumber = 1;
    await GetProducts();
}

And of course, we have to modify the Products.razor file:

<div class="row">
    <div class="col">
        <ProductTable Products="ProductList" OnDeleted="DeleteProduct" />
    </div>
</div>

If we try to delete any product at this moment, we will be able to do it. But, we don’t want to allow users to delete a product just like that. We want them to confirm their decision just in case they clicked by accident.

For that, we could create another modal component and use it, but in this case, we are going to show you how to use the JavaScript’s confirmation dialog.

JavaScript Functions in Blazor WebAssembly

Using JavaScript functions in Blazor WebAssembly is pretty easy because we have the IJSRuntime service that helps us in the process. By injecting it into the component, we can invoke the JavaScript functions in Blazor WebAssembly. Basically, it represents an instance of a JavaScript runtime to which calls may be dispatched.

Now, let’s see how to use this service in practice.

We are going to start with the ProductTable class file, by injecting the IJSRuntime service:

[Inject]
public IJSRuntime Js { get; set; }

And adding a new method:

private async Task Delete(Guid id)
{
    var product = Products.FirstOrDefault(p => p.Id.Equals(id));
    
    var confirmed = await Js.InvokeAsync<bool>("confirm", $"Are you sure you want to delete {product.Name} product?");
    if(confirmed)
    {
        await OnDeleted.InvokeAsync(id);
    }
}

We extract the product by its id property from the local Products list. Then, we use the Js property and the InvokeAsync method to call the confirm JavaScript function and pass a parameter to that function. If a user confirms the delete action, we invoke our event callback parameter and execute the method from the parent component.

Now, all we have to do is to modify the @onclick event on the Delete button in the razor file:

<td class="align-middle">
    <button type="button" class="btn btn-danger" @onclick="() => Delete(product.Id)">Delete</button>
</td>

After these changes, we can test our result:

Delete with confirmation - JavaScript Functions in Blazor WebAssembly

As you can see, we can remove our product only if we confirm our decision.

Great job.

Conclusion

In this article, we have learned how to handle the PUT and DELETE requests on both Web API and Blazor WebAssembly sides. Additionally, we have learned how to use the IJSRuntime service to invoke JavaScript functions in Blazor WebAssembly applications.

In the next article, we are going to learn about AuthenticationStateProvider in Blazor WebAssembly and how to use it for the authentication actions.