How to consume a REST API using Blazor

How to consume a REST API using Blazor

The RESTful service

A couple of blog posts ago I wrote about how to design a REST API with ASP.NET Core and now I intend to show how to create a front end that can interface this service in order to create and remove products from the repository.

To recap, the RESTful service was designed to manage a repository containing Products where a Product is defined like this:

public class Product
{
  public long Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
}

And the API to manage the Products looks like this:

URI Operation Description
/products GET Lists all the products
/products/{id} GET Returns information about a specific product
/products POST Creates a new product
/products/{id} PUT Replaces an existing product
/products/{id} DELETE Deletes a product

I will create an application, using Blazor, that can be used to list all products, create a new product, and delete an existing product.

Creating the Blazor application

Start by creating a new Blazor application. If you’ve never done this before there are some components that you need to install, follow the instruction on https://blazor.net/docs/get-started.html for details on how to do this.

Once you have all dependencies in place run the following command:

dotnet new blazor -o ProductsManager

which will create a new Blazor project named ProductsManager.

You can test that it works by changing directory to the new ProductsManager directory and running the application:

cd ProductsManager

dotnet run

You should now be able to open your web browser and see you Blazor application in action.

 

Notice the Fetch data link. Clicking this loads the FetchData component which we now will modify to fetch data via our REST API.

Fetching our Data

Open the ProductsManager project file in Visual Studio. A solution will be created automatically for you.

In the Pages folder you will find the FetchData component. If you open the source file (FetchData.cshtml) and scroll to the bottom you will find a call to HttpClient.GetJsonAsync that currently just reads a sample file located in wwwroot/sample-data.

The first thing we do is to replace the weather.json file with products.json in the wwwroot/sample-date/ and enter some sample data. Rename the weather.json file to products.json and replace the content with the following:

[
  {
    "Id": 1,
    "Name": "Unsteady chair",
    "Description":  "Chess player's secret weapon"
  },
  {
    "Id": 2,
    "Name": "Weather controller",
    "Description": "Control the weather with a simple click of a button. Requires two AA batteries (not included)"
  }
]

Now you will also need to update the FetchData component. Change the WeatherForecast class to look like the Product class above and update all related code to match these changes. The updated file is listed below:

@page "/fetchdata"
@inject HttpClient Http

<h1>Products listing</h1>

@if (products == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in products)
            {
                <tr>
                    <td>@product.Id</td>
                    <td>@product.Name</td>
                    <td>@product.Description</td>
                </tr>
            }
        </tbody>
    </table>
}

@functions {
    Product[] products;

    protected override async Task OnInitAsync()
    {
        products = await Http.GetJsonAsync<Product[]>("sample-data/products.json");
    }

    class Product
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}

Now, if everything works as expected you should be able to see the product listing from the sample file instead of the Weather Forecast when you click the Fetch Data link in your browser.

To get the data from an external web service via a REST API you simply need to change the ”sample-data/products.json” string to the URL of the web service. In my test I ran the server on localhost on port 44359. So when testing I added a const string:

private const string APIServer = "https://localhost:44359/api/products";

and then just changed the OnInitAsync method to look like this:

protected override async Task OnInitAsync()
{
  products = await Http.GetJsonAsync<Product[]>(APIServer);
}

Important: If you get access denied errors you need to ensure that you have set up Cross-Origin Resource Sharing (CORS) correctly in the RESTful service. You can read about how to do that here: https://docs.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.1

Once you have that working it should be quite straight forward how to extend the FetchData component to be able to add and remove products by using the REST API. Below I have a code listing of my working component:

@page "/fetchdata"
@inject HttpClient Http

<h1>Interfacing the Products API</h1>

@if (products == null)
{
  <em>Loading...</em>
}
else
{
  <table class="table">      
  <thead>          
  <tr>
    <th>Id</th> 
    <th>Name</th>            
    <th>Description</th>            
    <th></th>
  </tr>
  </thead>      
  <tbody>
  @foreach (var product in products)
  {              
    <tr>
      <td>@product.Id</td>
      <td>@product.Name</td>              
      <td>@product.Description</td>
      <td><input type="button" value="Delete" onclick="@(async () => await Delete(product.Id))" /></td>
    </tr>
  }
  </tbody>
  </table>

  <h2>Add a new product</h2>
  <form>
  <table>
  <tr>
    <td><label>Name</label></td>              
    <td><input type="text" bind="@Name" /></td>
  </tr>
  <tr>
    <td><label>Description</label></td>
    <td><input type="text" bind="@Description" /></td>
  </tr>
  <tr>
    <td></td>              
    <td><input type="button" value="Add" onclick="@(async () => await Add())" /></td>
  </tr>
  </table>
  </form>
}

@functions {
    private const string APIServer = "https://localhost:44359/api/products";

    private Product[] products;

    private string Name { get; set; } = "";
    private string Description { get; set; } = "";

    protected override async Task OnInitAsync()
    {
        products = await Http.GetJsonAsync<Product[]>(APIServer);
    }

    private async Task Add()
    {
        var newProduct = new Product { Name = Name, Description = Description };
        Name = string.Empty;
        Description = string.Empty;
        await Http.SendJsonAsync(HttpMethod.Post, APIServer, newProduct);
        products = await Http.GetJsonAsync<Product[]>(APIServer);
    }

    private async Task Delete(long productId)
    {
        await Http.DeleteAsync(APIServer + "/" + productId);
        products = await Http.GetJsonAsync<Product[]>(APIServer);
    }

    private class Product
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}

I hope you found this at least a bit helpful. If anything is unclear just add a comment. And If you want me to publish the code on GitHub just let me know and I will do that as well.