CHAPTER 5
It’s very common for mobile apps to exchange data with remote services, either on-premises or on the cloud. Xamarin.Forms supports a number of communication protocols and data exchange formats, but the most common scenario is to work with data in a platform-independent approach.
For instance, a web service might serve different types of applications (desktop, mobile, web clients), and in the ideal world, it should not know in advance what the caller application is and with which development platform it has been written, so data exchange should happen with standardized, cross-platform, and cross-device formats. From the point of view of development with Microsoft technologies, this can be done by developing web API solutions that expose data as JSON or XML, and that can be consumed by any kind of application, including mobile apps created with Xamarin.Forms.
This chapter explains how to consume a web API, and how to exchange data with a web API service using JSON as the standard format.
The companion solution for this chapter is more complex, and therefore, more thoroughly explained than the other chapters in the book. In fact, it includes a complete ASP.NET Core web API project and a Xamarin.Forms project based on the Model-View-ViewModel pattern. For this reason, I assume you open the sample solution for Chapter 5 in Visual Studio 2019, and that you will follow the reading while looking at the source code in the IDE. I will only highlight and list the code snippets that are relevant to the topic of this chapter. Based on this consideration, some knowledge of the Model-View-ViewModel (MVVM) pattern is also required. You can read an introduction in my book Xamarin.Forms Succinctly. If you wish to fully try the solution and you want to publish the web API project to Azure, I recommend you do the following:
Once you have set this up, you are ready to walk through the sample solution and this chapter.
The companion example for this chapter is made of three main blocks:
These will be described in more detail in the next sections.
Creating the database and publishing the web API project to Azure has been the perfect choice for the current example, since I do not own a physical web server and, at the same time, I could work with a real remote environment rather than showing local debugging. Let’s understand a bit more of the individual blocks of the architecture.
The companion solution for this chapter works with a database called Books, which contains a simplified list of books. For each book, the database contains the title, the author, and the ISBN code. You can easily re-create the database on Azure using SQL Server Management Studio or Visual Studio 2019, connecting to the Azure database engine, and running the script shown in Code Listing 16.
Notice how a Basic profile is selected, which allows for creating a free database, limited to 1 GB of storage.
Code Listing 16
CREATE DATABASE [Books] (EDITION = 'Basic', SERVICE_OBJECTIVE = 'Basic', MAXSIZE = 1 GB) WITH CATALOG_COLLATION = SQL_Latin1_General_CP1_CI_AS; GO ALTER DATABASE [Books] SET COMPATIBILITY_LEVEL = 100 GO ALTER DATABASE [Books] SET ANSI_NULL_DEFAULT OFF GO ALTER DATABASE [Books] SET ANSI_NULLS OFF GO ALTER DATABASE [Books] SET ANSI_PADDING OFF GO ALTER DATABASE [Books] SET ANSI_WARNINGS OFF GO ALTER DATABASE [Books] SET ARITHABORT OFF GO ALTER DATABASE [Books] SET AUTO_SHRINK OFF GO ALTER DATABASE [Books] SET AUTO_UPDATE_STATISTICS ON GO ALTER DATABASE [Books] SET CURSOR_CLOSE_ON_COMMIT OFF GO ALTER DATABASE [Books] SET CONCAT_NULL_YIELDS_NULL OFF GO ALTER DATABASE [Books] SET NUMERIC_ROUNDABORT OFF GO ALTER DATABASE [Books] SET QUOTED_IDENTIFIER OFF GO ALTER DATABASE [Books] SET RECURSIVE_TRIGGERS OFF GO ALTER DATABASE [Books] SET AUTO_UPDATE_STATISTICS_ASYNC OFF GO ALTER DATABASE [Books] SET DATE_CORRELATION_OPTIMIZATION OFF GO ALTER DATABASE [Books] SET ALLOW_SNAPSHOT_ISOLATION ON GO ALTER DATABASE [Books] SET PARAMETERIZATION SIMPLE GO ALTER DATABASE [Books] SET READ_COMMITTED_SNAPSHOT ON GO ALTER DATABASE [Books] SET MULTI_USER GO ALTER DATABASE [Books] SET QUERY_STORE = ON GO ALTER DATABASE [Books] SET QUERY_STORE (OPERATION_MODE = READ_WRITE, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 7), DATA_FLUSH_INTERVAL_SECONDS = 900, INTERVAL_LENGTH_MINUTES = 60, MAX_STORAGE_SIZE_MB = 10, QUERY_CAPTURE_MODE = AUTO, SIZE_BASED_CLEANUP_MODE = AUTO) GO ALTER DATABASE [Books] SET READ_WRITE GO |
You can enter some sample data manually, or you could run the script shown in Code Listing 17 to add information for two books to the table.
Code Listing 17
USE Books GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Books]( [Id] [int] IDENTITY(1,1) NOT NULL, [Title] [nvarchar](50) NOT NULL, [Author] [nvarchar](50) NOT NULL, [ISBN] [nvarchar](50) NULL, CONSTRAINT [PK_Books] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET IDENTITY_INSERT [dbo].[Books] ON GO INSERT [dbo].[Books] ([Id], [Title], [Author], [ISBN]) VALUES (1, N'Xamarin.Forms Succinctly', N'Alessandro Del Sole', N'978-1-64200-175-4') GO INSERT [dbo].[Books] ([Id], [Title], [Author], [ISBN]) VALUES (2, N'Visual Studio 2019 for Mac Succinctly', N'Alessandro Del Sole', N'978-1-64200-177-8') GO SET IDENTITY_INSERT [dbo].[Books] OFF GO |
You can improve the table by adding the publication date or a Boolean column to mark a record as logically deleted, without actually removing the record from the database, but this is left to you as an exercise.
The web API project is responsible for working against the database using Entity Framework, and it returns JSON responses, depending on the request. For example, it will return a JSON array when a GET call is made to retrieve the list of books.
The project has been created following this very detailed article published by Syncfusion, and I recommend you to do the same if you want to re-create the sample project on your own. One important note is about securing the API, which I will also recall in the next paragraphs, when appropriate. The article explains how to implement authorization and authentication based on JWT tokens, where JWT stands for JSON Web Token, and is a way to include an authorization code inside the API calls.
Authorization and authentication implementations can be very different between companies’ policies, customer requirements, and app architecture. For example, your requirements might be implementing custom authentication with username and password, or you might need an Active Directory authentication system if your application works within a domain, or you might work with bearer tokens. For this reason, the sample web API service does not require any authentication. In this way, you will be able to implement your own system based on your requirements. I will point out, where appropriate, how to add tokens and user credentials inside the API calls.
The companion project also implements the TokenController class discussed in the Syncfusion article for your convenience, so it will be very easy for you to implement JWT tokens discussed in the article itself. The next paragraphs highlight the most relevant parts of the project.
The data model consists of a Book class that maps the Books table in the database, and looks like the following:
public partial class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
}
A specialized instance of the DbContext class is then needed to connect the database to the data model, and is represented in Code Listing 18.
Code Listing 18
public partial class BooksContext : DbContext { public BooksContext(DbContextOptions<BooksContext> options) : base(options) { } public virtual DbSet<Book> Books { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book>(entity => { entity.Property(e => e.Author) .IsRequired() .HasMaxLength(50); entity.Property(e => e.Isbn) .HasColumnName("ISBN") .HasMaxLength(50); entity.Property(e => e.Title) .IsRequired() .HasMaxLength(50); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } |
As you can see, the OnModelCreating method performs the actual binding between the table and the data model, including some validations.
Tip: You will need to replace the sample connection string in the appsettings.json file with the proper one.
The API calls are implemented inside a controller class called BooksController. The first thing to do in the controller is implement a constructor that will receive an instance of the context class via dependency injection:
private readonly BooksContext _context;
public BooksController(BooksContext context)
{
_context = context;
}
The next piece of code is related to retrieving the list of books from the database. There are two GetBooks methods: one to get the full list, and one to get an individual book based on its ID:
[HttpGet]
public async
Task<ActionResult<IEnumerable<Book>>> GetBooks()
{
return await _context.Books.ToListAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetBooks(int id)
{
var books = await _context.Books.FindAsync(id);
if (books == null)
{
return NotFound();
}
return books;
}
The code is executed asynchronously and data is obtained via methods from the DbContext class: ToListAsync for the full list, and FindAsync for the individual object. Adding a new Book object to the database is done via the POST verb and the PostBook method, as follows:
[HttpPost]
public async Task<ActionResult<Book>> PostBook(Book book)
{
_context.Books.Add(book);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBooks",
new { id = book.Id }, book);
}
The Book object that will be added to the database is received by the API call in the form of a JSON object, and you will see how to package this in Xamarin.Forms shortly.
Notice how the API call returns a CreatedAtAction object, which matches the 201 HTTP status code (Created) with the ID of the new record.
You can update existing objects by invoking the HTTP PUT verb. In this example, updating a Book object can be performed via the PutBook method, which leverages the PUT verb behind the scenes, and works as follows:
public async Task<IActionResult> PutBook(int id, Book book)
{
if (id != book.Id)
{
return BadRequest();
}
_context.Entry(book).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BooksExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool BooksExists(int id)
{
return _context.Books.Any(e => e.Id == id);
}
As with adding an object, the Book entity instance is still received from the caller clients. One difference is that the entity state changes to EntityState.Modified and the code checks for concurrency exceptions. This method returns a NoContent response, which matches the 204 HTTP status code.
The very last method is called DeleteBook, which receives only the ID for the Book object you want to delete:
[HttpDelete("{id}")]
public async Task<ActionResult<Book>> DeleteBook(int id)
{
var books = await _context.Books.FindAsync(id);
if (books == null)
{
return NotFound();
}
_context.Books.Remove(books);
await _context.SaveChangesAsync();
return books;
}
The code is very easy, and this is a standard implementation. However, if you want to store the full history of the data, I recommend you add a BIT column called IsDeleted to the database, add a same-named property of type bool to the Book class, and replace the DbContext.Remove invocation as follows:
Book.IsDeleted = true;
In this way, the code will not physically remove the record from the database; it will mark it as logically deleted. If you go for this option, you might also want to change the GetBooks method body as follows:
return await _context.Books.Where(b=>b.IsDeleted == false).ToListAsync();
Once you understand the most relevant pieces of code, it’s time to test the API calls. You can do this by starting the web API project for debugging on the local machine, or you can deploy it to Azure (or to an on-premises server) for the most realistic experience. Whichever option you choose, you need a tool to make and test API calls.
One of the most popular and free tools is Postman. With Postman you can make API calls with any of the HTTP verbs, and you can include all the information needed for the request through a simple user interface. Whichever option you go with, take note of the web API address.
In Postman, open a new request to make a GET call, and paste the URL of your web API service and the /api/books suffix. When you’re ready, simply click Send. After a few seconds, you will see the list of books in the results window, in the form of a JSON array, as shown in Figure 12.

Figure 22: Getting the list of books with Postman
Adding a new book can be done by changing the HTTP verb to POST and specifying the data in the Body tab. If you select the raw view, you can write the JSON directly, as shown in Figure 13.

Figure 33: Adding a new object with a POST request
Another required step is opening the Headers tab and adding a new header tag of type Content-Type in the KEY column, with a value of application/json. When you click Send, the PostBook API call will be invoked, and the new object will be written into the database. The response contains the full properties for the newly added object, and the most important one is the new record ID.
Tip: If the web API implements a custom authentication mechanism with username and password, these will be included in the JSON sent to the API. If the implementation relies on a token, this will instead be included in the Authorization tab, where you will find a dropdown list of the most popular token types.
If you want to double-check, make the GET call again to see the list of books, and you will see that the new one is available. Updating a book will work very similarly: you will need to write the full list of Book properties and change the HTTP verb from POST to PUT. The URL of the API call will also need to include the book ID in the following form: /api/books/1, where 1 is the record ID.
Deleting a book instance is also easy. You must change the HTTP verb to DELETE and pass the record ID into the URL like you did for the PUT call. Then, in the Body tab, you select none, as the request has no body. Once you have ensured the API calls work outside of any application and development environment, you can understand how to consume the API in Xamarin.Forms.
The Xamarin.Forms project provides the following points of interest:
Tip: Due to the popularity of Newtonsoft.Json, Microsoft has created some documentation to help you migrate to System.Text.Json.
The final result of the work is shown in Figure 14, where you can see a list of books, and in Figure 15, where you can see the insertion of a new book. You can keep both figures as a reference since I will not list the XAML code (which you can instead follow through the companion source code).

Figure 14: The sample app displays a list of books

Figure 45: The sample app allows adding and saving a new book
Calling web APIs from Xamarin.Forms is not difficult, and is accomplished via the HttpClient class and its methods. It is good practice to group methods that work with a web API into a separate class. In the sample project, it’s called WebApiService, and it exposes methods to read, write, and delete data. Another good practice is to write reusable methods, avoiding code that targets only a specific data type.
A method called GetDataAsync sends a GET request to the specified endpoint, and is implemented as follows:
public static async Task<HttpResponseMessage> GetDataAsync(string url,
string id = null)
{
HttpResponseMessage response;
try
{
using (var client = new HttpClient())
{
if (id != null) url = $"{url}/{id}";
response = await client.GetAsync(url);
}
}
catch (HttpRequestException)
{
response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
catch
{
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
The reason for the id optional parameter is that, with a single method, you can obtain either the full list of objects from the specified endpoint (in our example a list of books), or an individual object from the specific API overload. The actual API call is done by the HttpClient.GetAsync method, and the resulting response is returned as it is to the caller (the BooksViewModel class in our case), which will be responsible for data processing. Multiple catch statements return different HTTP errors. This is to demonstrate how you can control in your logic the different error types that you might get from a web API service.
As I mentioned previously, the companion source code provides all the necessary code files to implement JWT tokens to secure the API calls, but this is left to you as an exercise, and API calls can be currently invoked with the so-called anonymous authentication. This is because it is not possible to demonstrate all the possible authentication solutions and, at the same time, to offer a working example. However, a few hints can be provided. If your web API requires a token, you can add the following line after instantiating the HttpClient class:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "Your Oauth token");
The constructor of the AuthenticationHeaderValue receives the Bearer literal and the authorization token, and is assigned to the Authorization property of the request’s headers. If your web API services rely on Windows domain authentication, you can create an instance of the HttpClient class, specifying that it needs to use the default Windows user credentials:
var client =
new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
The following method invokes a web API to delete data, and is very similar to the previous method:
public static async Task<HttpResponseMessage>
DeleteDataAsync(string url, int id)
{
HttpResponseMessage response;
try
{
using (var client = new HttpClient())
{
string fullUri = $"{url}/{id}";
response = await client.DeleteAsync(fullUri);
}
}
catch (HttpRequestException)
{
response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
catch
{
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
The only difference is that this method invokes HttpClient.DeleteAsync to send a DELETE request. The next method, called WriteDataAsync, is very interesting. Its purpose is to send a POST request to a web API, and its implementation takes advantage of .NET generics, so it can be reused:
public static async Task<HttpResponseMessage>
WriteDataAsync<T>(T data, string url, string id = null)
{
HttpResponseMessage response;
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string json = JsonConvert.SerializeObject(data);
var content =
new StringContent(json, Encoding.UTF8, "application/json");
if (id != null) url = $"{url}/{id}";
response = await client.PostAsync(url, content);
}
}
catch (HttpRequestException)
{
response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
catch
{
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
For a better understanding of the code, you can focus on two steps you did manually in Postman. The first step was adding the application/JSON content-type information to the request’s headers. In the code, this is done by adding to the DefaultRequestHeaders collection a new object of type MediaTypeWithQualityHeaderValue, which receives the specified MIME type as an argument.
The second step you made in Postman was specifying the JSON markup for the new book you wanted to add to the database via API. In the code, this takes two lines. The first line serializes the object you want to write into JSON via the JsonConvert.SerializeObject method. The second line creates an instance of the StringContent class based on the serialized data, the encoding, and the MIME type. In fact, it’s not enough for the API to receive the serialized object; it is also necessary for the API to know which text encoding and data format is used. Finally, the API call is made by invoking HttpClient.PostAsync.
You can update existing data by sending an HTTP PUT request to the API via the HttpClient.PutAsync method. You have different options:
I would personally choose the second option, which allows for having less code, and does not create more risk of confusion in the API code.
All the methods in the WebApiService class return an object of type HttpResponseMessage. Among the others, this object exposes a StatusCode property, of type HttpStatusCode, which contains the code for the result of the API call, and the Content property, of type HttpContent, which contains the actual response from the API.
Each view model will check the status code and the HTTP response. In the sample app, one view model called BooksViewModel is available. Here, a method called LoadBooksAsync is invoked by a command called LoadBooksCommand, and gets the lists of books from the web API as follows:
private async Task LoadBooksAsync()
{
// Replace with the URL of your Web API…
string url =
"https://yourwebapi.azurewebsites.net/api/books";
var result = await WebApiService.GetDataAsync(url);
switch (result.StatusCode)
{
case HttpStatusCode.OK:
string resultString =
await result.Content.ReadAsStringAsync();
var deserialized =
JsonConvert.DeserializeObject<List<Book>>(resultString);
Books = new ObservableCollection<Book>(deserialized);
return;
default:
MessagingCenter.
Send(this, "ServerError",
$"{result.StatusCode} {result.ReasonPhrase}");
return;
}
}
As you can see, the view model handles the status code returned by the GetDataAsync method and, in case of errors, sends a broadcast message that the user interface will catch to display a warning to the user. (See the MainPage.xaml.cs file in the companion source code for the implementation). If it’s successful, the response of the API call is first converted into a plain string.
Then, because the code expects a list of books as the response, the JsonConvert.DeserializeObject converts the obtained string into a specialized List<Book> object. The next line of code creates an ObservableCollection<Book> instance, because this type of collection is used to data-bind collections to the user interface. The behavior is similar for the AddBookAsync method, which invokes the WebApiService.PostAsync method to write a new book into the database:
private async Task AddBookAsync()
{
if (NewBook != null)
{
string url = "https://yourwebapi.azurewebsites.net/api/books";
var result = await WebApiService.WriteDataAsync(NewBook, url);
switch (result.StatusCode)
{
case HttpStatusCode.OK:
case HttpStatusCode.Created:
string resultString = await
result.Content.ReadAsStringAsync();
Book deserializedBook =
JsonConvert.DeserializeObject<Book>(resultString);
Books.Add(deserializedBook);
MessagingCenter.Send(this, "BookSaved");
return;
default:
MessagingCenter.Send(this, "ServerError",
$"{result.StatusCode} {result.ReasonPhrase}");
return;
}
}
}
This behavior is similar to the LoadBooksAsync method. Here the code also handles the Created HTTP status code. In this case, JsonConvert.DeserializeObject returns the individual Book instance that has been added to the database, and this will be added to the Books collection of the view model so that the user interface can update accordingly.
The DeleteBookAsync in the view model is also similar:
private async Task DeleteBookAsync()
{
string url =
"https://yourwebapi.azurewebsites.net/api/books";
var result =
await WebApiService.DeleteDataAsync(url, SelectedBook.Id);
switch (result.StatusCode)
{
case HttpStatusCode.OK:
string resultString = await result.Content.ReadAsStringAsync();
// Do anything you need with the deleted object...
Book deserializedBook =
JsonConvert.DeserializeObject<Book>(resultString);
Books.Remove(SelectedBook);
MessagingCenter.Send(this, "BookDeleted");
return;
default:
MessagingCenter.
Send(this, "ServerError",
$"{result.StatusCode} {result.ReasonPhrase}");
return;
}
}
The WebApiService.DeleteDataAsync method returns a Book object representing the deleted one, in case you need to display information to the user. The corresponding instance represented by the SelectedBook property is also removed from the Books collection.
Working with web APIs and JSON is really easy and fast in Xamarin.Forms, and with a bit of knowledge of the Model-View-ViewModel pattern, you can create a very flexible and solid architecture.
This chapter discussed a key topic in real-world development, which is invoking web APIs from a Xamarin.Forms project and deserializing JSON responses, based on a SQL database.
You have seen how web APIs make it easy to work with data, taking advantage of the Entity Framework and of the implementation of HTTP verbs (GET, POST, PUT, and DELETE). You have seen how easy it is to invoke a web API service from Xamarin.Forms, via the HttpClient class, and how to customize the request.
Finally, you have seen how to deserialize a response using the de facto standard library called Newtonsoft.Json, and how to turn the returned JSON into a .NET data model.
The communication between the mobile app and the web API shown in this chapter relies on the HTTPS protocol, which offers a high-level of security. However, this might not be enough—you might be required to go a step further by detecting certificate pinning attacks. This is covered in the next chapter.