Code Focused

How to Consume Web APIs in Blazor

Let me start by emphasizing Blazor is not a committed product at this point, currently being classified as experimental. So please don't go building anything in production just yet.

What Is Blazor?
For those of you new to Blazor, it's an experimental framework from the ASP.NET team. The goal is to provide a single-page application (SPA)-style framework that allows developers to write C# and Razor code and have it run in the browser via WebAssembly.

As of the Blazor 0.5.0 release, there are now two flavors, client-side and server-side. Server-side Blazor uses the browser as a thin client, with user interactions and DOM updates sent back and forth over a SignalR connection.

In this article I'll be using the client-side flavor of Blazor. As a new, experimental technology, Blazor requires some advance setup work to get started with Visual Studio, which you can see in documentation here. More information is provided in an introductory ASP.NET blog post here.

Overview
I'm going to be calling a standard Web API with the following endpoints:

  • GET -- /api/contacts (Gets all contacts)
  • POST -- /api/contacts (Adds a new contact)
  • DELETE -- /api/contacts/{name} (Deletes a contact with the specified name)

I'll build out two components, one to list existing contacts and another to add a new contact. I'm going to add these components to a default Blazor standalone app.

I'm also going to show how you can customize your HTTP calls, and I'll talk a bit about managing credentials.

Showing Existing Contacts (GET)
I'm going to start by getting the list of existing contacts and presenting them on-screen. I'm going to add a new Razor View (currently Blazor doesn't have its own file type so we have to use Razor Views for now) to the Pages folder and call it Contacts.cshtml. Then add the following code:

@page "/contacts"
@inject HttpClient httpClient

<h1>Contacts</h1>

<NavLink class="nav-link btn btn-default" href="newcontact">Add New Contact</NavLink>

<hr />

@if (_contacts.Any())
{
<div class="card-deck">
  @foreach (var contact in _contacts)
  {
  <div class="card">
    <div class="card-body">
      <h5 class="card-title">@contact.Name</h5>
      <p class="card-text">
        <strong>Phone Number</strong> @contact.PhoneNumber
      </p>
      <p class="card-text">
        <strong>Email</strong> @contact.Email
      </p>
    </div>
  </div>
  }
</div>
}
else
{
  <p>No Contacts Found</p>
}

@functions {

  private List<Contact> _contacts = new List<Contact>();

  protected override async Task OnInitAsync() => await GetContacts();

  private async Task GetContacts() => _contacts = await httpClient.GetJsonAsync<List<Contact>>("api/contacts");
}

The markup above should be pretty straightforward, especially if you have worked with MVC and Razor before. If there are any contacts, then I'm creating a Bootstrap 4 card component for each one and printing the contact name, phone number and email. Otherwise I'm showing a message stating no contacts were found. I've also included a link to add a new contact, but I'll cover that in the next section. The more interesting stuff is in the functions block.

I'm overriding one of Blazor's lifecycle methods, OnInitAsync. This is called by Blazor when initializing the component and gives you an opportunity to set up anything your component may need. In this case I'm using it to make the call to get any existing contacts from the API.

I'm using the HttpClient provided by Blazor. This is actually the standard .NET HttpClient; it has just been initialized using a different HttpMessageHandler implementation. The Blazor team has created a BrowserHttpMessageHandler and it handles interop between .NET and the JavaScript fetch API.

As you can see from the code, I've passed a type parameter to the GetJsonAsync method. This is the type I want the response deserialized into. This is possible because the GetJsonAsync method uses a built-in JSON library to deserialize requests and responses from the server.

It's one of five extension methods provided by the Blazor team for the HttpClient. The others are:

  • PostJsonAsync(string requestUri, object content);
  • PostJsonAsync(string requestUri, object content);
  • PutJsonAsync(string requestUri, object content);
  • PutJsonAsync(string requestUri, object content);

It's worth pointing out that the JSON library included with Blazor is probably not going to be there forever. The team has stated it will likely be removed in the future when support for third-party JSON libraries such as JSON.Net can be accommodated out of the box.

Adding a New Contact (POST)
I can now list the contacts returned by the API, but I need the ability to add new ones. To do that, I'm going to add another Razor View, which I'll call NewContact.cshtml, then add the following code:

@using Microsoft.AspNetCore.Blazor.Services
@page "/newcontact"
@inject HttpClient httpClient
@inject IUriHelper uriHelper

<div class="card">
  <div class="card-body">
    <h5 class="card-title">Add New Contact</h5>
    <div class="form-group row">
      <label for="name" class="col-sm-4 col-form-label">Name</label>
      <div class="col-sm-8">
        <input bind="@contact.Name" type="text" class="form-control" id="name" placeholder="Name" />
      </div>
    </div>
    <div class="form-group row">
      <label for="email" class="col-sm-4 col-form-label">Email</label>
      <div class="col-sm-8">
        <input type="email" class="form-control" id="email" placeholder="Email" bind="@contact.Email" />
      </div>
    </div>
    <div class="form-group row">
      <label for="phoneNumber" class="col-sm-4 col-form-label">Phone Number</label>
      <div class="col-sm-8">
        <input bind="@contact.PhoneNumber" type="text" class="form-control" id="phoneNumber" placeholder="Phone Number" />
      </div>
    </div>
    <div class="form-group row">
      <div class="col-sm-8">
        <button onclick=@SaveNewContact type="button" class="btn btn-primary">Save</button>
      </div>
    </div>
  </div>
</div>

@functions {

  private Contact contact { get; set; } = new Contact();

  private async Task SaveNewContact()
  {
    await httpClient.PostJsonAsync("api/contacts", contact);
        
    uriHelper.NavigateTo("contacts");
  }
}

This component is displaying a simple form for adding a new contact. I've included the namespace for the IUriHelper and injected an instance of it so I can redirect back to the contacts page after saving the new contact.

The focus here is the SaveNewContact method. It's using another of the HttpClient extensions I mentioned earlier to post the new contact up to the API. Again, the object I'm posting is being serialized to JSON by the built-in library.

In this case I'm not interested in any response data from the API. But if I was, I could call the PostJsonAsync<T>( ... ) overload instead. For example, if I was expecting the ID of the new contact to be returned, I could write the following:

var id = await httpClient.PostJsonAsync<int>("api/contacts", contact); 

Deleting a Contact (DELETE)
I'm going to add a bit more code to the contacts component now. I'm going to add a delete button within each contacts card. And I'm going to add a new method to call the delete endpoint on the API. The contacts component now looks like this:

@page "/contacts"
@inject HttpClient httpClient

<h1>Contacts</h1>

<NavLink class="nav-link btn btn-default" href="newcontact">Add New Contact</NavLink>

<hr />

@if (_contacts.Any())
{
<div class="card-deck">
  @foreach (var contact in _contacts)
  {
  <div class="card">
    <div class="card-body">
      <h5 class="card-title">@contact.Name</h5>
      <p class="card-text">
        <strong>Phone Number</strong&g @contact.PhoneNumber
      </p>
      <p class="card-text">
        <strong>Email</strong> @contact.Email
      </p>
      <button class="btn btn-danger" onclick=@(() => DeleteContact(contact))>Delete</button>
    </div>
  </div>
  }
</div>
}
else
{
  <p>No Contacts Found</p>
}

@functions {

  private List<Contact> _contacts = new List<Contact>();

  protected override async Task OnInitAsync() => await GetContacts();

  private async Task GetContacts() => _contacts = await httpClient.GetJsonAsync<List<Contact>>("api/contacts");

  private async Task DeleteContact(Contact contact)
  {
    await httpClient.DeleteAsync($"api/contacts/{contact.Name}");
    await GetContacts();
  }
}

The only real difference here in terms of the API interaction is that I'm calling the standard DeleteAsync method, rather than one provided by the Blazor team's extensions.

This example shows that you can use the standard methods available on the HttpClient, just as you normally would in any MVC or Razor pages application. While it's ultimately calling the fetch API, from a developer standpoint you shouldn't need to care about that. Well ... most of the time.

Customizing Http Requests
Depending on your requirements, you may need to customize your HTTP requests. You may want to add custom headers, change the mode for the request, set credentials and so on.

This can be done by defining a HttpRequestMessage and passing it to the SendAsync method of the HttpClient. As an example I'll rewrite the call to get all contacts and I'll add a couple of customizations:

private async Task GetContacts()
{
  var httpRequestMessage = new HttpRequestMessage()
  {
    Method = new HttpMethod("GET"),
    RequestUri = new Uri("http://localhost:49815/api/contacts"),
  };

  httpRequestMessage.Headers.Add("foo-header", "bar-value");
  httpRequestMessage.Properties[BrowserHttpMessageHandler.FetchArgs] = new
  {
    referrer = "foo-bar"
  };

  var response = await httpClient.SendAsync(httpRequestMessage);
  var content = await response.Content.ReadAsStringAsync();
  _contacts = Json.Deserialize<List<Contact>>(content);
}

To start, I'm defining the basics of my request by setting the Method and the RequestUri. I'm then adding a custom header, but the really interesting part is the properties collection.

The properties collection is part of the standard HttpClient, but in Blazor it has a special use. When it's initialized as above, any properties it contains will be passed into the fetch API. This allows developers to manage the setting used by the fetch API directly from C#. A full list of the options available for the fetch API can be found here.

Credentials
By default the credentials option for outbound requests is set to "same-origin." This means that cookies and HTTP auth headers will be sent as long as the API is of the same origin as the Blazor app. If you need to make calls to APIs on a different origin, then you will need to change the credentials setting to "include." This can be done quite easily by changing the value on the BrowserHttpMessageHandler like so:

BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.SameOrigin;

Note this is a global change that will affect all requests. If you only want to change the setting for a specific request, then there are two options. The first is to set the new value make the request and then set it back again, like so.

BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
_contacts = await httpClient.GetJsonAsync<List<Contact>>("https://someapi.com/contacts");
BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.SameOrigin;

The second option is to change it via a custom HttpRequestMessage:

var httpRequestMessage = new HttpRequestMessage()
  {
    Method = new HttpMethod("GET"),
      RequestUri = new Uri("https://someapi.com/contacts"),
  };

httpRequestMessage.Properties[BrowserHttpMessageHandler.FetchArgs] = new
{
  credentials = "include"
};

var response = await httpClient.SendAsync(httpRequestMessage);

I suggest you use the second option. Although it's a bit more code, I think it's the better way to handle request-specific configuration.

That's it for my introductory article on working with Blazor APIs. Stay tuned for more explorations of this exciting new technology.

About the Author

Chris Sainty is a lead software engineer at Flagship Group, a housing association based in Norwich, Norfolk (UK). He leads a team in developing in-house repairs management software using a wide range of technologies. Chris is passionate about Web technologies and ASP.NET Core in particular. He loves sharing knowledge and writes regular posts on his blog at https://codedaze.io.

comments powered by Disqus

Featured

Subscribe on YouTube